bzoj3326[SCOI2013]数数

这题我拖了两个月才调出来
实际上是推倒重写的,两个月前写的是啥我已经完全看不懂了

Fish 是一条生活在海里的鱼,有一天他很无聊,就开始数数玩。
他数数玩的具体规则是:
1. 确定数数的进制B
2. 确定一个数数的区间[L, R]
3. 对于[L, R] 间的每一个数,把该数视为一个字符串,列出该字符串的每一个(连续的)子串对应的B进制数的值。
4. 对所有列出的数求和.
现在Fish 数了一遍数,但是不确定自己的结果是否正确了。由于[L, R] 较大,他没有多余精力去验证是否正确,你能写一个程序来帮他验证吗?

\(B,N,M<=10^5\)

把一个数字的所有子串对应的B进制数的值的和称作这个数字的价值.

首先题目要求的东西有可减性,因此按照数位DP的套路我们把问题转化为求[1,L-1]的答案和[1,R]的答案.也就是说,问题可以转化为给出一个x,求所有严格小于x的数字的价值之和.

按照数位DP的套路,我们要考虑位数小于x的数字和位数等于x的数字.假如x有n位,那么就是求所有的1位数,2位数....n-1位数的价值之和.

也就是说我们首先要预处理一个数组f[i],表示所有的无前导零i位数的价值之和.比如f[2]就是10,11,12,13...98,99的价值之和.

考虑f[i]如何转移.我们分别考虑第i位和前i-1位对答案的贡献.

写不下去了....我把代码重写一遍,在里面加注释好了...(这个代码虽然加了很多注释,但它是能A的.看到我交了这道题10K代码的人一定会觉得我是个智障....)

