数位DP
刚开始把矩阵打出来,发现有规律,然后就没有然后了。。。(网上似乎有大神用规律做出来了?)
讲一讲数位DP的做法:
记 aij=i xor j−k , 用[…]表示值为0或1的真假表达式
转化一下,原题变成求一个式子:
∑n−1i=0∑m−1j=0aij[aij>0]−k∗∑n−1i=0∑m−1j=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();
}