HDU 6321(状压dp)

传送门

题面:

Problem C. Dynamic Graph Matching

Time Limit: 8000/4000 MS (Java/Others)    Memory Limit: 524288/524288 K (Java/Others)
Total Submission(s): 859    Accepted Submission(s): 345

 

Problem Description

In the mathematical discipline of graph theory, a matching in a graph is a set of edges without common vertices.
You are given an undirected graph with n vertices, labeled by 1,2,...,n. Initially the graph has no edges.
There are 2 kinds of operations :
+ u v, add an edge (u,v) into the graph, multiple edges between same pair of vertices are allowed.
- u v, remove an edge (u,v), it is guaranteed that there are at least one such edge in the graph.
Your task is to compute the number of matchings with exactly k edges after each operation for k=1,2,3,...,n2. Note that multiple edges between same pair of vertices are considered different.

Input

The first line of the input contains an integer T(1≤T≤10), denoting the number of test cases.
In each test case, there are 2 integers n,m(2≤n≤10,nmod2=0,1≤m≤30000), denoting the number of vertices and operations.
For the next m lines, each line describes an operation, and it is guaranteed that 1≤u<v≤n.

Output

For each operation, print a single line containing n2 integers, denoting the answer for k=1,2,3,...,n2. Since the answer may be very large, please print the answer modulo 109+7.

Sample Input

1 4 8 + 1 2 + 3 4 + 1 3 + 2 4 - 1 2 - 3 4 + 1 2 + 3 4

Sample Output

1 0 2 1 3 1 4 2 3 1 2 1 3 1 4 2

 

题目描述:

    给定一个 n 个点的无向图,m 次加边或者删边操作。 在每次操作后统计有多少个匹配包含 k = 1, 2, ..., n 2 条边。 2 ≤ n ≤ 10, 1 ≤ m ≤ 30000。

题目分析:

    方法1:->代码1

    首先我们要对数据范围有所敏感,因为我们发现n最大才只有10,而每加/减一条边后的状态是由前一个状态转移过来的,因而我们可以考虑用状态压缩dp去解决问题。

    因为本状态是由上一个状态转移而来,因而我们可以考虑设立一个二维的dp数组,dp[now][s]。代表了在当前的状态now中集合数为s的匹配数。因此我们可以找到第一条状态转移的方程 dp[now][i]= dp[pre][i].

    其次,在每进行一次加边的过程中,我们可以发现,假设加的一条边在前一个状态没有出现过,那么加上这条边之后的状态是由前一个状态的匹配数的基础上转移而来的,因此有状态转移方程

    同理,在每一次减边的过程中,如果减的两条边在前一个状态没有出现过的话,则在减去这条边后的状态是由前一个状态转移而来的,故有转移方程

    将所有方案的匹配数求完之后,只需统计一下答案即可。()

    

    方法2:->代码2

    在做状态转移的过程中,dp数组中的第一维事实上是可以省略的,我们事实上可以将数组压缩成一位的dp数组 dp[S],代表着S集合的点已经匹配的方案数。

    在此后的加边操作中,则我们需要从大到小遍历S,如果发现当前加的一条边在前一个状态没有出现过,则有状态转移方程,dp[S]+ = dp[S − from − to],即我们可以表示为

    同理在减边的过程中,我们可以看作是加边的逆操作,因此我们可以从小到大遍历S,如果发现当前减的一条边在前一个状态没有出现过,则有状态转移:

代码:

    代码1:

#include <bits/stdc++.h>
#define maxn 2005
using namespace std;
const int mod=1e9+7;
int dp[2][maxn];
int cnt[maxn];
int ans[maxn];
char str[2];
int bit(int x){//获取某个数二进制位上有多少个1
    int cnt=0;
    while(x){
        if(x&1) cnt++;
        x>>=1;
    }
    return cnt;
}
void init(){//初始化处理二进制位上1的个数
    for(int i=0;i<1024;i++){
        cnt[i]=bit(i);
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    init();
    int now=1,pre=0;
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        int all=1<<n;
        memset(dp,0,sizeof(dp));
        dp[0][0]=1;
        while(m--){
            scanf("%s",str);
            int from,to;
            scanf("%d%d",&from,&to);
            from--,to--;
            int tmp=(1<<from)|(1<<to);//代表第from位和第to位有一条边
            for(int i=0;i<all;i++){//先进行状态转移
                dp[now][i]=dp[pre][i];
            }
            if(str[0]=='+'){
                for(int i=0;i<all;i++){
                    if(!(tmp&i)){//如果没加上
                        dp[now][i|tmp]=(dp[now][i|tmp]+dp[pre][i])%mod;
                    }
                }
            }
            else{
                for(int i=0;i<all;i++){
                    if(!(tmp&i)){//如果没减去
                        dp[now][i|tmp]=(dp[now][i|tmp]-dp[pre][i]+mod)%mod;
                    }
                }
            }
            memset(ans,0,sizeof(ans));
            for(int i=0;i<all;i++){//统计答案
                ans[cnt[i]]=(ans[cnt[i]]+dp[now][i])%mod;
            }
            for(int i=2;i<=n;i+=2){
                if(i!=2) cout<<" ";
                cout<<ans[i];
            }
            puts("");
            pre^=1,now^=1;
        }
    }
}

    代码2:

#include <bits/stdc++.h>
#define maxn 2005
using namespace std;
const int mod=1e9+7;
int cnt[maxn];
int ans[maxn];
int dp[maxn];
char str[3];
int bit(int x){//获取某个数二进制中1的个数
    int cnt=0;
    while(x){
        if(x&1) cnt++;
        x>>=1;
    }
    return cnt;
}
void init(){//初始化
    for(int i=0;i<1024;i++){
        cnt[i]=bit(i);
    }
}
int main()
{
    int t;
    scanf("%d",&t);
    init();
    while(t--){
        int n,m;
        scanf("%d%d",&n,&m);
        memset(dp,0,sizeof(dp));
        dp[0]=1;
        int all=1<<n;
        while(m--){
            scanf("%s",str);
            int from,to;
            scanf("%d%d",&from,&to);
            from--,to--;
            int tmp=(1<<from)|(1<<to);
            if(str[0]=='+'){
                for(int i=all-1;i>=0;i--){
                    if(!(tmp&i)){//如果当前加的边在状态i中没出现过,则加上这条边的状态需要加上第i的状态
                        dp[i^tmp]=(dp[i^tmp]+dp[i])%mod;
                    }
                }
            }
            else{
                for(int i=0;i<all;i++){
                    if(!(tmp&i)){//减边的同理
                        dp[i^tmp]=(dp[i^tmp]-dp[i]+mod)%mod;
                    }
                }
            }
            memset(ans,0,sizeof(ans));
            for(int i=1;i<all;i++){
                ans[cnt[i]]=(ans[cnt[i]]+dp[i])%mod;
            }
            for(int i=2;i<=n;i+=2){
                if(i!=2) cout<<" ";
                cout<<ans[i];
            }
            puts("");
        }
    }
}
原文地址:https://www.cnblogs.com/Chen-Jr/p/11007253.html