Codeforces Global Round 14 F. Phoenix and Earthquake
一、题目大意
给n个点,m条边的图,所有边被摧毁,然后要建一棵树把点都连起来。第i个点有ai的材料,要连一条边需要x的材料,要修i点到j点的边,必须i点和j点总共有x的材料。在修好的边上可以运输材料。
问能否建出这棵树,要连哪些边,连边顺序是什么。
1.输出YES的条件是什么?
猜想,如果所有点权和不到$(n-1)*x$,则一定可以在任意一个生成树上完成建图,只要考虑顺序就行了。(心里想的是材料反正可以随便运)
证明如下:
①对于一棵生成树,其某个叶子结点u,若其点权大于等于x,则由u来承担其到fa[u]的边权,并把多于x的部分给到fa[u],因为如果不给的话,就算放在u里面也没用。
②若u的点权小于x,则将u去掉,对于剩下的n-1个点的生成树,仍然满足①的性质(因为u的点权小于x,所以去掉u的点权之后,剩下的n-1个点的点权仍大于等于$(n-2)*x$)。等到n-1个点的生成树建立完成后,再连到u的边。可假设一个极端情况,设点u的点权为$y(y<x)$,且整个n个点的生成树的点权和为$(n-1)x$,则剩下n-1个点建完之后剩下的点权和为:
$(n-1)x-y-(n-2)x$即$x-y$,与u点的y合并,恰好能建一条u到fa[u]的边。
证明完毕后,做法就显然了:对于先建的边直接输出,对于后建的边存到栈里面,最后输出。
二、代码
#include<iostream> #include<cstring> #include<algorithm> #include<cstdio> #include<vector> #include<stack> #define x first #define y second #define LL long long using namespace std; const int N=300050; const int M=N<<1; typedef pair<int,int> PII; int n,m,x; LL w[N]; //点权 int h[N],e[M],ne[M],idx; int dict[M]; void add(int a,int b,int id){ dict[idx]=id; //转换成原题目的边代号 e[idx]=b,ne[idx]=h[a],h[a]=idx++; } int st[N]; int res[N]; int l,r; void dfs(int u){ st[u]=1; for(int i=h[u];~i;i=ne[i]){ int j=e[i]; if(st[j]) continue; dfs(j); //先处理完子树 if(w[j]>=x){ //用子结点承担边权 w[j]-=x; w[u]+=w[j]; //多余的移过来 //printf("%d\n",dict[i]); res[l++]=dict[i]; } else{ res[r--]=dict[i]; } } } int main(){ scanf("%d%d%d",&n,&m,&x); l=1; r=n-1; LL sum=0; for(int i=1;i<=n;i++){ scanf("%d",&w[i]); sum+=w[i]; } if(sum<(LL)(n-1)*x){ printf("NO\n"); return 0; } //else printf("YES\n"); memset(h,-1,sizeof(h)); for(int i=1;i<=m;i++){ int a,b; scanf("%d%d",&a,&b); add(a,b,i); add(b,a,i); } dfs(1); int failed=0; for(int i=1;i<=n-1;i++) if(res[i]==0) failed=1; if(failed) printf("NO\n"); else{ printf("YES\n"); for(int i=1;i<=n-1;i++) printf("%d\n",res[i]); } return 0; }
三、写在后面
这道题代码调试了好几个小时,从MLE到WA再到AC,MLE是因为数组开太多了,其实完全没有必要用并查集去专门去建一棵树,因为是无向图,所以直接从某个点开始dfs就行了(注意不要回溯)。还有WA是因为一处地方忘开long long了,另一处地方数组开小了。所以以后记得有莫名其妙错误的时候检查long long,这两个bug还是codeforces在提交界面的警示号提示的。