2020牛客暑期多校训练营(第十场)

本文探讨了树形结构在算法竞赛中的应用,包括树的边权修改、比赛日程优化及同构树匹配问题。通过实例分析,介绍了如何利用数据结构如multiset优化复杂度,构造比赛日程以最小化队伍等待时间,以及使用费用流或最大权匹配解决树的同构匹配问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目链接

C - Decrement on the Tree
题意: 给你一颗有边权的无根树,你可以每次选取一条路径使得该路径上所有的边权减少1,现在有q次修改询问使得所有边权变为0的最少操作次?
思路: 由于有多次询问,所以每次修改询问,我们要做到复杂度为O(1)或者O(logn),所以比赛的时候一直在想有什么数据结构可以维护路径。但是这道题你需要将其转化一下,他就变得容易了。题目可以转化成算每个点作为路径端点的贡献,这样只需要算每个端点的贡献了。
接下来如何算每个端点的贡献呢?
考虑与一个点所有相连的边总权值与最大权值,要使得这个点不作为端点,我们每次最优选取两条边为同一条路径,所有就会出现三种情况:
1)相邻边权的最大值(max)大于其余剩下的相邻边权总和(rest),贡献为max-rest
2)相邻边权的最大值小于其余剩下的相邻边权总和且该点所有相邻边总边权为奇数,贡献为1
3)相邻边权的最大值小于其余剩下的相邻边权总和且该点所有相邻边总边权且为偶数,贡献为0
这样我们就能算每个点的贡献了,但是如何能做到快速修改呢
我们可以使用stl中的multiset进行修改即可
因为我们算出所有点的总贡献,一条路径有两个端点,所有最终得到的答案需要/2
代码:

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define lowbit(x) x & -x
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=1e5+5;
struct node{
    int u,v,w;
}edge[N];

ll sum[N];
multiset<int> s[N];
ll ans;
ll get(int x){
    auto t=s[x].end();
    t--;
    if(*t>sum[x]-*t)return *t-(sum[x]-*t);
    if(sum[x]%2==1)return 1;
    return 0;
}

int main(){
#ifdef Mizp
    freopen("in.txt","r",stdin);
#endif
    int n,q;
    scanf("%d%d",&n,&q);
    for(int i=1;i<n;i++){
        scanf("%d%d%d",&edge[i].u,&edge[i].v,&edge[i].w);
        sum[edge[i].u]+=edge[i].w;
        sum[edge[i].v]+=edge[i].w;
        s[edge[i].u].insert(edge[i].w);
        s[edge[i].v].insert(edge[i].w);
    }
    //cout<<get(2)<<endl;
    for(int i=1;i<=n;i++)
        ans+=get(i);
    
    printf("%lld\n",ans/2);
    while(q--){
        int p,w;
        scanf("%d%d",&p,&w);
        ans-=get(edge[p].u);ans-=get(edge[p].v);
		s[edge[p].u].erase(s[edge[p].u].lower_bound(edge[p].w));
		s[edge[p].v].erase(s[edge[p].v].lower_bound(edge[p].w));
		sum[edge[p].u]-=edge[p].w;sum[edge[p].v]-=edge[p].w;
		edge[p].w=w;
		sum[edge[p].u]+=edge[p].w;sum[edge[p].v]+=edge[p].w;
		s[edge[p].u].insert(w);s[edge[p].v].insert(w);
		ans+=get(edge[p].u);ans+=get(edge[p].v);
        printf("%lld\n",ans/2);
    }
    return 0;
}

I - Tournament
题意: 现在有 n 个队伍参加比赛,任意两个队伍之间都要进行一次比赛,也就是共需要进行 n * ( n - 1 ) / 2 次比赛,对于每个队伍来说,必须要在第一场比赛的时候到达赛场,在最后一场比赛结束后离开赛场,在赛场上呆的时间即为贡献,现在求出一种比赛的安排顺序,使得每个队伍的贡献之和最小
思路: 构造题,比赛构造了很久也构造不出来,具体思路请参考他人优秀题解
代码:

#include<bits/stdc++.h>
#include<iostream>
#include<cstdio>
#include<cstring>
#include<string>
#include<algorithm>
#include<stack>
#include<queue>
#define INF 0x3f3f3f3f
#define lowbit(x) x & -x
#define lson root<<1,l,mid
#define rson root<<1|1,mid+1,r
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int N=2e5+5;
int main(){
#ifdef Mizp
    freopen("in.txt","r",stdin);
#endif
    int T;
    scanf("%d",&T);
    while(T--){
        int n;
		scanf("%d",&n);
		for(int i=1;i<=n/2;i++)
            for(int j=1;j<i;j++)
                printf("%d %d\n",j,i);
        puts("");
		for(int i=n/2+1;i<=n;i++)
            for(int j=1;j<=n-i;j++)
                printf("%d %d\n",j,i);
        puts("");
		for(int i=1;i<=n/2;i++)
            for(int j=1;j<=i;j++)
                printf("%d %d\n",i,n-j+1);
        puts("");
		for(int i=n/2+1;i<n;i++)
            for(int j=i+1;j<=n;j++)
                printf("%d %d\n",i,j);
	}
    return 0;
}

