CF-1515-F-思维

1515-F 题目大意

给定一个\(n\)个点和\(m\)条边的连通图和一个整数\(x\),点有点权\(a_i\),权值非负。如果一条边\((u,v)\)满足\(a_u+a_v\le x\)则可以把\(u,v\)缩成一个点,新点的点权为\(a_u+a_v-x\),判断这个图是否能够缩成一个点,如果可以,请依次输出每条边。


Solution

首先,如果所有点权和小于\((n-1)*x\),那么一定无解。

否则,一定存在一种合法的方案,证明如下:

先随便选择一个叶子\(u\), 其点权为\(a_u\),分情况考虑:

1.\(a_u \ge x\),则此时修建点\(u\)与其父亲\(p\)之间的边,其父亲的点权更新为\(a_u+a_p-x\),然后把它们缩成一点,剩下的树是一颗\(n-1\)个节点的树,且满足条件。

2.\(a_u < x\),则把它和它父亲\(p\)之间的边放到最后修建,然后考虑剩余的\((n-1)\)个点来修建,仍然是一颗满足条件的树。

基于这个结论,做法如下:对整个树\(dfs\),枚举到点\(u\),其父亲为\(p\),以\(u\)为根的子树已经访问完毕,

  • 如果\(a_u \ge x\),则直接修建这条边,记录到答案中。
  • 否则把\(u\)集合,用栈维护,放到最后修建。

最后把栈中的边依次记录到答案中输出即可,时间复杂度\(O(n)\)

#include<bits/stdc++.h>
using namespace std;
using ll=long long;

void solve(){
    int n,m,x;
    cin>>n>>m>>x;
    vector<ll> a(n+1);
    for(int i=1;i<=n;i++){
        cin>>a[i];
    }
    vector<vector<pair<int,int>>> e(n+1);
    vector<int> vis(n+1);
    for(int i=0;i<m;i++){
        int x,y;
        cin>>x>>y;
        e[x].push_back({y,i+1});
        e[y].push_back({x,i+1});
    }
    if(accumulate(a.begin(),a.end(),0LL)<1LL*(n-1)*x){
        cout<<"NO";
        return;
    }
    vector<int> ans,st;
    function<void(int,int,int)> dfs=[&](int u,int fa,int i){
        vis[u]=1;
        for(auto [v,j]:e[u]){
            if(vis[v]) continue;
            dfs(v,u,j);
        }
        if(u!=1){
            if(a[u]>=x){
                ans.push_back(i);
                a[fa]+=a[u]-x;
            }else{
                st.push_back(i);
            }
        }
    };
    dfs(1,0,0);
    for(int i=st.size()-1;~i;i--){
        ans.push_back(st[i]);
    }
    cout<<"YES"<<'\n';
    for(int i=0;i<n-1;i++){
        cout<<ans[i]<<'\n';
    }
}

int main(){
    ios_base::sync_with_stdio(0);cin.tie(0);cout.tie(0);
    //freopen("input.txt","r",stdin);
    //freopen("output.txt","w",stdout);
    int T=1;
    //cin>>T;
    while(T--){
        solve();
    }
    return 0;
}
posted @ 2024-02-24 20:57  fengxue-K  阅读(20)  评论(0)    收藏  举报