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在提交界面的警示号提示的。

posted @ 2021-05-09 00:31  _rhinoceros  阅读(86)  评论(0编辑  收藏  举报