毒瘤數位DP練習及題解
前言
巫蠱偶(盒飯)大佬整理了一套毒瘤數位DP練習。因為課件出了點鍋所以不放上來了,這裡寫一寫這套練習的部分題解
T1 Blinker的仰慕者(BZOJ2757)
Description
Blinker 有非常多的仰慕者,他給每個仰慕者一個正整數編號。而且這些編號還隱藏著特殊的意義,即編號的各位數字之積表示這名仰慕者對Blinker的重要度。 現在Blinker想知道編號介於某兩個值A,B之間,且重要度為某個定值K的仰慕者編號和。
Input
輸入的第一行是一個整數N,表示Blinker想知道的資訊個數。
接下來的N行,每行有三個數,A,B,K。表示 Blinker想知道編號介於A和B之間的,
重要度為K的仰慕者的編號和。
Output
輸出N行,每行輸出介於A和B之間,重要度為 K的仰慕者編號和。結果可能很大,
模上20120427。
Sample Input
3
1 14 4
1 30 4
10 60 5
0<=K<=10^18
Sample Output
18
40
66
【樣例解釋】
第一組樣例中,在 1到14之間各位數字之積等於 4的有 4和 14,故編號和為18。
【資料範圍】
對於20%的資料,保證:2<=A<=B<=1000000000,1<=N<=30
對於50%的資料,保證:2<=A<=B<=1000000000000000000,1<=N<=30
對於100%的資料,保證:2<=A<=B<=1000000000000000000,1<=N<=5000
題解
這題特別毒瘤,我做了6個小時左右。首先可以發現一般情況下所有的合法K都只能有2,3,5,7四個質因數,那我們乾脆直接數位DP2,3,5,7的個數就可以了,有一種情況是K=0,那麼還要特判掉再跑一次其它的數位DP,還有詢問次數比較多,不能每次清空陣列去做,所以要把狀態改成能適應多種情況的。(兩遍數位DP寫了3.4K程式碼)結果發現MLE了。
我改成了map,然後在WA了6次之後變成TLE了。。。。
於是我只能手寫雜湊…..寫到心態爆炸。
不過我亂搞了一個雜湊函式實測效果還是不錯的,跑得挺快。
\#include<cstdio> #include<algorithm> using namespace std; const int MOD=20120427; typedef long long ll; pair<int,int> f[22][68005]; bool mark[22][68005]; int num[22],p[22]; class Hash{ ll hash[(int)1e6+5]; int s[(int)1e6+5]; int mod; public: Hash(){ mod=999983; for(int i=0;i<mod;++i) hash[i]=0; } void insert(ll key){ int d=key%mod; while(hash[d]!=0){ d=d+2; d=d>=mod?d-mod:d; } hash[d]=key; } void build(){ s[0]=hash[0]>0; for(int i=1;i<mod;++i) s[i]=s[i-1]+(hash[i]>0); } int query(ll key){ int d=key%mod; while(hash[d]!=key){ d=d+2; d=d>=mod?d-mod:d; } return s[d]; } }mp; void mod(int& x){ x>=MOD?x-=MOD:0; } pair<int,int> opera(pair<int,int> b,int x,int key){ return make_pair(b.first,((ll)b.first*key%MOD*p[x-1]+b.second)%MOD); } pair<int,int> operator+(pair<int,int> a,pair<int,int> b){ mod(a.first+=b.first); mod(a.second+=b.second); return a; } pair<int,int> solve(int x,ll key,int flag){ if(x==0){ if(key==1) return make_pair(1,0); else return make_pair(0,0); } if(flag==0){ int dkey=mp.query(key); if(mark[x][dkey]) return f[x][dkey]; mark[x][dkey]=true; pair<int,int> ret=make_pair(0,0); for(int i=1;i<=9;++i) if(key%i==0) ret=ret+opera(solve(x-1,key/i,0),x,i); f[x][dkey]=ret; return ret; }else if(flag==1){ pair<int,int> ret=make_pair(0,0); for(int i=1;i<num[x];++i) if(key%i==0) ret=ret+opera(solve(x-1,key/i,0),x,i); if(num[x]>0&&key%num[x]==0) ret=ret+opera(solve(x-1,key/num[x],1),x,num[x]); return ret; }else{ pair<int,int> ret=make_pair(0,0); for(int i=1;i<=9;++i) if(key%i==0) ret=ret+opera(solve(x-1,key/i,0),x,i); ret=ret+opera(solve(x-1,key,2),x,0); return ret; } } pair<int,int> g[20][2]; bool mark2[20][2]; pair<int,int> solve2(int x,int t,int flag){ if(x==0) return make_pair(t,0); if(flag==0){ if(mark2[x][t]) return g[x][t]; mark2[x][t]=true; pair<int,int> ret=make_pair(0,0); for(int i=1;i<=9;++i) ret=ret+opera(solve2(x-1,t,0),x,i); ret=ret+opera(solve2(x-1,1,0),x,0); g[x][t]=ret; return ret; }else if(flag==1){ pair<int,int> ret=make_pair(0,0); for(int i=1;i<num[x];++i) ret=ret+opera(solve2(x-1,t,0),x,i); if(num[x]>0){ ret=ret+opera(solve2(x-1,t,1),x,num[x]); ret=ret+opera(solve2(x-1,1,0),x,0); }else ret=ret+opera(solve2(x-1,1,1),x,0); return ret; }else{ pair<int,int> ret=make_pair(0,0); for(int i=1;i<=9;++i) ret=ret+opera(solve2(x-1,t,0),x,i); ret=ret+opera(solve2(x-1,t,2),x,0); return ret; } } ll k; int work(ll data){ if(data==0) return 0; int top=0; while(data>0) num[++top]=data%10,data/=10; pair<int,int> ret=make_pair(0,0); if(k%num[top]==0) ret=ret+opera(solve(top-1,k/num[top],1),top,num[top]); for(int i=1;i<num[top];++i) if(k%i==0) ret=ret+opera(solve(top-1,k/i,0),top,i); ret=ret+opera(solve(top-1,k,2),top,0); return ret.second; } int work2(ll data){ if(data==0) return 0; int top=0; while(data>0) num[++top]=data%10,data/=10; pair<int,int> ret=opera(solve2(top-1,0,1),top,num[top]); for(int i=1;i<num[top];++i) ret=ret+opera(solve2(top-1,0,0),top,i); ret=ret+opera(solve2(top-1,0,2),top,0); return ret.second; } int main(){ int cnt=0; for(ll i=1;i<=1e18;i*=2) for(ll j=i;j<=1e18;j*=3) for(ll k=j;k<=1e18;k*=5) for(ll t=k;t<=1e18;t*=7) mp.insert(t); mp.build(); int T; scanf("%d",&T); p[0]=1; for(int i=1;i<=19;++i) p[i]=(ll)p[i-1]*10%MOD; while(T--){ ll l,r; scanf("%lld%lld%lld",&l,&r,&k); if(k==0) printf("%d\n",((work2(r)-work2(l-1))%MOD+MOD)%MOD); else{ ll key=k; while(key%2==0) key/=2; while(key%3==0) key/=3; while(key%5==0) key/=5; while(key%7==0) key/=7; if(key>1){ puts("0"); continue; } printf("%d\n",((work(r)-work(l-1))%MOD+MOD)%MOD); } } return 0; }
T2 數數(SCOI2013/洛谷P3281)
題目描述
Fish 是一條生活在海里的魚,有一天他很無聊,就開始數數玩。他數數玩的具體規則是:
確定數數的進位制B
確定一個數數的區間$[L, R]$
對於$[L, R]$ 間的每一個數,把該數視為一個字串,列出該字串的每一個(連續的)子串對應的B進位制數的值。
對所有列出的數求和。現在Fish 數了一遍數,但是不確定自己的結果是否正確了。由於$[L, R]$ 較大,他沒有多餘精力去驗證是否正確,你能寫一個程式來幫他驗證嗎?
輸入格式:
輸入包含三行
第一行僅有一個數B,表示數數的進位制。
第二行有N +1 個數,第一個數為N,表示數L 在B 進位制下的長度為N,接下里的N個數從高位到低位的表示數L 的具體每一位。
第三行有M+ 1 個數,第一個數為M,表示數R 在B 進位制下的長度為M,接下里的M個數從高位到低位的表示數R 的具體每一位。
輸出格式:
輸出僅一行,即按照Fish 數數規則的結果,結果用10 進製表示,由於該數可能很大,輸出該數模上20130427的模數。
輸入輸出樣例
輸入樣例:
10
3 1 0 3
3 1 0 3
輸出樣例:
120
【樣例解釋】
僅有數103,該數的所有子串包括1, 10, 103, 0, 03, 3,其和為120。
【資料範圍與約定】
20% 資料,0 <= R <= L <= 10^5。
50% 資料,2 <= B <= 1000,1 <= N,M <= 1000。
100% 資料,2 <= B <= 10^5,1 <= N,M <= 10^5。
題解
這題比上一題稍微好一點,至少沒有MLE,兩遍DP,手寫hash這種操作。。。。這題我們可以記錄一段數字中以最高位為結束的所有子串的資訊來進行轉移。我們只需要記錄4個資訊:以最高位為結束的子串的個數,這些子串長度的權和(因為下一位加入的時候需要知道它被加了幾次),這些子串的值的和,這段數字中所有子串的和。
然後考慮一下轉移,由於B比較大,所以要用字首和進行轉移,轉移的式子比較複雜,不過程式碼確實短。。。
\#include<cstdio> #include<algorithm> using namespace std; const int MOD=20130427; typedef long long ll; struct Key{ int x,cnt,sum,qsum; }; const int MAX_N=5+1e5; int B,num[MAX_N]; Key f[MAX_N]; bool mark[MAX_N]; Key opera(Key key,int l,int r){ key.cnt=((ll)key.cnt*B+key.x)%MOD; key.sum=((ll)(l+r)*(r-l+1)/2%MOD*key.cnt+(ll)key.sum*(r-l+1))%MOD; key.qsum=((ll)key.qsum*(r-l+1)+key.sum)%MOD; key.cnt=(ll)key.cnt*(r-l+1)%MOD; key.x=(ll)key.x*(r-l+1)%MOD; return key; } Key operator+(Key x,Key y){ return (Key){(x.x+y.x)%MOD,(x.cnt+y.cnt)%MOD,(x.sum+y.sum) %MOD,(x.qsum+y.qsum)%MOD}; } Key solve(int x,int flag){ if(x==1){ if(flag==1) return (Key){num[x]+1,num[x]+1 ,(ll)(num[x]+1)*num[x]/2%MOD,(ll)(num[x]+1)*num[x]/2%MOD}; return (Key){B,B,(ll)B*(B-1)/2%MOD,(ll)B*(B-1)/2%MOD}; } if(flag==0){ if(mark[x]) return f[x]; mark[x]=true; f[x]=opera(solve(x-1,0),0,B-1); return f[x]; }else if(flag==1){ Key ret=opera(solve(x-1,1),num[x],num[x]); if(num[x]>0) ret=ret+opera(solve(x-1,0),0,num[x]-1); return ret; }else{ Key key=solve(x-1,0); // printf("<%d %d %d %d>",key.x,key.cnt,key.sum,key.qsum); key=opera(key,1,B-1); // printf("<%d %d %d %d>",key.x,key.cnt,key.sum,key.qsum); return key+solve(x-1,2); } } int work(int n){ Key ret=(Key){0,0,0}; if(n==1) return (ll)num[n]*(num[n]+1)/2%MOD; ret=opera(solve(n-1,1),num[n],num[n]); // printf("(%d %d %d %d)",ret.x,ret.cnt,ret.sum,ret.qsum); if(num[n]>1) ret=ret+opera(solve(n-1,0),1,num[n]-1); ret=ret+solve(n-1,2); Key key=solve(n-1,2); // printf("(%d %d %d %d)",key.x,key.cnt,key.sum,key.qsum); return ret.qsum; } int main(){ scanf("%d",&B); int n; scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&num[n-i+1]); int ans=0; if(n==1&&num[1]==1) ; else{ num[1]-=1; for(int i=1;i<=n;++i) if(num[i]<0){ --num[i+1]; num[i]+=B; } while(num[n]==0) --n; ans=MOD-work(n); } scanf("%d",&n); for(int i=1;i<=n;++i) scanf("%d",&num[n-i+1]); ans=(ans+work(n))%MOD; printf("%d",(ans+MOD)%MOD); return 0; }