1. 程式人生 > >JZOJ 5230. 隊伍統計

JZOJ 5230. 隊伍統計

desc 所有 pre wid 分析 想要 一個 矛盾 image

題目

Description

現在有n個人要排成一列,編號為1->n 。但由於一些不明原因的關系,人與人之間可能存在一些矛盾關系,具體有m條矛盾關系(u,v),表示編號為u的人想要排在編號為v的人前面。要使得隊伍和諧,最多不能違背k條矛盾關系(即不能有超過k條矛盾關系(u,v),滿足最後v排在了u前面)。問有多少合法的排列。答案對10^9+7取模。

Input

輸入文件名為count.in。
第一行包括三個整數n,m,k。
接下來m行,每行兩個整數u,v,描述一個矛盾關系(u,v)。
保證不存在兩對矛盾關系(u,v),(x,y),使得u=x且v=y 。

Output

輸出文件名為count.out。
輸出包括一行表示合法的排列數。

Sample Input

輸入1:
4 2 1
1 3
4 2

輸入2:
10 12 3
2 6
6 10
1 7
4 1
6 1
2 4
7 6
1 4
10 4
10 9
5 9
8 10

Sample Output

輸出1:
18

輸出2:
123120

Data Constraint

對於30%的數據,n<=10
對於60%的數據,n<=15
對應100%的數據,n,k<=20,m<=n*(n-1),保證矛盾關系不重復。

分析

  • 一道狀壓DP(不會啊啊啊啊

  • 考試時,穩穩地打了暴力 30
  • 後來才理解
  • 狀壓,顧名思義就是要將一些狀壓想辦法壓縮起來(可以壓,也可以刪)。其中這些狀態都滿足相似性和數量很多。這樣才好壓而且壓得有意義。
  • 所以一般基礎的狀壓就是將一行的狀態壓成一個數,這個題用數的二進制形式反映了一種情況。所以位運算可以幫助我們解決很多問題。
  • 這道題,我們將沖突的數用二進制放在a數組裏
  • 然後我們用二進制數來代表n位的選取情況
  • 進行DP f[i][j] 表示在二進制表示的第i種選取情況下有j種矛盾的情況數
  • 轉移方程就是f[當前已改變的集合][j+沖突個數]+=沒改變前的種數f[i][j]
  • 然後對於每個i 我們還要枚舉對第x位進行一次選取,計算出答案
  • (位運算分析具體在代碼裏)

技術分享圖片

代碼

 1 #include<fstream>
 2 #define M 1000000007
 3 #define cin fin
 4 #define cout fout
 5 using namespace std;
 6 ifstream fin("count.in");
 7 ofstream fout ("count.out");
 8 int a[21],f[1<<21][21],t[1<<21];
 9 int n,m,k;
10 void take()    //計算所有集合中1的個數 也就是選取的個數 
11 {
12     for (int i=0;i<=(1<<n)-1;i++)
13     {
14         int ii=i;
15         while (ii)
16         {
17             t[i]++;
18             ii-=(ii&(-ii));     //(ii&(-ii))找到最後的1位置,然後減去 
19         }
20     }
21 }
22 int main ()
23 {
24     ios::sync_with_stdio(false);   //cin優化 
25     cin>>n>>m>>k;
26     for (int i=1,u,v;i<=m;i++)
27     {
28         cin>>u>>v;
29         a[u]|=(1<<(v-1));   //用二進制,來反應它的沖突 例: 
30     }                        // 首先數組為    00000 
31     take();                  // 現在與3沖突了 00100  第三位 1 
32     f[0][0]=1;               // 又與 4 沖突了 01100  第四位變1 
33    for(int i=0;i<=(1<<n)-1;i++)  //所以這就表示出來了,當前數u與其他數的沖突情況 
34      for(int j=0;j<=k;j++)
35       if(f[i][j])
36         for(int x=1;x<=n;x++)
37           if((i&(1<<(x-1)))==0)   // 表示當前集合與要變的位置沒有變過 
38 /*因為t記錄的是(上面)t[i&a[x]]為沖突個數*/if(j+t[i&a[x]]<=k)    //例 1010 x=2 1010 & 0100 無改變
39                 f[i|(1<<(x-1))][j+t[i&a[x]]]=(f[i|(1<<(x-1))][j+t[i&a[x]]]+f[i][j])%M;
40     int ans=0;
41     for (int i=0;i<=k;i++)
42        ans=(ans+f[(1<<n)-1][i])%M;
43     cout<<ans;
44 }

JZOJ 5230. 隊伍統計