COCI2015/2016 Contest#6 D 遊戲
COCI2015/2016 Contest#6 D 遊戲
題目描述:
Mirko 和 Slavko 在玩一個遊戲,先由Mirko在1~N中選出幾組互質的數,例如 \(N=5\) 時,Mirko 可以選的組有 \({(1,2),(1,3),(1,4),(1,5),(2,3)\dots}\)。
然後輪到 Slavko,Slavko 的任務就是找到一個整數 \(x(2≤x≤N)\),使得對於每一組數 \((a,b)\) 都滿足以下兩個條件之一
• \(a, b < x\)
• \(a, b \ge x\)
例如,如果 Mirko 選了 \({(1,2),(3,4)}\),那麼x可以=3;
如果 Slavko 找不到滿足條件的 \(x\)
首先,我們預處理出所有互質對,顯然,互質對的個數最多隻有 \(\dfrac{n\times(n-1)}{2}\) 個。我們將互質對 \((i,j)\) 抽象成一條左端點在 \(i\),右端點在 \(j\) 的線段 ,那麼我們要求的就是這麼多條線段,任選其中的幾條構成一條連續的,左端點在 1,右端點在 \(n\) 的線段。考慮使用 dp 解決這個問題。
我們設 \(dp_{i,j}\) 表示前 \(i\) 條線段,構成有效連續部分為 1到 \(j\) 的線段的方案數。容易得到轉移式
\[dp_{i,R_i}=\sum_{k=L_i}^{R_i}{dp_{i-1.k}} \] 其中,\(L_i,R_i\)
的點都必須在 \(i\) 轉移之前轉移,所以我們得將所有線段按右端點排序。但是隻有這個式子是不對的,對於有效連續部分的右段在 \([1,L_i-1]\) 區間內的這些線段的方案數也要轉移,而且轉移後的有效連續部分的右端不變。相當於 \(i\) 這條線段對這種方案沒有任何貢獻。但它們的方案是不同的,因為 \(i\) 被選了。所以還有一條式子:
\[dp_{i,j}=dp_{i,j}+dp_{i-1,j}\;(j<L_i) \] 接下來按照這些式子轉移就行了。
轉移部分的程式碼如下:
for(int i=1;i<=cnt;++i)
{
for(int j=1;j<a[i].l;++j)
dp[i][j]=dp[i-1][j];
for(int j=a[i].l;j<=n;++j)
dp[i][a[i].r]=(dp[i-1][j]+dp[i][a[i].r])%MOD;
for(int j=1;j<=n;++j)
dp[i][j]=(dp[i-1][j]+dp[i][j])%MOD;
}
全部程式碼如下:
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const ll MOD = 1e9;
int n;
int gcd(int x,int y)
{
if(y==0) return x;
return gcd(y,x%y);
}
struct line
{
int l,r;
bool operator < (const line &x)const
{
return r<x.r;
}
}a[405];
ll dp[405][25];
int main()
{
scanf("%d",&n);
int cnt=0;
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j)
if(gcd(i,j)==1) a[++cnt]=line{i,j};
sort(a+1,a+1+cnt);
dp[0][1]=1;
for(int i=1;i<=cnt;++i)
{
for(int j=1;j<a[i].l;++j)
dp[i][j]=dp[i-1][j];
for(int j=a[i].l;j<=a[i].r;++j)
dp[i][a[i].r]=(dp[i-1][j]+dp[i][a[i].r])%MOD;
for(int j=1;j<=n;++j)
dp[i][j]=(dp[i-1][j]+dp[i][j])%MOD;
}
printf("%lld",dp[cnt][n]%MOD);
return 0;
}
總結:dp的階段劃分一定要清楚明白,如這題劃分成不選,選了有貢獻,選了無貢獻。如果不劃分清楚明白的話很可能會把選了無貢獻這部分忘記轉移使答案錯誤。
路漫漫其修遠兮,吾將上下而求索。