一、题目
二、解法
( t vizing) 定理板题,可以去看看 oiwiki
当然我不会证这个定理,我只会给出二分图背景下这个定理的构造性证明。
结论:二分图的边染色最小颜色数是点的最大度数
考虑增量法构造,现在考虑边 ((x,y)) 的染色,设点 (x) 未使用的最小颜色是 (lx),设点 (y) 未使用的最小颜色是 (ly),如果 (lx=ly) 那么直接把这条边染色成 (lx);否则从 (y) 开始找到一条 (lx,ly,lx...) 的"增广路",把这一条增广路上所有边的"反转"即可。
这种做法的正确性来源于增广路径不自交,因为除 (x) 以外的点都把 (lx,ly) 的边拿去增广了,以后的增广不会有这种颜色的边连进来,又因为 (lx) 是 (x) 未使用过的最小颜色,所以路径不会回到 (x),这也说明了增广路径是有限的。
因为每次取的是最小的未使用颜色,所以所有边的颜色都不超过最大度数。
三、应用
给你一张左部 (n) 个点、右部 (m) 个点的二分图,给每条边染 ([1,c]) 中的某一种颜色,定义一个点的权值为出现次数的最大值减去出现次数的最小值,试构造方案使得最后总权值最小。
答案下界是 (sum[d_imod c ot=0]),可以应用上述方法证明它,对于所有度数 (geq c) 的节点,我们对它做拆点,也就是任取 (c) 条边连在这个节点上,然后把这些边在原节点上删去,这样最后剩下的二分图最大度数一定 (leq c),可以对它做边染色。
拆出来的点一定拥有 ([1,c]) 中的所有颜色,原来的点会产生 (1) 的贡献,易知达到了答案下界,构造方案只需要记录每个点属于原先的哪个节点,然后把边染色对应上去即可。
四、总结
二分图中增广的思想特别重要,找有特殊性质(如最小值最大值)的元素构造的思想也值得借鉴。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 100005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,k,t,a[M],b[M],d[M],f[2005][1005];
void dfs(int u,int v,int x,int y)
{
int to=f[v][x];
f[u][x]=v;
f[v][x]=u;
if(!to)
{
f[v][y]=0;
return ;
}
dfs(v,to,y,x);
}
signed main()
{
n=read();m=read();k=read();
for(int i=1;i<=k;i++)
{
a[i]=read();b[i]=n+read();
d[a[i]]++;d[b[i]]++;
}n+=m;
for(int i=1;i<=n;i++) t=max(t,d[i]);
for(int i=1;i<=k;i++)
{
int lx=1,ly=1;
while(f[a[i]][lx]) lx++;
while(f[b[i]][ly]) ly++;
if(lx==ly)
{
f[a[i]][lx]=b[i];
f[b[i]][lx]=a[i];
continue;
}
dfs(a[i],b[i],lx,ly);
}
printf("%d
",t);
for(int i=1;i<=k;i++)
for(int j=1;j<=t;j++)
if(f[a[i]][j]==b[i])
{
printf("%d ",j);
break;
}
}