题目链接:题目传送门~
题意:给定一个n 个点的无向图,m 次加边或者删边操作。在每次操作后统计有多少个匹配包含k = 1, 2, ...,n/2条边。 一个匹配的意思是一个边的集合,并且这些边都没有公共顶点
n∈{2,4,6,8,10}, 1 ≤ m ≤ 30000
题解:解释这一题如何DP之前先明确几点:
- 一个有k条边的匹配(后面简称k匹配)必然包含2*k个点,一个2*k个点的集合只能构成k匹配或者不构成匹配。理由:一条边两个端点,k条边没有公共端点,那显然是包含2*k个点
- 题目要求匹配数量,也就是边集数量,由于第一点,我们就可以将边集转化为点集,k匹配自然对应2*k个点集了。第二点就是解释如何表示点集——即如何状态压缩(状压)。一个点跟一个集合的关系,要么属于,要么不属于。给每一个点标记一个位置,如果这个点属于这个集合,那么对应的标志位在这个集合里就是1,否则为0。这就可以用二进制数来表示了~一个二进制数从右向左第i位分别表示点i,那么10个点最多有=1024个状态,这就一一对应了10个点集合的1024个子集。比如十进制数 5,二进制为101,从右向左第1和第3个位置是1,即表示点集{1,3}。需要注意,任何一个单元素集{i}对应的是整数而不是
- 由第二点,我们了解了如何用数字表示集合,现在需要知道集合的运算。设数字a,b分别表示集合A、B,则A∪B对应a | b,A ∩ B对应a & b,A∪B-A∩B对应a^b,如果A∩B为空,则a^b和a|b一样
- __builtin_popcount(i)返回i的二进制表示里面1的数量,这对下面的理解没什么帮助,只是代码里用到了,所以提一下
理解上面三点之后就可以看状态转移方程了,设dp[i][j]表示集合j (上面第2点说的,一个数字就可以表示一个集合)所包含的匹配数。那么显然初始化dp[0][0]=1,其他都是0。然后考虑增边——假设增加的边为a—b,则可以用集合{a,b}表示,对应的数字是 e=(1<<(a-1))^(1<<(b-1)),即点a和点b的并。若任意集合t与集合e无交,即 t&e == 0,则增加的e会影响到集合t^e,显然 dp[i][t^e]=dp[i-1][t^e] + dp[i-1][t]。
这个“显然”怎么来的呢?首先,增边对原来的已经有了的匹配没有影响,所以等式右边有一个dp[i-1][t^e],然后再来理解dp[i-1][t]是啥~t和e没有交,因为e只有两个点,所以e是一个1匹配,假设t^e对应的是k匹配,则t对应的是(k-1)匹配。dp[i-1][t]表示上一次增边所得到的(k-1)匹配,然后给加上一个e,即{t,e}就是这次增边新生成k匹配,数量和dp[i-1][t]一样,所以dp[i][t^e]=dp[i-1][t^e] + dp[i-1][t]
然后是减边的状态转移,是增边的逆过程:dp[i][t^e]=dp[i-1][t^e] - dp[i-1][t]。这就不予以解释了,我相信,理解了增边的过程,减边的过程自然就理解了
AC代码:
#include<iostream>
#include<cstdio>
#include<cmath>
#include<map>
#include<vector>
#include<iomanip>
#include<algorithm>
#include<string>
#include<cstring>
#include<ctime>
#include<queue>
#define ll long long
#define DEBUG printf("DEBUG\n")
#define FOR(i, s, n) for(int i = s; i < n; ++ i)
#define For(i, s, n) for(int i = s; i > n; -- i)
#define mem(a, n) memset((a), (n), sizeof(a))
const int inf = 0x3f3f3f3f;
const ll INF = 0x3f3f3f3f3f3f3f3fLL;
const int mod = 1e9+7;
const int maxn = 1e5+10;
using namespace std;
typedef vector<int> V;
typedef vector<V> VV;
int dp[1024];
int ans[11];
int cnt[1024];
int main()
{
#ifdef AFei
freopen("in.c", "r", stdin);
#endif // AFei
FOR(i, 0, 1024) cnt[i] = __builtin_popcount(i);
int T;
scanf("%d", &T);
while(T --)
{
int m, n;
scanf("%d%d", &n, &m);
int t = 1<<n;
FOR(i, 0, t) dp[i] = 0;
dp[0] = 1;
while(m --)
{
FOR(i, 0, n+1) ans[i] = 0;
char s[2];
int a, b;
scanf("%s%d%d", s, &a, &b);
-- a, -- b;
int e = (1<<a) ^ (1<<b);
if(s[0] == '+')
{
for(int i = t-1; ~i; -- i)
if(!(i&e))
{
dp[i^e] += dp[i];
if(dp[i^e] >= mod) dp[i^e] -= mod;
}
}
else
{
for(int i = t-1; ~i; -- i)
if(!(i&e))
{
dp[i^e] -= dp[i];
if(dp[i^e] < 0) dp[i^e] += mod;
}
}
FOR(i, 0, t)
{
ans[cnt[i]] += dp[i];
if(ans[cnt[i]] >= mod)
ans[cnt[i]] -= mod;
}
for(int i = 2; i <= n; i += 2)
printf(i == n ? "%d\n" : "%d ", ans[i]);
}
}
return 0;
}