「CF830E」Perpetual Motion Machine 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17523153.html ,转载请注明出处。
传送门
「CF830E」Perpetual Motion Machine
题目大意
给定一个 \(n\) 个点 \(m\) 条边的图,点 \(i\) 有点权 \(a_i\)。
每个点 \(i\) 会产生 \(-a_i^2\) 的贡献,而每条边 \((u,v)\) 会产生 \(a_u a_v\) 的贡献。
求一种贡献为非负数的点权分配方案,使得所有点权落在 \([0,10^6]\),且有一个点的点权不为 \(0\),或者报告无解。
思路
首先可以发现,如果有一个连通块满足条件,那么其它连通块点权全部分配 \(0\) 即可满足条件。所以我们可以假定图是连通的。而且 \(n = 1\) 的情况十分简单,所以下方仅讨论 \(n \ge 2\)。
发现如果我们把所有点权全部赋值为 \(1\),可以解决 \(n<=m\) 的情况。
那么现在我们只需要考虑一棵树的情况。
直接构造比较复杂,考虑一些特殊树。
先尝试做链。
可以先列出需要满足的条件:
推导一下:
显然左边是非负的,取等就必须让 \(a_{1,2,\dots,n}\) 全部为 \(0\),与题目矛盾。
所以链是不合法的。
然后考虑菊花图。
显然中间节点权值大,叶子节点权值小更优。
我们干脆直接让叶子节点的权值都为 \(1\)。
设中间节点权值为 \(x\),那么条件为 \(x^2-(n-1)x+n-1 \le 0\)。
那么只要 \(\Delta=(n-1)^2-4(n-1) \ge 0\),那么一定存在满足条件的 \(x\)。
解得 \(n \ge 5\),也就是至少 \(4\) 个叶子节点。
构造时发现直接令 \(x=2\) 即可。
这给了我们启发。我们尝试将菊花图的做法拓展到图上,也就是让所有叶子节点点权为 \(1\),非叶子节点点权为 \(2\)。
不难发现这样的构造方法可以解决叶子节点数大于等于 \(4\) 的一般树。
那么叶子节点数为 \(1\)(\(n=1\))、为 \(2\)(链)以及大于等于 \(4\) 的树都解决了。考虑叶子节点数为 \(3\) 的树。
其实就是一个根上接了三条链。
我们定义 \(f(x,n)\) 表示长度为 \(n\) 的链,\(a_1=x\) 的贡献。
然后我们定义 \(a_{n+1}=0,b_i=a_{i+1}-a_i\),对其进行化简:
我们可以随意分配 \(a_i\),也就可以随意分配 \(b_i\)。那么最优情况下,\(b_i\) 一定都相等,因为 \(\sum b_i=x\),所以 \(b_i=\frac{x}{n}\)。
然后带入整个树中,得到
那么只需要满足 \(1 - \frac{1}{n_1} - \frac{1}{n_2} - \frac{1}{n_3} \ge 0\),即 \(\frac{1}{n_1} + \frac{1}{n_2} + \frac{1}{n_3} \le 1\)。
发现只有 \((n_1,n_2,n_3)=(3,3,3),(2,4,4),(2,3,6)\) 时可以取等。
我们只需要根据这三个临界条件判断是否合法。然后思考构造方案。
难想到,可以如下构造:
-
根节点权值为 \(n_1 n_2 n_3\)。
-
链 \(1\) 上的点权值为 \((n_1-dis) n_2 n_3\),其中 \(dis\) 为点到根节点的距离。其它链同理。
然后就做完了。复杂度 \(O(n\alpha(n))\)。
代码实现
#include<bits/stdc++.h>
#define ll long long
#define For(i,a,b) for(ll i=(a);i<=(b);++i)
#define Rep(i,a,b) for(ll i=(a);i>=(b);--i)
#define pb push_back
const ll N=1e6+10;
using namespace std;
const ll p=1e9+7;
ll ksm(ll a,ll b){ll bns=1;while(b){if(b&1)bns=bns*a%p;a=a*a%p;b>>=1;}return bns;}
ll n,m;
vector<ll>e[N];
ll in[N];//度数
ll tot[N];//连通块内边数
ll cnt[N];//连通块内叶子节点个数
ll siz[N];//连通块内节点个数
ll ans[N];//答案
//并查集
ll fa[N];
ll find(ll x){return fa[x]==x?x:fa[x]=find(fa[x]);}
void dfs(ll x,ll fa,ll&t){//统计子树大小,记录在t中
++t;
for(ll y:e[x]){
if(y==fa)continue;
dfs(y,x,t);
}
}
void solve(ll x,ll fa,ll c[],ll id,ll dis){//分配点权
//c表示构造长度,id表示属于的链的编号,dis表示到根节点的距离
ll s=1;
For(i,0,2)if(i!=id)s*=c[i];
ans[x]=s*max(0ll,c[id]-dis);//取max保证超出构造长度部分的点权全部为0
for(ll y:e[x]){
if(y==fa)continue;
solve(y,x,c,id,dis+1);
}
}
void mian(){
scanf("%lld%lld",&n,&m);
For(i,1,n)fa[i]=i,in[i]=tot[i]=cnt[i]=siz[i]=ans[i]=0,e[i].clear();//初始化
For(i,1,m){
ll x,y;
scanf("%lld%lld",&x,&y);
e[x].pb(y),e[y].pb(x);
ll fx=find(x),fy=find(y);
if(fx!=fy){
tot[fx]+=tot[fy];
fa[fy]=fx;
}
++tot[fx];
++in[x],++in[y];
}
For(i,1,n){
ll fi=find(i);
++siz[fi];
if(in[i]==1)++cnt[fi];
}
ll flag=0;//是否合法
For(i,1,n){
ll fi=find(i);
if(tot[fi]>=siz[fi]){//不是树
flag=1;
ans[i]=1;
}else if(cnt[fi]>=4){//不少于4个叶子节点
flag=1;
ans[i]=(in[i]>1)+1;
}else if(cnt[fi]==3&&in[i]==3){//3个叶子节点
ll c[3]={1,1,1},k=0;//c记录子树大小,k用来为每条链分配编号
ll id[3]={0,1,2};//排序后的下标,不直接排序c数组是为了方便对应原来的子树
for(ll j:e[i])dfs(j,i,c[k++]);
sort(id,id+3,[&](ll x,ll y){return c[x]<c[y];});//按c数组从小到大排序
//判断是否合法,此时c的作用变为记录构造长度
ll f=0;
if(c[id[0]]>=3){//(3,3,3)
f=1;c[id[0]]=3,c[id[1]]=3,c[id[2]]=3;
}else if(c[id[0]]>=2&&c[id[1]]>=3&&c[id[2]]>=6){//(2,3,6)
f=1;c[id[0]]=2,c[id[1]]=3,c[id[2]]=6;
}else if(c[id[0]]>=2&&c[id[1]]>=4&&c[id[2]]>=4){//(2,4,4)
f=1;c[id[0]]=2,c[id[1]]=4,c[id[2]]=4;
}
//分配权值
if(f){
flag=1;
k=0;
ans[i]=c[0]*c[1]*c[2];
for(ll j:e[i])solve(j,i,c,k++,1);
}
}
}
if(!flag){
printf("NO\n");
return;
}
printf("YES\n");
For(i,1,n)printf("%lld ",ans[i]);
printf("\n");
}
int main(){
int T=1;
scanf("%d",&T);
while(T--)mian();
return 0;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!