BZOJ 4513 [Sdoi2016]储能表

数位DP

刚开始把矩阵打出来,发现有规律,然后就没有然后了。。。(网上似乎有大神用规律做出来了?)

讲一讲数位DP的做法:

 aij=i xor jk , 用[…]表示值为0或1的真假表达式

转化一下,原题变成求一个式子:

n1i=0m1j=0aij[aij>0]kn1i=0m1j=0[aij>0]

于是需要求出矩阵中有多少元素 aij 大于k,以及它们的和

我原本记f[i][0/1]表示做到第i位,前i位是否顶下界的答案,然而这种状态信息不足,无法知道所有合法答案中接下来的i和j是否会超过n和m。于是多记两维f[i][0/1][0/1][0/1]表示i,j是否顶上界即可。

我刚开始把所有转移式+条件手列出来,很大可能会写错,而且很麻烦。实际上只需要枚举下一位是什么,然后主动转移判断对应状态是什么即可。

#include<cstdio>
#include<cstring>
#define H 63
using namespace std;
typedef long long ll;
namespace runzhe2000
{
    int T, mod;
    ll bin[H], f[H][2][2][2], g[H][2][2][2], n, m, k;
    void main()
    {
        scanf("%d",&T);
        while(T--)
        {
            scanf("%lld%lld%lld%d",&n,&m,&k,&mod);
            n--; m--;
            bin[0] = 1;
            for(int i = 1; i < H; i++)bin[i] = (bin[i-1] << 1) % mod;
            memset(g,0,sizeof(g));
            memset(f,0,sizeof(f));
            f[H-1][1][1][1] = 1;
            for(int i = H-1; i; i--)
            {
                int ni = ( n & (1ll<<(i-1)) ) ? 1 : 0;
                int mi = ( m & (1ll<<(i-1)) ) ? 1 : 0;
                int ki = ( k & (1ll<<(i-1)) ) ? 1 : 0;
                for(int s1 = 0; s1 <= 1; s1++)
                {
                    for(int s2 = 0; s2 <= 1; s2++)
                    {
                        for(int s3 = 0; s3 <= 1; s3++)
                        {
                            if(f[i][s1][s2][s3])
                            {
                                for(int a = 0; a <= 1; a++)
                                {
                                    if(s2 && ni < a)continue;
                                    for(int b = 0; b <= 1; b++)
                                    {
                                        if(s3 && mi < b)continue;
                                        if(s1 && (a^b) < ki)continue;
                                        int ss1 = (s1 && ki==(a^b))?1:0;
                                        int ss2 = (s2 && ni==a    )?1:0;
                                        int ss3 = (s3 && mi==b    )?1:0;
                                        ( f[i-1][ss1][ss2][ss3] += f[i][s1][s2][s3] ) %= mod;
                                        ( g[i-1][ss1][ss2][ss3] += (a^b)*bin[i-1]*f[i][s1][s2][s3]%mod+g[i][s1][s2][s3]) %= mod;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            ll ans = 0;
            k %= mod;
            for(int s1 = 0; s1 <= 1; s1++)
                for(int s2 = 0; s2 <= 1; s2++)
                    for(int s3 = 0; s3 <= 1; s3++)
                        (ans+=g[0][s1][s2][s3]-k*f[0][s1][s2][s3]%mod)%=mod;
            printf("%lld\n",(ans+mod)%mod);
        }
    }
}
int main()
{
    runzhe2000::main(); 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值