UVALive - 7271 hihocoder 1259

15 篇文章 0 订阅

训练赛的时候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;
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值