「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\) 的情况。

那么现在我们只需要考虑一棵树的情况。

直接构造比较复杂,考虑一些特殊树

先尝试做链。

可以先列出需要满足的条件:

\[\sum_{i=1}^{n-1}a_i a_{i+1}-\sum_{i=1}^{n}a_i^2 \ge 0 \]

推导一下:

\[a_1^2+a_n^2+\sum_{i=1}^{n-1}(a_i-a_{i+1})^2 \le 0 \]

显然左边是非负的,取等就必须让 \(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\) 的贡献。

\[f(x,n)=-\frac{1}{2}(a_1^2+a_n^2+\sum_{i=1}^{n-1}(a_i-a_{i+1})^2) \]

然后我们定义 \(a_{n+1}=0,b_i=a_{i+1}-a_i\),对其进行化简:

\[f(x,n)=-\frac{x^2}{2}-\frac{1}{2}\sum_{i=1}^{n}b_i^2 \]

我们可以随意分配 \(a_i\),也就可以随意分配 \(b_i\)。那么最优情况下,\(b_i\) 一定都相等,因为 \(\sum b_i=x\),所以 \(b_i=\frac{x}{n}\)

\[f(x,n)=-\frac{x^2}{2}-\frac{x^2}{2n} \]

然后带入整个树中,得到

\[ans=f(x,n_1)+f(x,n_2)+f(x,n_3)+2x^2 \]

\[=\frac{x^2}{2}-\sum_{i=n_1,n_2,n_3}\frac{x^2}{2i} \ge 0 \]

那么只需要满足 \(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;
}

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @ 2023-07-03 18:14  zsc985246  阅读(268)  评论(0编辑  收藏  举报