「CF830E」Perpetual Motion Machine 题解
本文网址:https://www.cnblogs.com/zsc985246/p/17523153.html ,转载请注明出处。
传送门
「CF830E」Perpetual Motion Machine
题目大意
给定一个 个点 条边的图,点 有点权 。
每个点 会产生 的贡献,而每条边 会产生 的贡献。
求一种贡献为非负数的点权分配方案,使得所有点权落在 ,且有一个点的点权不为 ,或者报告无解。
思路
首先可以发现,如果有一个连通块满足条件,那么其它连通块点权全部分配 即可满足条件。所以我们可以假定图是连通的。而且 的情况十分简单,所以下方仅讨论 。
发现如果我们把所有点权全部赋值为 ,可以解决 的情况。
那么现在我们只需要考虑一棵树的情况。
直接构造比较复杂,考虑一些特殊树。
先尝试做链。
可以先列出需要满足的条件:
推导一下:
显然左边是非负的,取等就必须让 全部为 ,与题目矛盾。
所以链是不合法的。
然后考虑菊花图。
显然中间节点权值大,叶子节点权值小更优。
我们干脆直接让叶子节点的权值都为 。
设中间节点权值为 ,那么条件为 。
那么只要 ,那么一定存在满足条件的 。
解得 ,也就是至少 个叶子节点。
构造时发现直接令 即可。
这给了我们启发。我们尝试将菊花图的做法拓展到图上,也就是让所有叶子节点点权为 ,非叶子节点点权为 。
不难发现这样的构造方法可以解决叶子节点数大于等于 的一般树。
那么叶子节点数为 ()、为 (链)以及大于等于 的树都解决了。考虑叶子节点数为 的树。
其实就是一个根上接了三条链。
我们定义 表示长度为 的链, 的贡献。
然后我们定义 ,对其进行化简:
我们可以随意分配 ,也就可以随意分配 。那么最优情况下, 一定都相等,因为 ,所以 。
然后带入整个树中,得到
那么只需要满足 ,即 。
发现只有 时可以取等。
我们只需要根据这三个临界条件判断是否合法。然后思考构造方案。
难想到,可以如下构造:
-
根节点权值为 。
-
链 上的点权值为 ,其中 为点到根节点的距离。其它链同理。
然后就做完了。复杂度 。
代码实现
#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;
}
尾声
如果你发现了问题,你可以直接回复这篇题解
如果你有更好的想法,也可以直接回复!
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现