1. 程式人生 > >HDU 6321(狀壓dp)

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("");
        }
    }
}