训练赛的时候C想了半天没思路后,看了下K,发现了一些规律和性质,然后和qt讨论了一蛤,qt突然发现就是二进制数权值为3次幂的东西,然后就想到数位dp了,结果交上去RE,这不科学啊,拿头RE啊?然后这里改那里改还是re,于是去vj上查了一蛤这题,发现果然是oj的锅,然后发现这题在hihocoder上有,一提交就T了,然后发现原题3s时限,hihocoder上1s时限,被卡常了= =,于是看了网上题解,有一个针对多组数据的小优化。。。加了就过了。
f[1]=1,3*f[n]*f[2*n+1]=(3*f[n]+1)*f[2*n],由于3*f[n]和3*f[n]+1是相邻的自然数,是互质的,那么f[2*n]就只可能是3*f[n],6*f[n],9*f[n]....
而f[2*n]<6*f[n],那么f[2*n]=3*f[n],f[2*n+1]=3*f[n]+1.
这就是一个线段树了,f[n]就是编号为n的节点上的值,比如f[6]就在6号节点上,而6的二进制是110,三进制数是1*3^2+0*3+1=10
1...n中间那么就是二进制要小于等于n的二进制
那么这就对应上数位dp,dp[pos][res]表示从低往高选到pos位余数为res的值,最后吧所有dp[pos][0...k-1]加起来就是答案了。
多组数据的常数优化是,dp[knum][pos][res],然后后面如果出现相同的k,就不用多次更新dp数组了。
#include<bits/stdc++.h>
using namespace std;
long long n;
int k,knum;
int a[100];
long long ans;
int mi[100];
long long dp[5][100][65540];
int dy[5]={3,5,17,257,65537};
inline void prework()
{
scanf("%lld%d",&n,&k);
mi[1]=1;
for(int i=2;i<=70;i++)
mi[i]=(mi[i-1]*3)%k;
}
inline long long dfs(int pos,int res,bool jug)
{
if(pos==0)
{
if(res==0)
return 1ll;
else
return 0ll;
}
if(!jug && dp[knum][pos][res]!=-1) return dp[knum][pos][res];
int r=jug?a[pos]:1ll;
long long sum=0;
int tmp;
for(int i=0;i<=r;i++)
{
tmp=((res-mi[pos]*i)%k+k)%k;
sum+=dfs(pos-1,tmp,jug && (i==r));
}
if(!jug)
dp[knum][pos][res]=sum;
return sum;
}
inline void mainwork()
{
int pos=0;
long long num=n;
while(num)
{
a[++pos]=num%2ll;
num/=2ll;
}
if(k==3) knum=0;
if(k==5) knum=1;
if(k==17) knum=2;
if(k==257) knum=3;
if(k==65537) knum=4;
ans=0;long long tmp;
for(register int i=0;i<k;++i)
{
tmp=dfs(pos,i,1);
if(i==0)
tmp--;
ans^=tmp;
}
}
inline void print()
{
printf("%lld\n",ans);
}
int main()
{
int t;
for(int l=0;l<5;l++)
for(int i=0;i<=63;i++)
for(register int j=0;j<dy[l];++j)
dp[l][i][j]=-1;
scanf("%d",&t);
for(int i=1;i<=t;i++)
{
prework();
mainwork();
print();
}
return 0;
}