【51nod 1684】子集价值【DP】【拆括号的技巧】

这是一篇关于解决使用新定义的按位运算 '#' 计算序列子集价值的问题。通过分析序列的二进制表示,采用动态规划的方法,计算不同位的组合对结果的贡献,以 O(p^2 * n) 的复杂度求解问题。

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

Description

给出⼀种新的按位运算 ‘#’ 的真值表
定义⼀个序列的价值为序列中所有数从左向右进行’#’ 运算得到的值给定⼤小为 n 的序列列 a[],求它的 2^n-1 个⾮空子序列的价值平⽅和
n <= 50000, a[i] < 2^30

Soultion

  我们考虑一个这样的S2=(ac1 # ac2 #)2
  将S表示成二进制(x1x2)2
  我们将x分解是二的幂次,若S and 2i为0,则xi=0,否则xi=2i
  那么我们将S2分解后可以得到如下形式(x0+x1+...xp1)2
  再分解上式得到x20+x0x1+...+x0xk1+x1x0+x21+...+x1xp1+...+
  
  我们发现这个式子中最多只与两个位有关。
  因此我们可以递推,就是只考虑所有这样的S中第i位和第j位的累和。每次枚举i和j(O(p2)),表示选择了第i位和第j位,令dp[0/1][0/1]表示通过新的位运算后,到当前枚举的位置,第i位为0/1,第j位为0/1的方案总数。
  
  因为只有i和j都为1时相乘才为1,所以最后将2^(i+j)乘上dp[1][1]就是对答案的贡献了。
  只需将这些贡献累计起来就是答案了。
  总复杂度为O(p2n)

Summary

  做带有平方的题目可以考虑将平方分解。

Code

#include<cstdio>
#include<cstring>
#include<algorithm>

#define N 50100
#define mod 1000000007
using namespace std;

int to[2][2],f[N][2][2],a[N];
int n,p;

void M(int &x) {if(x >= mod) x -= mod;}
int get(int p1,int p2)
{
    memset(f,0,sizeof(f));
    for(register int k = 1;k <= n;k++) {
        int n1 = (a[k]>>p1)&1,n2 = (a[k]>>p2)&1;
        M(++f[k][n1][n2]);
        for(int i = 0;i < 2;i++)
            for(int j = 0;j < 2;j++) {
                M( f[k][to[i][n1]][to[j][n2]] += f[k-1][i][j] );
                M( f[k][i][j] += f[k-1][i][j] );
            }
    }
    return f[n][1][1];
}

int main()
{
    scanf("%d%d",&n,&p);
    for(int i = 0;i < 2;i++) for(int j = 0;j < 2;j++) scanf("%d",&to[i][j]);
    for(int i = 1;i <= n;i++) scanf("%d",&a[i]);
    int ans = 0;
    for(int i = 0;i < p;i++)
        for(int j = 0;j < p;j++)
            ans = (ans + (1ll<<(i+j)) % mod * get(i,j)) % mod;
    printf("%d\n",ans);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值