HDU 6391 Lord Li's problem(dp+组合数学)

探讨了在一个特定的二进制数字集合中,如何选择子集使其异或结果等于给定目标值的问题,并提供了一种动态规划的解决方案。

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

Description

对于00~2n1这些数字中二进制表示只有三个11的数字集,问从中选取一个k子集与SS异或结果为T的方案数

Input

多组用例,每组用例首先输入两个整数n,kn,k,之后输入两个长度为nn01串为S,TS,T的二进制表示,以0 00 0结束输入

(1n40,0kmin(20,C3n))(1≤n≤40,0≤k≤min(20,Cn3))

Output

输出方案数,结果模1926081719260817

Sample Input

4 3
1101
1001
3 1
101
010
5 3
11010
10111
0 0

Sample Output

Case #1: 1
Case #2: 1
Case #3: 6

Solution

问题转化为用不同的kk个二进制表示只有三个1nn位二进制数异或得到S^TT,假设S^TTm个位置为11,显然方案数只和m有关,与这mm1的具体位置无关,那么我们只需使得这kk个数字异或结果的后m位为11,前nm位为00即可,以dp[i][j]表示用ii个不同的数字异或结果为后j位为11,前nj位为00的方案数,考虑第i个数的贡献,有四种情况

11.311全部在之前为0的位,也即从当前的jj1中找33个位置放置当前数字的311,故有转移

dp[i][j]+=Cj3dp[i1][j3]

22.211在之前为0的位,111在之前为11的位,也即从当前的j11中找2个位置放置当前数字产生的221,从当前的njn−j11里找1个位置放置当前数字消除的111,故有转移

dp[i][j]+=C2jC1njdp[i1][j1]dp[i][j]+=Cj2⋅Cn−j1⋅dp[i−1][j−1]

33.111在之前为0的位,221在之前为11的位,也即从当前的j11中找1个位置放置当前数字产生的111,从当前的njn−j11里找2个位置放置当前数字消除的221,故有转移
dp[i][j]+=C1jC2njdp[i1][j+1]dp[i][j]+=Cj1⋅Cn−j2⋅dp[i−1][j+1]

44.311全部在之前的1位,也即从当前的njn−j003个位置放置当前数字消除的331,故有转移
dp[i][j]+=C3njdp[i1][j+3]dp[i][j]+=Cn−j3⋅dp[i−1][j+3]

首先注意到此时我们放置的第ii个数字可能会与前面的i1个数字中的某个重复,故有
dp[i][j]=(C3n(i2))dp[i2][j]dp[i][j]−=(Cn3−(i−2))⋅dp[i−2][j]

表示前i2i−2个不同的数字已经使得后jj位为1,前njn−j位为00,而第i个数字重复出现了两次,选取一个和前i2i−2个数字不同的数字均可

其次我们不应考虑数字的前后顺序,故有

dp[i][j]=dp[i][j]idp[i][j]=dp[i][j]i

单组用例时间复杂度O(nk)O(nk),总时间复杂度O(Tnk)O(Tnk),注意到可以预处理每个长度的答案,对于每组用例只需统计mm即可,时间复杂度O(n2k+Tn)

Code

#include<cstdio>
#include<algorithm>
using namespace std;
typedef long long ll;
#define mod 19260817
int mul(int x,int y)
{
    ll z=1ll*x*y;
    return z-z/mod*mod;
}
int add(int x,int y)
{
    x+=y;
    if(x>=mod)x-=mod;
    return x;
}
int n,k,inv[22],C[44][44],dp[44][22][44];
char a[44],b[44];
void init()
{
    inv[1]=1;
    for(int i=2;i<=20;i++)inv[i]=mul(mod-mod/i,inv[mod%i]);
    for(int i=0;i<=40;i++)
    {
        C[i][0]=C[i][i]=1;
        for(int j=1;j<i;j++)C[i][j]=add(C[i-1][j-1],C[i-1][j]);
    }
    for(int n=1;n<=40;n++)
    {
        dp[n][0][0]=1;
        for(int i=1;i<=min(20,C[n][3]);i++)
            for(int j=0;j<=n;j++)
            {
                if(j+1<=n)dp[n][i][j]=add(dp[n][i][j],mul(dp[n][i-1][j+1],mul(C[j][1],C[n-j][2])));
                if(j+3<=n)dp[n][i][j]=add(dp[n][i][j],mul(dp[n][i-1][j+3],C[n-j][3]));
                if(j>=1)dp[n][i][j]=add(dp[n][i][j],mul(dp[n][i-1][j-1],mul(C[j][2],C[n-j][1])));
                if(j>=3)dp[n][i][j]=add(dp[n][i][j],mul(dp[n][i-1][j-3],C[j][3]));
                if(i>=2)dp[n][i][j]=add(dp[n][i][j],mod-mul(dp[n][i-2][j],add(C[n][3],mod-(i-2))));
                dp[n][i][j]=mul(dp[n][i][j],inv[i]);
            }
    }
}
int main()
{
    int Case=1;
    init();
    while(~scanf("%d%d",&n,&k),n||k)
    {
        int m=0;
        scanf("%s%s",a,b);
        for(int i=0;i<n;i++)m+=(a[i]-'0')^(b[i]-'0');
        printf("Case #%d: %d\n",Case++,dp[n][k][m]);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值