HDU 6321 Dynamic Graph Matching题解(状压DP)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Q1410136042/article/details/81318677

题目链接:题目传送门~

题意:给定一个n 个点的无向图,m 次加边或者删边操作。在每次操作后统计有多少个匹配包含k = 1, 2, ...,n/2条边。 一个匹配的意思是一个边的集合,并且这些边都没有公共顶点
n∈{2,4,6,8,10}, 1 ≤ m ≤ 30000

题解:解释这一题如何DP之前先明确几点:

  1. 一个有k条边的匹配(后面简称k匹配)必然包含2*k个点,一个2*k个点的集合只能构成k匹配或者不构成匹配。理由:一条边两个端点,k条边没有公共端点,那显然是包含2*k个点
  2. 题目要求匹配数量,也就是边集数量,由于第一点,我们就可以将边集转化为点集,k匹配自然对应2*k个点集了。第二点就是解释如何表示点集——即如何状态压缩(状压)。一个点跟一个集合的关系,要么属于,要么不属于。给每一个点标记一个位置,如果这个点属于这个集合,那么对应的标志位在这个集合里就是1,否则为0。这就可以用二进制数来表示了~一个二进制数从右向左第i位分别表示点i,那么10个点最多有2^{10}=1024个状态,这就一一对应了10个点集合的1024个子集。比如十进制数 5,二进制为101,从右向左第1和第3个位置是1,即表示点集{1,3}。需要注意,任何一个单元素集{i}对应的是整数2^{i-1}而不是2^{i}
  3. 由第二点,我们了解了如何用数字表示集合,现在需要知道集合的运算。设数字a,b分别表示集合A、B,则A∪B对应a | b,A ∩ B对应a & b,A∪B-A∩B对应a^b,如果A∩B为空,则a^b和a|b一样
  4.  __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;
}

猜你喜欢

转载自blog.csdn.net/Q1410136042/article/details/81318677