题解 P3286 【[SCOI2014]方伯伯的商场之旅】

· · 题解

prework不清空,爆零两行泪。。。

神仙数位DP题。

根据题面和数据范围,显然这是一道数位DP题。

但是又不能按照数位DP的常规思路来做。

最开始想到用f[i][j][k]表示填到第i位,第i位为j,是否卡上界的答案。

很快发现这个想法太\texttt{naive}了,因为面对大量的数时,根本不知道到底要合并到哪一堆。

但观察之后我们可以发现一些有用的性质,比如向右填一个数字后,合并点(指最终剩的一堆的位置)只可能向右移动。因为加入的数一定在原来的合并点的右边,向左移动只能使代价更大,但向右移动可能使代价更小。

借助这个性质,我们可以先把所有点合并到最左边,再逐一考虑向右移动。

$f[i][j]$表示填到第$i$位,第$i$位为$j$时的答案。DP方程如下。 $$f[i][j]=j\times(i-1)\times k^{i-1}+\sum\limits_{u=0}^{k-1}{f[i-1][u]}$$ 最后按照数位DP的套路试填即可。 $\texttt{Task2:}$求$1-N$的所有数,全部合并到第$s$位,其中低于$s$位的数字之和要小于高于$s$位(这里包括第$s$位)的数字之和的数的个数。 直接求也不好求,我们直接枚举$s$,再分别枚举高于和低于$s$位的数位之和。 $f[i][l][r][op]$表示填到第$i$位,$l$表示高于或等于$s$位的数字之和,$r$表示低于$s$位的数字之和。$op$表示是否卡上界(数位DP套路)。 然后枚举当前位上的数字直接转移即可。 代码如下,写了两个``namespace``分别对应上面两个``task``,可读性应该是比较高的/cy。 ```cpp #include<bits/stdc++.h> #define int long long #define rep(i,a,b) for(int i=a;i<=b;i++) #define pre(i,a,b) for(int i=a;i>=b;i--) #define N 66 #define M 255 using namespace std; int k,a[N],n,s[N],b[N],c[N]; namespace task1{ int f[N][N]; int solve(){ memset(f,0,sizeof(f)); b[0]=1;rep(i,1,n)b[i]=b[i-1]*k; rep(i,1,n)rep(j,0,k-1){ rep(o,0,k-1)f[i][j]+=f[i-1][o]; f[i][j]+=j*(i-1)*b[i-1]; } int ans=0; rep(i,0,a[n]-1)ans+=f[n][i]; rep(i,1,n)ans+=a[i]*(i-1); pre(i,n-1,1){ rep(j,0,a[i]-1)ans+=f[i][j]; pre(j,n,i+1)ans+=a[j]*(j-1)*a[i]*b[i-1]; } return ans; } } namespace task2{ int f[N][M][M][2]; void prework(int pos){ memset(f,0,sizeof(f)); f[n+1][0][0][1]=1; pre(i,n,1){ if(i>=pos){ rep(l,0,c[n-i]){ rep(j,0,a[i]-1)f[i][l+j][0][0]+=f[i+1][l][0][1]; rep(j,0,k-1)f[i][l+j][0][0]+=f[i+1][l][0][0]; f[i][l+a[i]][0][1]+=f[i+1][l][0][1]; } } else{ rep(l,0,c[n-pos+1])rep(r,0,c[pos-i-1]){ rep(j,0,a[i]-1)f[i][l][r+j][0]+=f[i+1][l][r][1]; rep(j,0,k-1)f[i][l][r+j][0]+=f[i+1][l][r][0]; f[i][l][r+a[i]][1]+=f[i+1][l][r][1]; } } } } int solve(){ int ans=0; rep(i,2,n){ prework(i); rep(l,1,c[n-i+1])rep(r,0,l-1){ ans+=(f[1][l][r][1]+f[1][l][r][0])*(r-l); } } return ans; } } int solve(int x){ n=0;while(x)a[++n]=x%k,x/=k; rep(i,1,k)s[i]=s[i-1]+i; rep(i,1,n)c[i]=c[i-1]+k-1; return task1::solve()+task2::solve(); } signed main(){ int l,r;scanf("%lld%lld%lld",&l,&r,&k); printf("%lld\n",solve(r)-solve(l-1)); return 0; } /* 114514 1919810 10 */ ```