题目
分析
-
一道状压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 }