HDU 5803 Zhu’s Math Problem 2016多校第六场1011 数位dp
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=5803
题意:在a<=A,b<=B,c<=C,d<=D下,找出符合a+c>b+d 并且 a+d≥b+c的所有(a,b,c,d)。
题解:由于A,B,C,D范围太大,可直接考虑数位dp。为什么说可以直接考虑数位dp呢?当数位dp做多的时候,会发现数位dp就一个套路。就是在搜索的时候是一位一位地考虑,那么当我们得到两个k长度的数A,B时,如果A,B状态相同,就可以只搜一个,另外一个直接回溯。为什么这样?我们考虑以什么作为状态的时候,就直接考虑相同长度的两个数A,B,如果A,B某一种特征相同时,那么剩下位数添加数字能得到相同的结果,我们就把这个特征作为状态。举个例子,比如说我们要找区间内不包含连续49的数字的个数。当我们得到同数位的468和247时,显然,468和247再增加一位时,468和247都可以在下一位添加0~9任意数字(非限制情况)。
回到本题,我们考虑上述条件的时候,令f1=a+c-b-d,f2=a+d-b-c,我们知道当相同数位不同数字的f1,f2相同时,那么这两个状态就是等价的。那么直接存这个状态是不行,仔细考虑一下,会发现在做减法时,只有相邻的两位才会才产生影响,那么f1,f2只要存下前一位的结果就行。用二进制优化(黑科技啊)。
代码如下:
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
int T;
ll p[4];
int hi[3]={-2,0,2};
int dp[65][1<<4][4][4];
inline void modify(int& x,int y)
{
x+=y;
if(x>=mod) x-=mod;
}
inline int carry(int x,int f)
{
if(f==3) return 3;
int res=x+hi[f];
if(res<-1) return -1;
if(res>1) return 3;
return res+1;
}
int dfs(int pos,int limit,int f1,int f2)
{
if(pos<0) return f1>=2&&f2>=1;
if(dp[pos][limit][f1][f2]!=-1) return dp[pos][limit][f1][f2];
int ed[4];
int& ans=dp[pos][limit][f1][f2]=0;
for(int i=0;i<4;i++) ed[i]=limit>>i&1?1:p[i]>>pos&1;
for(int i=0;i<=ed[0];i++)
for(int j=0;j<=ed[1];j++)
for(int k=0;k<=ed[2];k++)
for(int z=0;z<=ed[3];z++)
{
int newf1=carry(i+k-j-z,f1),newf2=carry(i+z-j-k,f2),newlimit=limit;
if(newf1==-1||newf2==-1) continue;
if(i<ed[0]) newlimit|=1;
if(j<ed[1]) newlimit|=2;
if(k<ed[2]) newlimit|=4;
if(z<ed[3]) newlimit|=8;
modify(ans,dfs(pos-1,newlimit,newf1,newf2));
}
return ans;
}
int main()
{
scanf("%d",&T);
while(T--)
{
memset(dp,-1,sizeof(dp));
scanf("%I64d%I64d%I64d%I64d",&p[0],&p[1],&p[2],&p[3]);
printf("%d\n",dfs(61,0,1,1));
}
return 0;
}