C题:Fuzzy Graph
题目大意
solution
对于基础目标,可以使用dfs遍历生成一颗树,将原图删去一些回边。然后将树上的节点蓝绿染色(染色是对称的,颜色可以随意选择),同时保证相邻节点颜色不同。
然后处理额外目标
- 对额外目标1,当前蓝绿节点数都 ≥ n 3 \geq \frac{n}{3} ≥3n,则将一部分的蓝绿节点染成红色即可,同时保证无相邻颜色相同的节点。
- 对额外目标2,若当前存在蓝色节点或绿色节点数量 < n 3 < \frac{n}{3} <3n即存在蓝色节点或绿色节点数量 > 2 n 3 > \frac{2n}{3} >32n ,简单推理可知,叶节点的数量 > n 3 >\frac{n}{3} >3n,则将所有的叶节点染成红色即可。推理如下:
不妨假设蓝色节点(B)数量
<
n
3
<\frac{n}{3}
<3n,绿色节点(G)数量
>
2
n
3
>\frac{2n}{3}
>32n
B
叶
+
B
非
叶
<
n
3
G
叶
+
G
非
叶
>
2
n
3
B_叶+B_{非叶}<\frac{n}{3} \\G_叶+G_{非叶}>\frac{2n}{3}
B叶+B非叶<3nG叶+G非叶>32n
因为所有非叶节点都有至少一条边连向不同颜色的节点
G
非
叶
≤
B
叶
+
B
非
叶
<
n
3
G_{非叶}\leq B_叶+B_{非叶}<\frac{n}{3}
G非叶≤B叶+B非叶<3n
∴
G
叶
>
n
3
\therefore G_叶>\frac{n}{3}
∴G叶>3n
则叶节点的总数一定大于
n
3
\frac{n}{3}
3n
几个细节
- 处理额外目标1时要从叶节点开始染色,使后效性最小。
- 额外目标1染色时相邻节点不能是同色,可以开个数组标记。
code
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+7;
int T,n,m;
char color[]={'B','G','R'};
int cl[N],leaf[N],sumB,sumG,sumR,vis[N],vis2[N];
vector <int> ve[N];
void dfs(int x,int clr)//生成树,并将初始颜色染上
{
cl[x]=clr;
if(cl[x]==0) sumB++;
else sumG++;
leaf[x]=1;
vis[x]=1;
for(int i=0;i<ve[x].size();i++)
{
if(vis[ve[x][i]]) continue;
dfs(ve[x][i],clr^1);
leaf[x]=0;
}
}
void dfs2(int x,int fa,int g)
{
vis[x]=1;
bool flag=0;
for(int i=0;i<ve[x].size();i++)//先遍历再修改,达到先染叶节点的效果
{
int son=ve[x][i];
if(son==fa) continue;
if(vis[son]) continue;
dfs2(son,x,g);
if(vis2[son]) flag=1;
}
if(!flag){//相邻点不能同时染成红色
if(cl[x]==0&&sumB>g){
sumB--;
vis2[x]=1;
cl[x]=2;
}
else if(cl[x]==1&&sumG>g){
sumG--;
vis2[x]=1;
cl[x]=2;
}
}
}
int main()
{
int u,v;
scanf("%d",&T);
while(T--)
{
sumB=sumG=sumR=0;
scanf("%d%d",&n,&m);
for(int i=0;i<=n;i++)
{
leaf[i]=vis2[i]=vis[i]=0;
cl[i]=-1;
ve[i].clear();
}
for(int i=1;i<=m;i++)
{
scanf("%d%d",&u,&v);
ve[u].push_back(v);
ve[v].push_back(u);
}
dfs(1,0);
int g=n/3;
if(sumB>2*g||sumG>2*g)//额外目标2,直接将叶节点染成红色
{
for(int i=1;i<=n;i++)
if(leaf[i])
cl[i]=2;
}
else{//额外目标1
memset(vis,0,sizeof vis);
dfs2(1,0,g);
}
for(int i=1;i<=n;i++)
printf("%c",color[cl[i]]);
printf("\n");
}
return 0;
}
小结
题目代码比较好写(虽然我debug了半天),想到构建Tarjan树,并通过贪心染色是解决题目的关键。