Uestc1291 上天的卿学姐【状压dp】

本文探讨了一个关于16维01向量的数学问题,旨在寻找这些向量中所有单调不减子序列的数量。文章提供了一种高效的O(2^(m/2))的状压动态规划解法,并附带完整的C++实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值