【[AHOI2009]同類分佈】
這是一篇有些賴皮的題解
(如果不賴皮的話,bzoj上也是能卡過去的)
首先由於我這個非常\(sb\)的方法複雜度高達\(O(171^4)\),所以面對極限的\(1e18\)的資料實在是卡死了
但是這個時候可以騙一下
一般來說肯定會有一個點的資料到達了\(1e18\),所以我們先將\(1\)到\(1e18\)之間的答案算出來,這樣再去算另一個左邊界的話至少可以節省一半的常數,就算左邊界不是很小也有可能還算點希望
如果左邊界特別小的話,可能就能幸運的卡過去
這道題的左邊界就非常小啊,我估計不超過\(1e6\)
於是就卡過去了
再來看看我這個非常\(sb\)的dp,我覺得可能沒有人這麼寫
我們設\(dp[i][j][s][k]\)表示一個數填到了\(i\)位,最高位填的是\(j\),數位和是\(s\),且這些數中對於某一個數取模得\(k\)的數的個數
至於這個某一個數是什麼,我們當然是要最外面套上一個列舉數位和了
那麼答案很簡單啊,如果我們當前列舉的數位和是\(x\)的話,答案肯定就跟\(dp[][][x][0]\)有關係了
那麼這個方程怎麼轉移呢
顯然有
\[dp[i+1][p][j+p][(p*10^i+k)\%x]=\sum_{t=0}^9dp[i][t][j][k]\]
\(t\)表示上一位填的數,\(i\)是位數,\(p\)是這一位填的數,\(j\)是數位和,\(k\)
同時我們發現好像直接去列舉\(t\)有些奢侈,我們可以直接把\(\sum_{t=0}^9dp[i][t][j][k]\)算好,於是我用\(dp[i][10][j][t]\)來存下來\(\sum_{t=0}^9dp[i][t][j][k]\),這樣就可以優化轉移了
之後就是數位\(dp\)的套路卡上界了,大概就是注意一下卡上界的時候存一下前面的數位和
複雜度大概是\(O((log_{10}n*9)^4)\),確實這是一個很垃圾的複雜度
程式碼
#include<iostream> #include<cstring> #include<cstdio> #define re register #define maxn 172 #define LL long long #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) LL dp[20][11][maxn][maxn]; LL L,R; LL ans; int num[2],a[20][2]; LL base[20]; LL mod; inline LL qm(LL x) {return x>=mod?x-mod:x;}//優化一下取模 inline void spilt(LL x,int pd) { num[pd]=0; while(x) a[++num[pd]][pd]=x%10,x/=10; }//分解數位 inline void work(int x,int Len) { mod=x; memset(dp,0,sizeof(dp)); for(re int i=0;i<=9;++i) dp[1][i][i][qm(i)]+=1,dp[1][10][i][qm(i)]+=1; for(re int i=1;i<Len;++i)//列舉長度 for(re int j=0;j<=min(x,i*9);++j)//列舉數位和 for(re int k=0;k<x;++k)//列舉對當前列舉的數位和x取模後的值 { if(!dp[i][10][j][k]) continue; for(re int p=0;p<=9;++p) dp[i+1][p][j+p][(p*base[i]+k)%x]+=dp[i][10][j][k],dp[i+1][10][j+p][(p*base[i]+k)%x]+=dp[i][10][j][k]; } } inline LL slove(int pd,int x) { LL tot=0; for(re int i=1;i<num[pd];++i) tot+=dp[i][10][x][0]-dp[i][0][x][0];//統計所有位數小於給定數的,注意首位不能填0 for(re int i=1;i<a[num[pd]][pd];++i) tot+=dp[num[pd]][i][x][0];//統計所有位數和給定數相同的,但是最高位小於給定數的 LL now=a[num[pd]][pd],cnt=now; //now表示前面所有的數位和,cnt表示前面的數的值是多少 //(比如說12345,卡到三這一位上,now=1+2=3,cnt=1*10+2*1=12) if(x-now<0) return tot; for(re int i=num[pd]-1;i;--i)//當前不同的那一位,[i+1,num]與x完全相同 { LL t=qm(x-cnt*base[i]%x);//根據算出後面的數位所需要的餘數是多少 for(re int j=0;j<a[i][pd];j++) tot+=dp[i][j][x-now][t]; //當前第i位可以填的數必須要小於給定數當前的這一位,這裡就按照dp的方式來統計答案 now+=a[i][pd]; cnt=cnt*10+a[i][pd]; cnt=qm(cnt); if(x-now<0) break; } return tot; } int main() { scanf("%lld%lld",&L,&R); spilt(L,0),spilt(R+1,1); base[0]=1; for(re int i=1;i<=18;++i) base[i]=base[i-1]*10; if(R==1000000000000000000) { ans+=29410615796612778; for(re int i=1;i<=num[0]*9;++i) work(i,num[0]),ans-=slove(0,i); printf("%lld\n",ans); return 0; }//去掉這個if在bzoj上也能卡過去 for(re int i=1;i<=num[1]*9;++i)//列舉數位和 work(i,num[1]),ans+=slove(1,i)-slove(0,i); printf("%lld\n",ans); return 0; }