您的位置:首页 > 其它

BZOJ 4513: [Sdoi2016]储能表(记忆化搜索)

2018-03-14 15:10 218 查看

题目

原题链接

格子(i,j)的大小是max(i^j-k,0),求一个前缀矩阵mod p(动态给出)的值,范围可达1e18,5000组询问。

分析

网上搜链接,握草,只写一个状态!?

网上搜代码,握草,全是填表法!?

还有什么叫直接DP就好了,小生完全不会啊啊啊啊啊!!!!

还有这道题好像可以找规律,然后我数位DP分析着分析着就变成找规律了,而且还很疑惑觉得为什么和周围的小伙伴不一样?要注意数位DP的内涵啊。

切入正题:

由于是xor运算,很容易想到转换成二进制来做。

我们实际上要做的就是要找到这样的一些点对的亦或和:i<=n && j<=m && i^j>=k,然后从高位开始考虑,因此记忆化搜索的时候需要同时把n,m,k转换成二进制后带入限制,其中这里k是向下的限制,所以和一般的数位DP略有不同。

在转移的时候,我们考虑的是最高位可以选择的情况,正如一般的数位DP一样,但是我们没有必要并且并不容易把两个元素的同一位分开考虑,所以对于一个状态我们同事考虑两个元素pos的选择。

所以我们在记忆化的时候用f(i,lim1,lim2,lim3)表示当前第i位,n,m,k是否有限制时的前缀矩阵和。

推到后面你发现推不下去了,所以自然而然加上一个表示i^j>=k的元素的个数。就可以顺利递推了。

可是为什么网上这么多人选择直接递推呢?因为不同于一般的数位DP,这里同一个限制状态可能被多个不同的转移调用,不记录有限制的时候的状态会T,所以这次我们要记忆转移的所有状态,包括限制情况。这么一来,所有状态都被记录,没有任何必要用记忆化了,不过这种要记录限制的情况只能对一个数使用,因为不同的数限制不同。

不过我还是写了记忆化来填补一下网上题解没有记忆化的遗憾嘛。

代码

两个DP可以通过传值变量合成一个,写成两个方便理解,上面是个数计算,下面是和的计算。

注意每次分解数组要清空。

有很多爆int的地方要小心。

#include<cmath>
#include<queue>
#include<cstdio>
#include<vector>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long LL;
const int maxn=65;
int T,p;
LL n,m,k;
LL f[maxn][2][2][2],g[maxn][2][2][2];
int z1[maxn],z2[maxn],z3[maxn];
LL dp1(int pos,bool lim
4000
1,bool lim2,bool lim3)
{
if(pos<1)return 1;
if(g[pos][lim1][lim2][lim3]!=-1)return g[pos][lim1][lim2][lim3];
int up1=lim1?z1[pos]:1;
int up2=lim2?z2[pos]:1;
int down=lim3?z3[pos]:0;
bool l1,l2,l3;
LL ret=0;
for(int a=0;a<=up1;a++)
{
for(int b=0;b<=up2;b++)
{
if((a^b)<down)continue;
l1=lim1 && a==up1;
l2=lim2 && b==up2;
l3=lim3 && (a^b)==down;
ret=(ret+dp1(pos-1,l1,l2,l3))%p;
}
}
return g[pos][lim1][lim2][lim3]=ret;
}
LL dp2(int pos,bool lim1,bool lim2,bool lim3)
{
if(pos<1)return 0;
if(f[pos][lim1][lim2][lim3]!=-1)return f[pos][lim1][lim2][lim3];;
int up1=lim1?z1[pos]:1;
int up2=lim2?z2[pos]:1;
int down=lim3?z3[pos]:0;
bool l1,l2,l3;
LL ret=0;
for(int a=0;a<=up1;a++)
{
for(int b=0;b<=up2;b++)
{
if((a^b)<down)continue;
l1=lim1 && a==up1;
l2=lim2 && b==up2;
l3=lim3 && (a^b)==down;
ret=(ret+dp2(pos-1,l1,l2,l3))%p;
if(a^b)ret=( ret + ( ( 1ll << ( pos-1 ) ) %p ) * dp1(pos-1,l1,l2,l3) )%p;
}
}
return f[pos][lim1][lim2][lim3]=ret;
}
void fenjie(LL x,int *z)
{
int sz=0;
do{
z[++sz]=x&1;
x>>=1;
}while(x);
}
void calc()
{
scanf("%lld%lld%lld%d",&n,&m,&k,&p);
n--,m--;
memset(f,-1,sizeof(f));
memset(g,-1,sizeof(g));
memset(z1,0,sizeof(z1));
memset(z2,0,sizeof(z2));
memset(z3,0,sizeof(z3));
fenjie(n,z1);
fenjie(m,z2);
fenjie(k,z3);
printf("%lld\n",(dp2(61,1,1,1)-1ll*k%p*dp1(61,1,1,1)%p+p)%p);
}
int main()
{
scanf("%d",&T);
while(T--)
calc();
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: