「CF830E」Perpetual Motion Machine 题解

本文网址:https://www.cnblogs.com/zsc985246/p/17523153.html ,转载请注明出处。

传送门

「CF830E」Perpetual Motion Machine

题目大意

给定一个 n 个点 m 条边的图,点 i 有点权 ai

每个点 i 会产生 ai2 的贡献,而每条边 (u,v) 会产生 auav 的贡献。

求一种贡献为非负数的点权分配方案,使得所有点权落在 [0,106],且有一个点的点权不为 0,或者报告无解。

思路

首先可以发现,如果有一个连通块满足条件,那么其它连通块点权全部分配 0 即可满足条件。所以我们可以假定图是连通的。而且 n=1 的情况十分简单,所以下方仅讨论 n2

发现如果我们把所有点权全部赋值为 1,可以解决 n<=m 的情况。

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

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

先尝试做链。

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

i=1n1aiai+1i=1nai20

推导一下:

a12+an2+i=1n1(aiai+1)20

显然左边是非负的,取等就必须让 a1,2,,n 全部为 0,与题目矛盾。

所以链是不合法的

然后考虑菊花图。

显然中间节点权值大,叶子节点权值小更优。

我们干脆直接让叶子节点的权值都为 1

设中间节点权值为 x,那么条件为 x2(n1)x+n10

那么只要 Δ=(n1)24(n1)0,那么一定存在满足条件的 x

解得 n5,也就是至少 4 个叶子节点。

构造时发现直接x=2 即可

这给了我们启发。我们尝试将菊花图的做法拓展到图上,也就是让所有叶子节点点权为 1,非叶子节点点权为 2

不难发现这样的构造方法可以解决叶子节点数大于等于 4 的一般树

那么叶子节点数为 1n=1)、为 2(链)以及大于等于 4 的树都解决了。考虑叶子节点数为 3 的树。

其实就是一个根上接了三条链。

我们定义 f(x,n) 表示长度为 n 的链,a1=x 的贡献。

f(x,n)=12(a12+an2+i=1n1(aiai+1)2)

然后我们定义 an+1=0,bi=ai+1ai,对其进行化简:

f(x,n)=x2212i=1nbi2

我们可以随意分配 ai,也就可以随意分配 bi。那么最优情况下,bi 一定都相等,因为 bi=x,所以 bi=xn

f(x,n)=x22x22n

然后带入整个树中,得到

ans=f(x,n1)+f(x,n2)+f(x,n3)+2x2

=x22i=n1,n2,n3x22i0

那么只需要满足 11n11n21n30,即 1n1+1n2+1n31

发现只有 (n1,n2,n3)=(3,3,3),(2,4,4),(2,3,6) 时可以取等

我们只需要根据这三个临界条件判断是否合法。然后思考构造方案。

难想到,可以如下构造:

  • 根节点权值为 n1n2n3

  • 1 上的点权值为 (n1dis)n2n3,其中 dis 为点到根节点的距离。其它链同理。

然后就做完了。复杂度 O(nα(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 @   zsc985246  阅读(271)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示