题目链接:
点击打开链接
题目大意:
给出一张二分图,问这张二分图还能最多加多少条边?
题目分析:
这道题的思路是这样的:
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 ); } }