• 【BZOJ4513】[SDOI2016] 储能表(数位DP)


    点此看题面

    大致题意: 给定(n,m,k),求(sum_{i=0}^{n-1}sum_{j=0}^{m-1}max((i xor j)-k,0))

    前言

    早上来机房做数学作业,结果一个不等式证明暴拆了一百多项,而且算到一半感觉不太对劲,心态爆炸。

    于是把数学扔一边,先开始做题,于是就找到了这道题。

    然后发现是好水一道数位(DP)啊。。。(反正比我的数学作业简单多了)

    本来应该可以一发(A)的,结果数组没清干净(WA)成了(20)分。

    (水完这篇博客继续肝数学。。。)

    数位(DP)

    首先,把(n,m,k)转化为二进制。

    然后,我们设(f_{x,0/1,0/1,0/1})从高位向低位 (DP)到右起第(x)位,是否满足(i<n),是否满足(j<m),是否满足((i xor j)>k)(i xor j)(x)的总和,(g_{x,0/1,0/1,0/1})表示对应的数的个数。

    转移时,只要枚举(i,j)当前位置上填(0)还是(1)(注意,所填的数必须要符合大小限制,这一过程可以详见代码),然后转移。

    最终的答案就是(f_{lim,0,0,0}-kg_{lim,0,0,0})(lim)为最高位)。

    代码

    #include<bits/stdc++.h>
    #define Tp template<typename Ty>
    #define Ts template<typename Ty,typename... Ar>
    #define Reg register
    #define RI Reg int
    #define Con const
    #define CI Con int&
    #define I inline
    #define W while
    #define LL long long
    #define Inc(x,y) ((x+=(y))>=X&&(x-=X))
    using namespace std;
    LL N,M,K;int X;
    class DigitalDP//数位DP
    {
    	private:
    		#define SZ 70
    		int lim,n[SZ+5],m[SZ+5],k[SZ+5];struct data {int v,t;}f[SZ+5][2][2][2];
    		I void Init(LL x,int *v) {RI t=0;W(x) v[++t]=x&1,x>>=1;t>lim&&(lim=t);}
    		I data DP(CI x,CI f1,CI f2,CI f3)
    		{
    			if(!x) return (data){0,f1&&f2&&f3};if(~f[x][f1][f2][f3].v) return f[x][f1][f2][f3];//边界&&记忆化
    			data &F=f[x][f1][f2][f3],t;F.v=0;
    			(f3||!k[x])&&(t=DP(x-1,f1|n[x],f2|m[x],f3),F.v=(t.v+F.v)%X,Inc(F.t,t.t));//填0,0
    			(f1||n[x])&&(t=DP(x-1,f1,f2|m[x],f3|!k[x]),F.v=((1LL<<x-1)%X*t.t+t.v+F.v)%X,Inc(F.t,t.t));//填1,0
    			(f2||m[x])&&(t=DP(x-1,f1|n[x],f2,f3|!k[x]),F.v=((1LL<<x-1)%X*t.t+t.v+F.v)%X,Inc(F.t,t.t));//填0,1
    			(f1||n[x])&&(f2||m[x])&&(f3||!k[x])&&(t=DP(x-1,f1,f2,f3),F.v=(t.v+F.v)%X,Inc(F.t,t.t));//填1,1
    			return f[x][f1][f2][f3];
    		}
    	public:
    		I void Solve()
    		{
    			lim=0,Init(N,n),Init(M,m),Init(K,k);//转化为二进制
    			RI i,x,y,z;for(i=1;i<=lim;++i) for(x=0;x^2;++x)//清空f
    				for(y=0;y^2;++y) for(z=0;z^2;++z) f[i][x][y][z]=(data){-1,0};
    			data res=DP(lim,0,0,0);printf("%d
    ",(res.v-1LL*res.t*(K%X)%X+X)%X);//DP&&计算最终答案
    			for(i=1;i<=lim;++i) n[i]=m[i]=k[i]=0;//注意这里的清空
    		}
    }D;
    int main()
    {
    	RI Qt;scanf("%d",&Qt);W(Qt--) scanf("%lld%lld%lld%d",&N,&M,&K,&X),D.Solve();return 0;
    }
    
  • 相关阅读:
    C++基类的析构函数定义为虚函数的原因
    android的学习网站
    QT显示url图片
    Ubuntu安装JDK
    linux下打包压缩和解压命令
    嵌入式目录
    QT pri 文件的作用
    QT pro文件详细写法+实例
    Computer(树的直径做法)
    树的直径
  • 原文地址:https://www.cnblogs.com/chenxiaoran666/p/BZOJ4513.html
Copyright © 2020-2023  润新知