题意:给定n, m, k, p, 求出
\[
\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} max(i\oplus j-k)\\
最大数据范围
T = 5000, n \leq 10 ^ {18} , m \leq 10 ^ {18} , k \leq 10 ^ {18} , p \leq 10 ^ 9
\]
题解:转化成求\[\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} i\oplus j*\epsilon(i\oplus j-k) – k*\sum_{i=0}^{n-1}\sum_{j=0}^{m-1} 1*\epsilon(i\oplus j-k)\],其中\[\epsilon(x)当x> 0时为1,否则为0\]. 这样的话可以直接数位dp计算了,有两个范围和一个约束,要计算的量也有两个,刚学到的kuangbin的板子十分的好用!!!这个甩锅给-1位的写法真的太简洁了。
error:第一个是搞错了当前位的贡献,只乘上了二进制位的权值,没有乘后面的种类数。第二个是爆int,日常long long错误
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,m,k,p;
ll dp1[70][2][2][2],dp2[70][2][2][2];
int nb[70],mb[70],kb[70];
ll pre[70];
void add(ll& a,ll b){
a=(a+b>=p?a+b-p:a+b);
}
ll dfs1(int pos,int n1,int m1,int k1,ll& w){
if(pos==-1){
if(k1==0&&n1==0&&m1==0) return 1;
else return 0;
}
if(dp1[pos][n1][m1][k1]!=-1){
w=dp2[pos][n1][m1][k1];
return dp1[pos][n1][m1][k1];
}
int endn=(n1?nb[pos]:1);
int endm=(m1?mb[pos]:1);
ll ans=0,tmp=0,pw=0;
for(int i=0;i<=endn;i++)
for(int j=0;j<=endm;j++){
pw=0;
if(k1&&(i^j)<kb[pos]) continue;
tmp=dfs1(pos-1,n1&&i==endn,m1&&j==endm,k1&&(i^j)==kb[pos],pw);
if(tmp>=p) tmp%=p;
if(tmp){
add(ans,tmp);
add(w,(pw+(i^j)*pre[pos]*tmp)%p);
}
}
dp2[pos][n1][m1][k1]=w;
return dp1[pos][n1][m1][k1]=ans;
}
void work(){
for(int i=0;i<=62;i++){
pre[i]=(int)((1LL<<i)%p);
}
memset(dp1,-1,sizeof(dp1));
memset(dp2,-1,sizeof(dp2));
int pos=0;ll tk=k;//error2
while(n||m||k){
nb[pos]=(n&1),mb[pos]=(m&1),kb[pos]=(k&1);
n>>=1,m>>=1,k>>=1;
pos++;
}
ll ans1=0,ans2=0;
ans1=dfs1(pos-1,1,1,1,ans2);
tk%=p;
ans1%=p;
printf("%lld\n",((ans2-tk*ans1%p)%p+p)%p);//error1
}
void bruteforce(){
ll p1=0,p2=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if((i^j)>k){
p1++;
p2+=(i^j);
}
}
}
// cout<<p1%p<<" "<<p2%p<<endl;
}
int main()
{
// freopen("1.txt","r",stdin);
// freopen("2.txt","w",stdout);
int t;
scanf("%d",&t);
while(t--){
scanf("%lld%lld%lld%lld",&n,&m,&k,&p);
//bruteforce();
work();
}
return 0;
}
/*
1
504363800392059286 554192717354508770 21453916680846604 401134357
*/
