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;
}