標籤:
DP/整數拆分
整個映射關係可以分解成幾個迴圈(置換群的預備知識?),那麼總行數就等於各個迴圈長度的最小公倍數+1(因為有個第一行的1~N)。那麼有多少種可能的排數就等於問有多少種可能的最小公倍數。
呃現在問題就變成了:給你一個數N,將它分解成幾個數的和,然後找這些數的最小公倍數總共多少種。很明顯又要找質數了>_>。
可以發現只要找迴圈長度(即拆出來的數)是質數的冪的情況就可以了,因為像6=2*3這種情況,我們可以用2和3來代替,又由於對於正整數來說,和$\leq$積,所以所有的非質數冪的情況都可以用質數冪的情況來表示/代替。(取一個6等於取2和3)
這個枚舉總共有多少種分拆方案……我是用DP來實現的(沒辦法,dfs會TLE)
令$f[i][j]$表示用前 i 種質數的冪拼出 j 的方案數,那麼$ans=\sum_{j=1}^n f[tot][j]$ tot為小於等於n的質數的數量。
轉移也很簡單啦~我的方法是從當前節點去更新其他節點的遞推……寫的可能有點奇怪……
f[i][j]可以轉移到:f[i+1][j]和f[i+1][j+k] $(k=prime[i+1]^t)$ 呃……好像說的不太清楚……看我代碼吧>_<不過我開了滾動數組……
1 /************************************************************** 2 Problem: 1025 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:20 ms 7 Memory:828 kb 8 ****************************************************************/ 9 10 //BZOJ 102511 #include<cstdio>12 #include<algorithm>13 #define F(i,j,n) for(int i=j;i<=n;++i)14 using namespace std;15 typedef long long LL;16 const int N=1010;17 int n,prime[N],tot;18 LL f[2][N],ans;19 bool vis[N];20 void getprime(int n){21 F(i,2,n){22 if (!vis[i]) prime[++tot]=i;23 F(j,1,tot){24 if (i*prime[j]>n) break;25 vis[i*prime[j]]=1;26 if (i%prime[j]==0) break;27 }28 }29 }30 int main(){31 scanf("%d",&n);32 getprime(n);33 f[0][0]=1;34 for(int i=0;i<tot;i++){35 int now=i&1;36 F(j,0,n) f[now^1][j]=0;37 F(j,0,n){38 f[now^1][j]+=f[now][j];39 for(int k=prime[i+1];k+j<=n;k*=prime[i+1])40 f[now^1][j+k]+=f[now][j];41 }42 }43 F(j,1,n) ans+=f[tot&1][j];44 printf("%lld\n",ans+1);45 return 0;46 }View Code
P.S.我一開始想的其實是$ans=\sum_{i=1}^tot\sum_{j=1}^n f[i][j]$ 所以轉移的時候就不是從f[i]轉移到f[i+1]了……而是轉移到所有的$f[t][j+k](t>i)$所以時間複雜度更高,後來寫題解的時候才突然想到這個更好理解&好寫的代碼……
1 /************************************************************** 2 Problem: 1025 3 User: Tunix 4 Language: C++ 5 Result: Accepted 6 Time:368 ms 7 Memory:4796 kb 8 ****************************************************************/ 9 10 //BZOJ 102511 #include<cstdio>12 #include<algorithm>13 #define rep(i,n) for(int i=0;i<n;++i)14 #define F(i,j,n) for(int i=j;i<=n;++i)15 using namespace std;16 typedef long long LL;17 const int N=1010;18 int n,prime[N],tot,f[N][N];19 LL ans;20 bool vis[N];21 void getprime(int n){22 F(i,2,n){23 if (!vis[i]) prime[++tot]=i;24 F(j,1,tot){25 if (i*prime[j]>n) break;26 vis[i*prime[j]]=1;27 if (i%prime[j]==0) break;28 }29 }30 }31 int main(){32 scanf("%d",&n);33 getprime(n);34 f[0][0]=1;35 rep(i,tot) F(j,0,n){36 F(t,i+1,tot)37 for(int k=prime[t];j+k<=n;k*=prime[t])38 f[t][j+k]+=f[i][j];39 }40 F(i,1,tot) F(j,1,n) ans+=f[i][j];41 printf("%lld\n",ans+1);42 return 0;43 }View Code(一開始的奇怪做法)
1025: [SCOI2009]遊戲Time Limit: 1 Sec Memory Limit: 162 MB
Submit: 1436 Solved: 900
[Submit][Status][Discuss]Description
windy學會了一種遊戲。對於1到N這N個數字,都有唯一且不同的1到N的數字與之對應。最開始windy把數字按順序1,2,3,……,N寫一排在紙上。然後再在這一排下面寫上它們對應的數字。然後又在新的一排下面寫上它們對應的數字。如此反覆,直到序列再次變為1,2,3,……,N。 如: 1 2 3 4 5 6 對應的關係為 1->2 2->3 3->1 4->5 5->4 6->6 windy的操作如下 1 2 3 4 5 6 2 3 1 5 4 6 3 1 2 4 5 6 1 2 3 5 4 6 2 3 1 4 5 6 3 1 2 5 4 6 1 2 3 4 5 6 這時,我們就有若干排1到N的排列,上例中有7排。現在windy想知道,對於所有可能的對應關係,有多少種可能的排數。
Input
包含一個整數,N。
Output
包含一個整數,可能的排數。
Sample Input【輸入範例一】
3
【輸入範例二】
10
Sample Output【輸出範例一】
3
【輸出範例二】
16HINT
【資料規模和約定】
100%的資料,滿足 1 <= N <= 1000 。
Source[Submit][Status][Discuss]
【BZOJ】【1025】【SCOI2009】遊戲