题意:
Soda有一个n个点m条边的二分图, 他想要通过加边使得这张图变成一个边数最多的完全二分图. 于是他想要知道他最多能够新加多少条边. 注意重边是不允许的.
思路:
先将二分图着色,将每个连通分量区分出左右两边的点,在着色过程中,顺便将每个连通分量两边的点数存起来,注意一个连通分量左右两边的点数是绑定的一对数字。现在的问题就是,需要将这几对数字给分配掉,每一对都必须拆开来丢到不同的桶中,而且尽量使得两桶中各自的数字之和最接近,才能使得边最多。
分配的过程可以使用DP解决,主要是数字挺大的,bitset优化的背包很强大,可以解决这个问题。
bitset优化了的代码:
#include <bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
#define INF 0x7f7f7f7f
using namespace std;
const int N=;
vector<int> vect[N];
int col[N], set1, set2; void color(int u, int c)
{
col[u]=c;
col[u]==?set2++:set1++;//统计两边的数量
for(int i=; i<vect[u].size(); i++)
{
int t=vect[u][i];
if(!col[t]) color(t, -col[u] );
}
} bitset<N> dp;
int s1[N/], s2[N/];
int cal(int n, int m)
{
if(m==n/*(n-n/) ) return ;
dp.reset(); memset(col, , sizeof(col));
int ans=, k=;
for(int i=; i<=n; i++)
{
if(!col[i])
{
set1=set2=;
color(i, ); s1[k]=set1;//每个连通分量两边的点数
s2[k++]=set2;
}
} dp[]=;
for(int i=; i<k; i++) //类似于背包+bitset优化
dp = dp<<s1[i] | dp<<s2[i]; //这么神奇!! for(int i=n/; i>=; i--) //再将那个最接近n/2的找出来即可
if( dp[i] ) return i*(n-i)-m;
} int main()
{
freopen("input.txt", "r", stdin);
int n, m, t, p, q, a, b;
cin>>t; while(t--)
{
scanf("%d%d",&n,&m);
for(int i=; i<=n; i++) vect[i].clear();
for(int i=; i<m; i++)
{
scanf("%d%d",&a,&b);
vect[a].push_back(b);
vect[b].push_back(a);
}
printf("%d\n",cal(n, m));
}
return ;
}
AC代码
贴一个没有优化的TLE代码:
#include <bits/stdc++.h>
#define LL long long
#define pii pair<int,int>
#define INF 0x7f7f7f7f
using namespace std;
const int N=;
vector<int> vect[N];
int col[N];
int set1, set2; void color(int u, int c)
{
col[u]=c;
if(col[u]==) set1++;//统计两边的数量
else set2++; for(int i=; i<vect[u].size(); i++)
{
int t=vect[u][i];
if(!col[t]) //没色
color(t, -col[u] );
}
} bitset<N/> dp[N];
int s1[N/],s2[N/];
int cal(int n, int m)
{
if(m==n/*(n-n/) ) return ;
for(int i=; i<=n; i++) vect[i].clear(), dp[i].reset(); memset(col, , sizeof(col));
int ans=, k=;
for(int i=; i<=n; i++)
{
if(!col[i])
{
set1=set2=;
color(i, ); s1[k]=set1;//每个连通分量两边的点数
s2[k++]=set2;
}
} if(k==) return s1[] * s2[] - m;//连通图
dp[][]=;
for(int i=; i<k; i++)//在这进行DP,尽量使得两边的点数平衡
{
ans=;
set1=s1[i];
set2=s2[i]; for(int j=n/; j>=set1; j--)
if( dp[i][j-set1] ) dp[i+][j]=,ans=max(ans,j); for(int j=n/; j>=set2; j--)
if( dp[i][j-set2] ) dp[i+][j]=,ans=max(ans,j);
}
return ans*(n-ans)-m;
} int main()
{
//freopen("input.txt", "r", stdin);
int n, m, t, p, q, a, b;
cin>>t; while(t--)
{
scanf("%d%d",&n,&m);
for(int i=; i<m; i++)
{
scanf("%d%d",&a,&b);
vect[a].push_back(b);
vect[b].push_back(a);
}
printf("%d\n",cal(n, m));
}
return ;
}
TLE代码