J - Identical Trees
题意: 给定同构有根树两棵树(即形状一样,保证有解),现在你可以将第一棵树中的任意一个节点编号改成任意一个数。现问最少需要多少次操作才能将第一棵树改成与第二棵树相同。
相同的要求是对于每个节点其父节点相同,但是对于一个节点其子节点的顺序可以不一样。
思路: 比赛的时候想成无根树,直接GG,正确思路应该是从根节点开始,进行暴力dfs验证,若是二者子树大小不一样直接剪枝return掉,然后将符合的进行二分图最大权匹配或者跑费用流都行,若流量不等于子树大小或者不是完美匹配则剪枝return掉

代码:
费用流:

#include <bits/stdc++.h>
#define inf 0x3f3f3f3f
#define debug(x) cout<<"debug:"<<#x<<" = "<<x<<endl;
using namespace std;
typedef long long ll;
 
/*
MCMF 最小费用最大流
*/
const int INF = 0x3f3f3f3f;
const int maxn = 1000 + 10;
struct Edge
{
    int u, v, c, f, cost;
    Edge(int u, int v, int c, int f, int cost):u(u), v(v), c(c), f(f), cost(cost){}
};
struct MCMF
{
    vector<Edge>e;
    vector<int>G[maxn];
    int a[maxn];//找增广路每个点的水流量
    int p[maxn];//每次找增广路反向记录路径
    int d[maxn];//SPFA算法的最短路
    int inq[maxn];//SPFA算法是否在队列中
    int n, m;
    void init(int s1,int s2)
    {
 
        e.clear();
        for(int i = 0; i <= s1+s2+1; i++)G[i].clear();
        int s = s1+s2,t = s1+s2+1;
        for(int i=0;i<s1;i++) addEdge(s,i,1,0);
        for(int i=s1;i<s1+s2;i++) addEdge(i,t,1,0);
    }
    void addEdge(int u, int v, int c, int cost)
    {
        e.push_back(Edge(u, v, c, 0, cost));
        e.push_back(Edge(v, u, 0, 0, -cost));
        int m = e.size();
        G[u].push_back(m - 2);
        G[v].push_back(m - 1);
    }
    bool bellman(int s, int t, int& flow, long long & cost)
    {
        for(int i = 0; i <= t + 1; i++) d[i] = INF,inq[i] = 0;
        d[s] = 0;inq[s] = 1;//源点s的距离设为0,标记入队
        p[s] = 0;a[s] = INF;//源点流量为INF(和之前的最大流算法是一样的)
 
        queue<int>q;//Bellman算法和增广路算法同步进行,沿着最短路拓展增广路,得出的解一定是最小费用最大流
        q.push(s);
        while(!q.empty())
        {
            int u = q.front();
            q.pop();
            inq[u] = 0;//入队列标记删除
            for(int i = 0; i < G[u].size(); i++)
            {
                Edge & now = e[G[u][i]];
                int v = now.v;
                if(now.c > now.f && d[v] > d[u] + now.cost)
                    //now.c > now.f表示这条路还未流满(和最大流一样)
                    //d[v] > d[u] + e.cost Bellman 算法中边的松弛
                {
                    d[v] = d[u] + now.cost;//Bellman 算法边的松弛
                    p[v] = G[u][i];//反向记录边的编号
                    a[v] = min(a[u], now.c - now.f);//到达v点的水量取决于边剩余的容量和u点的水量
                    if(!inq[v]){q.push(v);inq[v] = 1;}//Bellman 算法入队
                }
            }
        }
        if(d[t] == INF)return false;//找不到增广路
        flow += a[t];//最大流的值,此函数引用flow这个值,最后可以直接求出flow
        cost += (long long)d[t] * (long long)a[t];//距离乘上到达汇点的流量就是费用
        for(int u = t; u != s; u = e[p[u]].u)//逆向存边
        {
            e[p[u]].f += a[t];//正向边加上流量
            e[p[u] ^ 1].f -= a[t];//反向边减去流量 (和增广路算法一样)
        }
        return true;
    }
    int MincostMaxflow(int s, int t, long long & cost)
    {
        cost = 0;
        int flow = 0;
        while(bellman(s, t, flow, cost));//由于Bellman函数用的是引用,所以只要一直调用就可以求出flow和cost
        return flow;//返回最大流,cost引用可以直接返回最小费用
    }
}ans;
 
 
vector<int>G1[maxn];
vector<int> G2[maxn];
int dfs(int rt1,int rt2){
    int s1 = G1[rt1].size(),s2 =G2[rt2].size();
    if(s1!=s2) return -1;
    vector<int>e[3];
    e[0].clear(),e[1].clear(),e[2].clear();
    for(int i=0;i<s1;i++){
        for(int j=0;j<s2;j++){
            int x = dfs(G1[rt1][i],G2[rt2][j]);
            if(x!=-1) e[0].push_back(i),e[1].push_back(j+s1),e[2].push_back(x);
        }
    }
    ans.init(s1,s2);
    ll cost = 0;
    for(int i=0;i<e[0].size();i++) ans.addEdge(e[0][i],e[1][i],1,e[2][i]);
    int num = ans.MincostMaxflow(s1+s2,s1+s2+1,cost);
    // printf("s1=%d s2=%d rt1=%d rt2=%d\n",s1,s2,rt1,rt2);
    // printf("rt1=%d rt2=%d s1=%d s2=%d num=%d cost=%lld\n",rt1,rt2,s1,s2,num,cost);
    if(num!=s1) return -1;
    if(rt1!=rt2) cost++;
    // printf("cost=%lld\n",cost);
    return cost;
}