#include<cstdio>
typedef long long ll;//全部使用long long以避免int溢出.
const ll mod=20130427,inv2=10065214;
const int maxn=100005;
ll a[maxn];ll len;//a[]:存储输入的L,R len:表示有多少位
ll base;//base:进制
void input(){
  scanf("%lld",&len);
  for(int i=len;i>=1;--i)scanf("%lld",&a[i]);
}
void plus(){
//work函数处理的是所有严格小于a[]的数字的价值之和
//所以处理R的时候需要把R+=1,处理严格小于R+1的价值之和
  a[1]++;
  for(int i=1;i<=len;++i){
    if(a[i]>=base){
      a[i]-=base;a[i+1]++;
      if(i==len)++len;
    }
  }
}
ll basesum;//basesum=1+2+3+...+(base-1)
ll pwr[maxn],pwrs[maxn];//pwr[i]:base^i pwrs[i]:1+base+base^2+...+base^(i-1)+base^i
ll f[maxn],g[maxn],h[maxn];
//f[i]:所有的i位数(允许前导0)的价值之和 f[2]对应着 00,01,02,03...98,99的价值之和
//g[i]:所有的i位数(允许前导0)的价值之和 g[2]对应着 10,11,12,13...98,99的价值之和
//h[i]:所有的i位数(允许前导0)的数值之和 h[2]对应着 00+01+02+03+...+99=(0+99)*100/2
void init(){
  basesum=(base*(base-1)/2)%mod;
  pwr[0]=1;
  for(int i=1;i<maxn;++i)pwr[i]=pwr[i-1]*base%mod;
  pwrs[0]=pwr[0];
  for(int i=1;i<maxn;++i)pwrs[i]=(pwrs[i-1]+pwr[i])%mod;
  for(int i=1;i<maxn;++i)h[i]=(0+(pwr[i]-1))*pwr[i]%mod*inv2%mod;
  f[0]=g[0]=0;
  ll tmp=0;
  for(int i=1;i<maxn;++i){
    f[i]=basesum*pwr[i-1]%mod*pwrs[i-1]%mod;//最高位(第i位)对答案的贡献
    //最高位取某个确定值w(例如1,2,3)时,一共有base^(i-1)个串
    //在一个串中,包含最高位的子串有i个,长度分别为1,2,3...i,最高位的大小分别为w*base^0,w*base^1,w*base^2...w*base^(i-1)
    //把结果加起来得到basesum*pwr[i-1]*pwrs[i-1]
    f[i]=(f[i]+base*f[i-1]%mod)%mod;//后(i-1)位在不包含第i位的所有子串中的贡献.
    //如果第i位确定,后(i-1)位有base^(i-1)种情况,它们的贡献就是f[i-1].
    //第i位有base种选择,所以乘上base
    tmp=(tmp*base+h[i-1])%mod;
    f[i]=(f[i]+tmp*base)%mod;//后(i-1)位在包含第i位的所有子串中的贡献
    //如果一个子串包含第i位,这个子串在后(i-1)位中必然包含的是一个前缀(从第i-1位开始的连续几位)
     //我们枚举在后(i-1)位包含的前缀的长度len,那么还有(i-1)-len位是没有确定的.而且第i位也是没有确定的.
    //对于某个长度为len的前缀,它对答案贡献base^(i-len)次
    //那么我们对所有长度为len的前缀一并计算
    //求出所有长度为len的前缀的数值之和h[i](也就是长度为len的所有允许前导零的数字的和),再乘上pwr[i-len]
    //直接做是平方的复杂度,会T
    //仔细观察我们求的式子,f[i]的时候我们加上了h[1]*pwr[i-1]+h[2]*pwr[i-2]...+h[i-1]*pwr[1]
    //f[i+1]的时候我们加上了h[1]*pwr[(i+1)-1]+h[2]*pwr[(i+1)-2]+...+h[i-1]*pwr[2]+h[i]*pwr[1]
    //把f[i]的式子乘上base,再加上最后一项就能O(1)得到f[i+1]时候的式子.
    //程序中的tmp变量少乘上一个base,在递推g[i]的时候把tmp乘上base-1,递推f[i]时把tmp乘上base就可以了.

    //递推g[i]的时候和f[i]思路类似,把递推f[i]的一些地方从base改为base-1即可(最高位不能是0)
    g[i]=basesum*pwr[i-1]%mod*pwrs[i-1]%mod;
    g[i]=(g[i]+(base-1)*f[i-1]%mod)%mod;
    g[i]=(g[i]+(base-1)*tmp%mod)%mod;
  }
}
ll work(){//严格小于a[]的数字的价值之和
  ll ans=0;
  //统计位数小于len的
  for(int i=1;i<len;++i)ans=(ans+g[i])%mod;

  //统计位数等于len的
  //按照数位DP的套路,我们考虑所有严格小于a[]的数字X.
  //按照数位DP的套路,枚举X中从高到低第一个和a[]不同的位置在哪里
  //然后我们分别考虑这个位置之前/之后的数字对答案的贡献.恰好在这个位置的数字算在这个位置之前进行考虑
  //注意这里选出的子串可能有三种:完全在这个位置之前,完全在这个位置之后,跨越这个位置之前和之后
  //假如从高到低(也就是从第len位到第1位)依次考虑,在第i位出现了第一处不同,那么认为第i位到第len位是"这个位置之前",第1位到第i-1位是"这个位置之后"
  //这里的"先后"是按照从高到低考虑的顺序先后
  
  //完全在这个位置之前的串的贡献之和
  ll pre=0,pre2=0;
  for(int i=len;i>=1;--i){//从高到低枚举第一个不同的位置
    //如果不同的位置是最高位那么可以在[1,a[i]-1]取值,如果不是最高位那么可以在[0,a[i]-1]取值
    //直接枚举这个取值会T,但我们可以先通过直接枚举这个取值列出一个式子,然后对求解的过程进行优化
    //不同的位置在第i位则第i+1位到第len位都已经确定,完全包含于[i+1,len]的所有子串之和用pre2表示
    //pre表示在第i位结尾的所有子串之和.i变化1的时候,pre2要加上pre
    //而pre如何变化?考虑从在第i+1位结尾的所有子串变化到在第i位结尾的所有子串,那么原先的每个子串后面都加了一个数字
    //所以原先每个子串的价值都乘上base.同时我们又新加了一个数字a[i],这个数字在len-(i-1)个串中出现.
    //因此pre=pre*base+a[i]*(len-(i-1))
    //枚举不同的位置上的一个小于a[i]的数字x时,我们假设第i位上是j,也可以按照上面的方法算出第i位为j时,所有在第i位结尾的串的价值之和
    //注意后面的i-1位没有确定,有pwr[i-1]种可能,所以每个串要统计pwr[i-1]次
    //由此得到下面的naive算法
    // int lo=(i==len)?1:0;
    // for(ll j=lo;j<a[i];++j){
    //   ll tmppre=(pre*base+j*(len-(i-1)))%mod;
    //   ll tmppre2=(pre2+tmppre)%mod;
    //   ans=(ans+tmppre2*pwr[i-1])%mod;
    // }
    //然后我们发现这个naive算法中式子的每一部分可以拆开算,不需要枚举j
    int lo=(i==len)?1:0;
    ans=(ans+(pre*base+pre2)%mod*(a[i]-lo)%mod*pwr[i-1]%mod)%mod;
    ans=(ans+(lo+a[i]-1)*(a[i]-lo)/2%mod*(len-(i-1))%mod*pwr[i-1]%mod)%mod;
    //更新pre,pre2.注意先更新pre再更新pre2
    pre=(pre*base+a[i]*(len-(i-1)))%mod;
    pre2=(pre2+pre)%mod;
  }
  
  //完全在这个位置之后的串的贡献之和
  for(int i=len;i>=1;--i){//从高到低枚举第一个不同的位置
    int lo=(i==len)?1:0;
    //假如第i位是第一个不同的位置,那么这一位有a[i]-1种或a[i]种选择
    //这一位后面i-1位任意选择,第i位确定之后有pwr[i-1]个数字,其贡献为f[i-1]
    //f[i-1]乘上第i位的方案数即可
    ans=(ans+(a[i]-lo)*f[i-1]%mod)%mod;
  }
  
  //跨越第一个不同的位置的串的贡献中,这个位置之后的数字产生的贡献
  //认为第一个不同的位置本身不属于"这个位置之后"
  ll tmp=0;
  for(int i=1;i<=len;++i){//从低到高枚举第一个不同的位置
    //注意由于接下来的计算方式,这里i的枚举顺序只能是从1到len
    //首先我们发现,第i位的具体取值对第1到第i-1位的贡献没有影响.无论第i位取值是什么,后面i-1位的贡献之和是一样的.那么我们只需要算出第i位在某一种取值时后面i-1位的贡献之和,然后乘上第i位的选取方案数a[i]或者a[i]-1即可
    //如果一个子串跨越两侧,那么它包含第i位,并且在后(i-1)位中必然包含的是一个前缀(从第i-1位开始的连续几位)
    //我们枚举在后(i-1)位包含的前缀的长度len,那么还有(i-1)-len位是没有确定的.
    //对于某个长度为len的前缀,它对答案贡献base^(i-len)次
    //那么我们对所有长度为len的前缀一并计算
    //求出所有长度为len的前缀的数值之和h[i](也就是长度为len的所有允许前导零的数字的和),再乘上pwr[i-len]
    //直接做是平方的复杂度,会T
    //仔细观察我们的式子(先不考虑乘第i位的方案数)
    //枚举到i的时候加上h[1]*pwr[i-1-1]+h[2]*pwr[i-1-2].....+h[i-1]*pwr[0]
    //枚举到i+1,加上h[1]*pwr[i-1]+h[2]*pwr[i-2]+...+h[i-1]*pwr[1]+h[i]*pwr[0]
    //因此i+1的时候把i的式子乘上一个base,再加上一项h[i]即可
    //需要从i的式子推到i+1的式子,所以必须从小到大枚举i
    //i的时候加的是h[i-1]
    //注意这个跨越两侧的串在第i位到第len位之间的端点有len-i+1种选择,要乘上去
    int lo=(i==len)?1:0;
    tmp=(tmp*base+h[i-1])%mod;
    ans=(ans+tmp*(a[i]-lo)%mod*(len-i+1)%mod)%mod;
  }
  pre=0;
  //跨越这个位置的串的贡献中,这个位置之前的数字产生的贡献
  for(int i=len;i>=1;--i){
    int lo=(i==len)?1:0;
    //不妨先考虑naive做法,枚举这个不同的位置的取值j,从0/1到a[i]-1
    //如果一个串"跨越这个位置",那么要求这个串必须包含第i-1位的数字,而且这个串在第i位到第len位的部分是从第i位开始的连续一段
    //也就是说,"跨越"的串在这个位置之前的部分以第i位结尾
    //考虑某个在[i,len]区间中开始,并且在第i位结尾的串.它若在这里对答案产生贡献,必然是出现在了某个跨越第i位的串,也就是向第i-1位的方向又延伸了几位.
    //如果这个在第i位结尾的串原先的数值是x,那么向i-1位方向延伸一位之后,它在整个子串中代表的数值是x*base.延伸2位,代表的取值是x*base^2.
    //最多可以延伸i-1位,那么总的贡献就是x*(pwr[1]+pwr[2]+...+pwr[i-1])
    //也就是x*(pwrs[i-1]-1),因为pwrs[i-1]里面算了pwr[0]所以要减1
    //再注意到后面i-1位可以随意取值,有pwr[i-1]种的方案
    //所以要用在第i位结尾的所有串的价值之和乘(pwrs[i-1]-1)再乘pwr[i-1]
    //由此得到这个naive算法:
    // for(ll j=lo;j<a[i];++j){
    //   ll tmppre=(pre*base%mod+j*(len-i+1)%mod)%mod;
    //   ans=(ans+tmppre*(pwrs[i-1]-1)%mod*pwr[i-1]%mod)%mod;
    // }
    //然后这个naive算法的式子里可以每一项拆开算,就不用枚举j了.
    ans=(ans+pre*base%mod*(pwrs[i-1]-1)%mod*pwr[i-1]%mod*(a[i]-lo)%mod)%mod;
    ans=(ans+(a[i]-lo)*(a[i]-1+lo)/2%mod*(len-i+1)%mod*(pwrs[i-1]-1)%mod*pwr[i-1]%mod)%mod;
    pre=(pre*base%mod+a[i]*(len-i+1)%mod)%mod;
  }
  return ans;
}
int main(){
  scanf("%lld",&base);
  init();
  input();
  ll ans1=work();//ans1:严格小于L的数字的价值之和
  input();plus();
  ll ans2=work();//ans2:严格小于R+1的数字的价值之和
  printf("%lld\n",(ans2-ans1+mod)%mod);
  return 0;
}

posted @ 2017-04-29 14:20  liu_runda  阅读(218)  评论(0编辑  收藏  举报
偶然想到可以用这样的字体藏一点想说的话,可是并没有什么想说的. 现在有了:文化课好难