[Contest on 2021.10.17] HustOJ 就是個 **
\(\rm S\)
題目描述
有 \(n\) 個球,每個球有 R
、G
、Y
三種顏色,\(\sf Oxide\) 覺得如果有兩個相鄰的球顏色相同很醜。
她每次可以交換兩個球,問至少交換多少次才能不醜。
\(1\le n\le 400\)。
解法
拿到這道題真的毫無思路… 有一個很重要的結論是:無論怎樣交換,相同顏色的球的相對位置不會改變。
這樣問題就可以轉化為:在長度為 \(n\) 的序列中填上三種顏色,每種顏色數量固定,填顏色的價值定義為 "逆序對的個數",且相鄰位置顏色不同。具體而言,將原序列編號,第 \(i\) 個 \(j\)
令 \(dp_{i,j,k,0/1/2}\) 表示三種顏色分別填到 \(i,j,k\) 個,位置 \(i+j+k\) 的顏色是 \(0/1/2\)。顏色是用來判斷相鄰顏色是否相同的。複雜度 \(\mathcal O(n^3)\),第一維需要滾動一下。
程式碼
#pragma GCC optimize(2) #include <cstdio> #define print(x,y) write(x),putchar(y) template <class T> inline T read(const T sample) { T x=0; char s; bool f=0; while((s=getchar())>'9' or s<'0') f |= (s=='-'); while(s>='0' and s<='9') x = (x<<1)+(x<<3)+(s^48), s = getchar(); return f?-x:x; } template <class T> inline void write(const T x) { if(x<0) { putchar('-'); write(-x); return; } if(x>9) write(x/10); putchar(x%10^48); } #include <cstring> #include <iostream> using namespace std; const int maxn = 405; const int inf = 0x3f3f3f3f; int dp[2][maxn][maxn][3],n; int pre[maxn][3],pos[maxn][3]; char s[maxn]; int add(int p,int j,int k,int b,int c) { int ret=0; if(pre[p][b]-j>0) ret += pre[p][b]-j; if(pre[p][c]-k>0) ret += pre[p][c]-k; return ret; } int Sardine() { for(int i=1;i<=n;++i) { for(int j=0;j<3;++j) pre[i][j]=pre[i-1][j]; if(s[i]=='R') { ++pre[i][0]; pos[pre[i][0]][0] = i; } else if(s[i]=='G') { ++pre[i][1]; pos[pre[i][1]][1] = i; } else { ++pre[i][2]; pos[pre[i][2]][2] = i; } } memset(dp,0x3f,sizeof dp); for(int i=0;i<3;++i) dp[0][0][0][i]=0; for(int i=0;i<=pre[n][0];++i) { bool d=(i&1); int mn; for(int j=0;j<=pre[n][1];++j) for(int k=0;k<=pre[n][2];++k) { if(!i and !j and !k) continue; for(int o=0;o<3;++o) dp[d][j][k][o]=inf; mn = inf; for(int o=1;o<3;++o) mn = min(mn,dp[d^1][j][k][o]); if(mn^inf) dp[d][j][k][0] = mn+add(pos[i][0],j,k,1,2); if(j) { mn = inf; for(int o=0;o<3;++o) if(o^1) mn = min(mn,dp[d][j-1][k][o]); if(mn^inf) dp[d][j][k][1] = mn+add(pos[j][1],i,k,0,2); } if(k) { mn = inf; for(int o=0;o<2;++o) mn = min(mn,dp[d][j][k-1][o]); if(mn^inf) dp[d][j][k][2] = mn+add(pos[k][2],i,j,0,1); } } } int ans = inf; for(int i=0;i<3;++i) ans = min(ans,dp[pre[n][0]&1][pre[n][1]][pre[n][2]][i]); return ans==inf?-1:ans; } int main() { n=read(9); scanf("%s",s+1); print(Sardine(),'\n'); return 0; }
\(\rm Y\)
題目描述
有 \(n\) 個人站成一個圈,每個人有 \(a_i\) 個球,下面要進行一次操作:
- 每個人把一些它的球交給它左邊的人,所有人 同時 進行。
假設操作後每個人有 \(b_i\) 個球,記這個序列為 \(B\)。對於每 種 \(B\),它的價值為 \(∏b_i\)。
對於所有可能的 \(B\),你要計算它們的價值和。
\(1\le n\le 10^6,0\le a_i\le 10^9\)。
解法
神仙 \(\mathtt{dp}\) 題,屬於不看題解一輩子也想不出來的範疇。考試的時候大家都喊著做過做過,我也感覺自己好像做過… 結束後一看 \(\rm AtCoder\)
小提示:這個 \(\mathtt{dp}\) 也理解了蠻久的,表述時難免有些冗長。
首先可以思考一下 \(B\) 最終的種數,這對正解也有啟發:發現對於傳球序列 \(C\),如果它差分之後相等,最終形成的 \(B\) 就是相同的 —— 所以我們不妨只計算 \(\min C_i=0\) 的傳球序列。種數是 \(\prod_{i=1}^n(a_i+1)-\prod_{i=1}^na_i\),一個容斥。
現在要計算 \(\prod b_i\),它的組合意義是 "對於每一 種 \(B\),每個人在自己最終的球中選擇一個球的方案數,且 球不同"。但實際上,當最終的球數(\(b_i\))相同時,即使球的來源不同,編號不同,由計算式我們仍認為這兩種方案相同!也就是說,球不同只建立在最終的 \(b_i\) 個球內部。這也是不能直接 \(\mathtt{dp}\) 的原因,這裡的 "球不同" 和我們往常的理解有偏差,稱往常的理解為 "嚴格的"。
不妨就先按 球嚴格不同 來 \(\mathtt{dp}\),這其實就是 "曲線救國" 的思想,因為對於每一種 \(B\) 我們並不好計算貢獻,但是對於 每種傳球序列 就好算了,這是可以融合到轉移方程裡的。如何去重?類似上面的容斥,我們欽定傳球序列不能有零,同樣地 \(\mathtt{dp}\) 一次即可。
令 \(dp_{i,0/1}\) 分別為 "第 \(i\) 個人在原先球中選擇,前 \(i-1\) 個人選球" 的選球方案數;"第 \(i\) 個人在第 \(i-1\) 人給的球中選擇,前 \(i\) 個人選球" 的選球方案數。至於狀態為什麼如此鬼畜,可以看看後面的轉移:
- \(dp_{i,0}\leftarrow dp_{i-1,0}\)。此時需要計算 \(i-1\) 的選球方案。考慮 \(i-1\) 的傳球方案:如果 \(i-1\) 傳 \(a_i-a\) 個球,就剩下 \(a\) 個球,他的選擇方案為 \(a\) 種,所以係數就是 \(\sum_{j=1}^{a_i}j\)。
- \(dp_{i,0}\leftarrow dp_{i-1,1}\)。考慮 \(i-1\) 的傳球方案,係數為 \(a_i+1\)。
- \(dp_{i,1}\leftarrow dp_{i-1,1}\)。考慮 \(i-1\) 的傳球方案:如果 \(i-1\) 傳 \(a\) 個球,\(i\) 的選擇方案為 \(a\) 種,所以係數就是 \(\sum_{j=1}^{a_i}j\)。
- \(dp_{i,1}\leftarrow dp_{i-1,0}\)。考慮 \(i-1\) 的傳球方案:如果 \(i-1\) 傳 \(a_i-a\) 個球,就剩下 \(a\) 個球,二人在此種傳球方案中的球選擇方案為 \(a(a_i-a)\) 種,所以係數就是 \(\sum_{j=1}^{a_i}j(a_i-j)=a_i\sum_{j=1}^{a_i}j-\sum_{j=1}^{a_i}j^2\)。
完了?由於這是一個環,所以需要列舉第一個人的 \(0/1\) 狀態。如列舉 \(0\) 狀態,就 \(dp_{1,0}\leftarrow 1,dp_{1,1}\leftarrow 0\),最後答案是 \(dp_{1,0}-1\)(減去最開始加的 \(1\))。
總共是 \(\mathcal O(n)\) 的。
程式碼
#pragma GCC optimize(2)
#include <cstdio>
#define print(x,y) write(x),putchar(y)
template <class T>
inline T read(const T sample) {
T x=0; char s; bool f=0;
while((s=getchar())>'9' or s<'0')
f |= (s=='-');
while(s>='0' and s<='9')
x = (x<<1)+(x<<3)+(s^48),
s = getchar();
return f?-x:x;
}
template <class T>
inline void write(const T x) {
if(x<0) {
putchar('-');
write(-x);
return;
}
if(x>9) write(x/10);
putchar(x%10^48);
}
#include <cstring>
const int maxn = 1e6+5;
const int mod = 1e9+7;
int n,a[maxn],dp[maxn][2],inv2,inv6;
inline int adj(int x,int y) {
return x+y>=mod?x+y-mod:(x+y<0?x+y+mod:x+y);
}
inline int inv(int x,int y=mod-2) {
int r=1;
while(y) {
if(y&1) r=1ll*r*x%mod;
x=1ll*x*x%mod; y>>=1;
}
return r;
}
int s1(int x) {
return 1ll*x*(x+1)%mod*inv2%mod;
}
int s2(int x) {
return 1ll*x*(x+1)%mod*(x*2+1)%mod*inv6%mod;
}
int DP(int O,int zero) {
memset(dp,0,sizeof dp);
dp[1][0]=O,dp[1][1]=O^1;
for(int i=1;i<=n;++i) {
int to = i%n+1;
dp[to][0] = adj(dp[to][0],1ll*dp[i][0]*s1(a[i]-zero)%mod);
dp[to][0] = adj(dp[to][0],1ll*dp[i][1]*(a[i]-zero+1)%mod);
dp[to][1] = adj(dp[to][1],1ll*dp[i][0]*adj(1ll*a[i]*s1(a[i])%mod,-s2(a[i]))%mod);
dp[to][1] = adj(dp[to][1],1ll*dp[i][1]*s1(a[i])%mod);
}
return adj(O?dp[1][0]:dp[1][1],-1);
}
int main() {
n=read(9);
inv2 = inv(2), inv6 = inv(6);
for(int i=1;i<=n;++i)
a[i]=read(9);
print(adj(adj(DP(0,0),DP(1,0)),-adj(DP(0,1),DP(1,1))),'\n');
return 0;
}
彩蛋
還有一個 矩陣 的做法,但我真的懶得看了。咕咕咕…