int main(){
    int n;
    scanf("%d",&n);
    for(int i=1,x;i<=n;i++) 
        scanf("%d",&x),G1[x].push_back(i);
    for(int i=1,x;i<=n;i++) 
        scanf("%d",&x),G2[x].push_back(i);
    ll ans = dfs(G1[0][0],G2[0][0]);
    printf("%lld\n", ans);
    return 0;
}

KM:

#include <bits/stdc++.h>
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>
#include <cmath>
#define mem(a,b) memset(a,b,sizeof(a))
#define ll long long
#define ull unsigned long long
#define PI acos(-1)
#define pb(x)   push_back(x)
#define il inline
#define re register
#define IO; ios::sync_with_stdio(0);cin.tie(0);
#define ls (o<<1)
#define rs (o<<1|1)
#define pii pair<int,int>
using namespace std;
const int maxn = 505;
const int maxm = maxn*maxn;
const int INF = 1010;
const ll LINF = 3e17+1;
const int mod = 1e9+7;
int r, m,h,s,t;
inline int read(){
    register int x=0,f=1;char ch=getchar();
    while(!isdigit(ch)){if(ch=='-')f=-1;ch=getchar();}
    while(isdigit(ch)){x=(x<<3)+(x<<1)+ch-'0';ch=getchar();}
    return (f==1)?x:-x;
}
vector<int>mp1[505], mp2[505];
int dp[505][505];
struct KKMM
{
    int w[maxn][maxn], lx[maxn], ly[maxn];
    int linker[maxn], slack[maxn], pre[maxn];
    int n, m;
    bool vis[maxn];
    void bfs(int x) {
        int y = 0, nex = 0, d;
        for (int i = 1; i <= n; ++i) pre[i] = 0, vis[i] = false;
        for (int i = 1; i <= n; ++i) slack[i] = INF;
        linker[y] = x;
        while (true) {
            x = linker[y]; d = INF; vis[y] = true;
            for (int i = 1; i <= n; ++i) {
                if (!vis[i]) {
                    if (slack[i] > lx[x] + ly[i] - w[x][i]) {
                        slack[i] = lx[x] + ly[i] - w[x][i];
                        pre[i] = y;
                    }
                    if (slack[i] < d) {
                        d = slack[i]; nex = i;
                    }
                }
            }
            for (int i = 0; i <= n; ++i) {
                if (vis[i]) {
                    lx[linker[i]] -= d; ly[i] += d;
                }
                else slack[i] -= d;
            }
            y = nex; if (linker[y] == -1) break;
        }
        while (y) {
            linker[y] = linker[pre[y]]; y = pre[y];
        }
    }
    int KM()
    {
        int res = 0;
        for (int i = 1; i <= n; i++)    lx[i] = ly[i] = 0, linker[i] = -1;
        for (int i = 1; i <= n; i++)    bfs(i);
        for (int i = 1; i <= n; i++)    res += lx[i], res += ly[i];
        return res>=INF?INF:res;
    }
}km;
void dfs(int rt1, int rt2)
{
    if (mp1[rt1].size() != mp2[rt2].size())
        return;
    int n = mp1[rt1].size();
    if (n == 0)
    {
        dp[rt1][rt2] = (rt1 != rt2) ? 1 : 0;
        return ;
    }
    for (auto x : mp1[rt1])
        for (auto y : mp2[rt2])
            dfs(x,y);
    km.n = n;
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
        {
            km.w[i+1][j+1] = -dp[mp1[rt1][i]][mp2[rt2][j]];
        }
    }
    int ans = -km.KM();
    if (rt1 != rt2) ans++;
    dp[rt1][rt2] = ans;
    return ;
}
int main()
{
    int n = read();
    int rt1, rt2, a;
    for (int i = 1; i <= n; i++)
    {
        a = read();
        if (a == 0) rt1 = i;
        else mp1[a].push_back(i);
    }
    for (int i = 1; i <= n; i++)
    {
        a = read();
        if (a == 0) rt2 = i;
        else mp2[a].push_back(i);
    }
    for (int i = 0; i < 505; i++)
        for (int j = 0; j < 505; j++)
            dp[i][j] = INF;
    dfs(rt1,rt2);
    printf("%d\n", dp[rt1][rt2]);
 
 
    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值