题目链接:
题目大意:
给出一张二分图,问这张二分图还能最多加多少条边?
题目分析:
这道题的思路是这样的:
1.首先对于一张二分图,我们有两个点集,两点之间存在边的一定在不同点的集合中,那么我们我们可以利用染色将每个联通快中点分成两部分。
2.若两个点集的点数分别是n,m,那么边的总数是n*m。n+m==点的总数t,n*m=n*(t-n) = t*n - n^2,所以当n和m越接近时,边数愈多。
3.所以我们利用动态规划求取,相当于每组两个物品,必须选一个,如何选取才能使结果最接近n/2;
4.如果直接dp的话,那么我们需要n^2的复杂度。一定会超时,真是忧伤..........但是有一个数据结构能够帮我们解决这个问题
首先看dp[i][j]代表选过前i组物品后能否到达j个数量,因为只存0/1,所以我们可以用bitset来存。。
然后dp[i][j] = dp[i-1][j-p[i].first] | dp[i-1][j-p[i].second]。
所以我们可以直接位运算得到,也就是偏移p[i].first和p[i].second的长度,得到就是当前的状态,那么就是O(n*位移的复杂度),得到了优化。
最后必须怀有一颗感恩的心!!!STL大法好
代码如下:
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <bitset>
#define MAX 10007
using namespace std;
int t,n,m,u,v;
int color[MAX];
int cnt[3];
//int dp[2*MAX];
bitset<MAX> dp;
typedef pair<int,int> PII;
vector<int> e[MAX];
vector<PII> p;
void add ( int u , int v )
{
e[u].push_back ( v );
e[v].push_back ( u );
}
void dfs ( int u , int c = 1)
{
color[u]=c;
cnt[color[u]]++;
for ( int i = 0 ; i < e[u].size() ; i++ )
{
int v = e[u][i];
if ( color[v] ) continue;
dfs ( v , 3 - color[u] );
}
}
void init ( )
{
memset ( color , 0 , sizeof ( color ));
for ( int i = 0 ; i < MAX ; i++ )
e[i].clear();
dp.reset();
p.clear();
}
int main ( )
{
scanf ( "%d" , &t );
while ( t-- )
{
scanf ( "%d%d" , &n , &m );
init ( );
int ans = -m;
while ( m-- )
{
scanf ( "%d%d" , &u , &v );
add ( u , v );
}
for ( int i = 1 ; i <= n ; i++ )
if (!color[i])
{
memset ( cnt , 0 , sizeof ( cnt ));
dfs ( i );
if ( cnt[1] || cnt[2] )
p.push_back ( make_pair ( cnt[1] , cnt[2]));
}
//dp[0] = 1;
/*for ( int i = 0 ; i < p.size() ; i++ )
for ( int j = n/2 ; j >= 1 ; j-- )
if ( ( j >= p[i].first&&dp[j-p[i].first])
||( j >=p[i].second&&dp[j-p[i ].second]) )
dp[j] = 1;*/
//dp.reset();
//cout << "YES" << endl;
dp[0] = 1;
for ( int i = 0 ; i < p.size() ; i++ )
dp = dp<<p[i].first | dp<<p[i].second;
int mid;
for ( int i = 0 ;; i++ )
{
if ( dp[n/2-i] )
{
mid = n/2 - i;
break;
}
}
ans += mid*(n-mid);
printf ( "%d\n" , ans );
}
}