Description
众所周知,卿学姐十分擅长数据结构。
一天卿学姐开始研究起二维偏序的问题,卿学姐三下五除二就写了个树状数组解决了。
于是卿学姐开始做三维的问题,搞了个树套树也是过了。
欲求不满的卿学姐直接开始搞五维的偏序,仔细思索之后,卿学姐研究出一种用分块加bitset的做法。
峰回路转,沈宝宝问感觉自己要上天的卿学姐16维偏序怎么做,卿学姐现在还在研究六维偏序,不得不将这个问题交给你。
为了简单起见,现在有nn个mm维01向量,定义向量uu大于等于向量vv,当且仅当向量uu中的每个分量都大于等于vv中对应位置的分量,即:
ui≥vi,1≤i≤m
现在问这个向量序列中有多少个子序列是单调不减的子序列。
由于答案可能很大,所以输出结果取模1e9+7
Input
第一行两个整数n,m分别表示向量的个数和向量的维度。
接下来nn行m列,第ii行为一个01的字符串,长度是m,表示第i个向量
1≤n≤200000,1≤d≤16
Output
输出一个整数,表示单调不减子序列的个数
Sample input and output
3 2
00
00
11
7
4 3
110
100
011
101
5
Hint
对于第二个样例来说,如果子序列最后的长度是1,我们总共能构造4个,如果最后的长度是2,我们能构造一个{100,101}。
解题思路:
很容易想到O(2m)O(2m)的状压dp。
一种是f[i]表示以i结尾的方案数,O(2m)O(2m)枚举子集状态确定新增方案数,O(1)O(1)转移;
一种是f[i]表示结尾小等于i的方案数,O(1)O(1)确定新增方案数,O(2m)O(2m)枚举超集转移;
考虑如何优化.
折半状态,结合两种算法,f[i][j]表示前8位确定为i,后8位是j的子集的方案数,确定新增方案数和转移就都是Om/2Om/2的了。
#include<bits/stdc++.h>
using namespace std;
const int mod=1e9+7;
int n,m,x,f[1<<8][1<<8];
char s[16];
int main()
{
//freopen("lx.in","r",stdin);
scanf("%d%d",&n,&m);
while(n--)
{
scanf("%s",s);x=0;
int len=strlen(s);
for(int i=0;i<len;i++)x=(x<<1)+s[i]-'0';
int a=x>>8,b=x^(a<<8),tmp=1;
for(int i=a;;i=a&(i-1))
{
tmp=(tmp+f[i][b])%mod;
if(!i)break;
}
for(int i=b;i<(1<<8);i=(i+1)|b)f[a][i]=(f[a][i]+tmp)%mod;
}
int ans=0;
for(int i=0;i<(1<<8);i++)ans=(ans+f[i][255])%mod;
cout<<ans;
return 0;
}