1. 程式人生 > 其它 >[Contest on 2021.10.17] HustOJ 就是個 **

[Contest on 2021.10.17] HustOJ 就是個 **

蜜汁 re 調了一百年。 目錄

\(\rm S\)

題目描述

\(n\) 個球,每個球有 RGY 三種顏色,\(\sf Oxide\) 覺得如果有兩個相鄰的球顏色相同很醜。
她每次可以交換兩個球,問至少交換多少次才能不醜。

\(1\le n\le 400\)

解法

拿到這道題真的毫無思路… 有一個很重要的結論是:無論怎樣交換,相同顏色的球的相對位置不會改變。

這樣問題就可以轉化為:在長度為 \(n\) 的序列中填上三種顏色,每種顏色數量固定,填顏色的價值定義為 "逆序對的個數",且相鄰位置顏色不同。具體而言,將原序列編號,第 \(i\)\(j\)

種球就對應它位置上的編號 \(\rm id\)。填完顏色後,第 \(i\)\(j\) 種球有了新的位置 \(k\),就令 \(p_k=\rm id\),答案就是 \(p\) 中逆序對個數。

\(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;
}

彩蛋

還有一個 矩陣 的做法,但我真的懶得看了。咕咕咕…