题意:
给n个点m条边的无向图,和三个整数n1,n2,n3,n1+n2+n3=n
现在要你给每个点赋予点权1或2或3,满足下面条件:
- 1的个数恰好为n1,2的个数恰好为n2,3的个数恰好为n3
- 相邻点的差值必须为1
如果无解输出NO,否则输出YES和赋值方案
数据范围:n<=5e3,m<=1e5
解法:
相邻点的差值必须为1,那么1只能和2匹配,3只能和2匹配,2只能和1,3匹配
将2和1,3分成两部分,那么每部分内部不存在边,因此是一个二分图
对原图进行二分图染色,将原图中的多个连通块分成若干二元组(a,b)
二元组每部分对应2或者1,3,需要自己划分
问题就变为是否存在一种划分,满足一部分的和为n2,另一部分的和为n3
可以用dp解决,但是要输出方案,看了高rank选手的代码发现记录方法挺巧妙的
code:
#include<bits/stdc++.h>
using namespace std;
const int N=5e3+5;
vector<int>g[N];
int col[N];
int n1,n2,n3;
int n,m;
//
vector<int>temp[N][2];
int err;
int cnt;
int d[N][N];
int ans[N];
void dfs(int x,int c){
if(err)return ;
col[x]=c;
temp[cnt][c==1].push_back(x);
for(int v:g[x]){
if(!col[v]){
dfs(v,-c);
}else if(col[v]==col[x]){
err=1;
}
}
}
signed main(){
scanf("%d%d",&n,&m);
scanf("%d%d%d",&n1,&n2,&n3);
for(int i=1;i<=m;i++){
int a,b;scanf("%d%d",&a,&b);
g[a].push_back(b);
g[b].push_back(a);
}
for(int i=1;i<=n;i++){
if(!col[i]){
cnt++;
dfs(i,1);
if(err){
puts("NO");return 0;
}
}
}
memset(d,-1,sizeof d);
d[0][0]=1;
for(int i=1;i<=cnt;i++){
int x=temp[i][0].size();
int y=temp[i][1].size();
for(int j=0;j<=n;j++){
if(d[i-1][j]!=-1){
d[i][j+x]=j;//直接记录前驱
d[i][j+y]=j;
}
}
}
if(d[cnt][n2]==-1){
puts("NO");return 0;
}
puts("YES");
int now=n2;
for(int i=cnt;i>=1;i--){
int cc=now-d[i][now];//数量
now=d[i][now];
int cur=0;
if((int)temp[i][1].size()==cc)cur=1;
for(int v:temp[i][cur]){
ans[v]=2;
}
for(int v:temp[i][cur^1]){
if(n1)ans[v]=1,n1--;
else ans[v]=3;
}
}
for(int i=1;i<=n;i++){
printf("%d",ans[i]);
}
return 0;
}