USACO 2021-2022 Contest 1 Bronze 題解


廣告

蒟蒻第一次打 USACO,只打了 Bronze 就跑路了。不得不說也有很有意思的題目。接下來就看看題目吧。

由於現在還看不到題目,只給出加工后的題目大意。

T1 Lonely Photo

Content

有一個長度為 \(n\) 的字符串 \(s\),僅包含兩種字符:GH。定義字符串 \(s'\) 是孤獨的,當其僅當 \(s'\) 中恰好只有一個 GH\(|s'|\geqslant 3\)。例如,字符串 GHGGGGHHHH 是孤獨的,而字符串 GHGHGHG 不是。現在,請你求出 \(s\) 的所有孤獨的子串的個數。

數據范圍:\(n\leqslant 5\times 10^5\)

Solution

我們很容易想到一個 \(\mathcal O(n^2)\) 的做法,即暴力模擬每個子串並判斷其是否是孤獨的。但是有沒有更優的方法?確實有。我們可以先預處理出當前字符前面、后面有多少個連續的與當前字符不同的字符,設在 \(i\) 位置上的字符前面連續的與當前字符不同的字符有 \(\textit{bef}_i\) 個,后面連續的與當前字符不同的字符有 \(\textit{aft}_i\) 個。那么,我們就可以對於每個位置直接算其對答案的貢獻就好了。具體地,對於當前位置 \(i\)

  • 如果 \(\textit{bef}_i>0\)\(\textit{aft}_i>0\),那么當前位置對答案的貢獻加上 \(\textit{bef}_i\cdot\textit{aft}_i\)
  • 如果 \(\textit{bef}_i>1\),那么那么當前位置對答案的貢獻加上 \(\textit{bef}_i-1\)
  • 如果 \(\textit{aft}_i>1\),那么那么當前位置對答案的貢獻加上 \(\textit{aft}_i-1\)

綜上,當前位置 \(i\) 對答案的貢獻是 \(\textit{bef}_i\cdot\textit{aft}_i+\max(0,\textit{bef}_i-1)+\max(0,\textit{aft}_i-1)\)

由此,我們將算法優化到了 \(\mathcal{O}(n)\),就可以通過這道題目了。

Code

賽時代碼。

namespace Solution {
	const int N = 5e5 + 7;
	int n, cntg, cnth, g[N], h[N], numg[N], numh[N], bef[N], aft[N];
	char a[N];
	
	iv Sub1_work() {
		ll ans = 0;
		F(int, i, 1, n) {
			if(i <= n - 2 && ((a[i] == 'G' && a[i + 1] == 'H' && a[i + 2] == 'H') || (a[i] == 'H' && a[i + 1] == 'G' && a[i + 2] == 'G'))) {
				int cur = i + 2;
				for(; cur < n && a[cur + 1] == a[i + 2]; cur++);
				ans += (cur - (i + 2) + 1);
			}
			if(i > 1 && i < n && ((a[i] == 'G' && a[i - 1] == 'H' && a[i + 1] == 'H') || (a[i] == 'H' && a[i - 1] == 'G' && a[i + 1] == 'G'))) {
				int curl = i - 1, curr = i + 1;
				for(; curl > 1 && a[curl - 1] == a[i - 1]; curl--);
				for(; curr < n && a[curr + 1] == a[i + 1]; curr++);
				ans += 1ll * ((i - 1) - curl + 1) * (curr - (i + 1) + 1);
			}
			if(i >= 3 && ((a[i] == 'G' && a[i - 1] == 'H' && a[i - 2] == 'H') || (a[i] == 'H' && a[i - 1] == 'G' && a[i - 2] == 'G'))) {
				int cur = i - 2;
				for(; cur > 1 && a[cur - 1] == a[i - 2]; cur--);
				ans += ((i - 2) - cur + 1);
			}
		}
		write(ans);
	}
	iv Sub2_work() {
		F(int, i, 1, n) if(a[i] == 'G') g[++cntg] = i, numg[i] = cntg;
		F(int, i, 1, n) if(a[i] == 'H') h[++cnth] = i, numh[i] = cnth;
		int cnt = 0;
		F(int, i, 1, n) {
			if(a[i] == 'G') cnt++;
			else {
				bef[i] = cnt;
				if(numh[i] != 1) aft[h[numh[i] - 1]] = cnt;
				cnt = 0;
			}
		}
		aft[h[cnth]] = cnt, cnt = 0;
		F(int, i, 1, n) {
			if(a[i] == 'H') cnt++;
			else {
				bef[i] = cnt;
				if(numg[i] != 1) aft[g[numg[i] - 1]] = cnt;
				cnt = 0;
			}
		}
		aft[g[cntg]] = cnt;
		ll ans = 0;
		F(int, i, 1, n) {
			ans += 1ll * bef[i] * aft[i];
			if(bef[i] >= 2) ans += bef[i] - 1;
			if(aft[i] >= 2) ans += aft[i] - 1;
		}
		write(ans);
	}
	
	iv Main() {
		read(n), scanf("%s", a + 1);
		if(n <= 5000) Sub1_work();
		else Sub2_work();
		return;
	}
}

T2 Air Cownditioning

Content

有兩個長度為 \(n\) 的數組 \(a,b\)。每次操作你可以選擇一個區間 \([l,r]\),然后將下標在這個區間里面的所有 \(b_i\)\(1\) 或者減 \(1\),求最少需要多少操作才能夠使得 \(\forall i\in[1,n],a_i=b_i\)

數據范圍:\(1\leqslant n\leqslant 10^5\)

Solution

首先我們令 \(c_i=a_i-b_i\),然后就把這道題目轉換成了最少需要多少次操作使得 \(\forall i\in[1,n],c_i=0\)。然后我們就發現有一道經典的差分題目和這題非常相像,就是洛谷 P4552。沒做那道題目的同學可以先去做做那道題目,然后你就發現這道題目可以隨便亂殺了。

我們可以套路地將 \(c_i\) 進行一個差分操作,即令 \(d_i=c_i-c_{i-1}\),然后就又轉化為最少需要多少操作才能夠使得 \(\forall i\in[1,n],d_i=0\)。然后,我們可以統計一下所有 \(d_i\) 中正數的和 \(sum_1\) 和負數的和的絕對值 \(sum_2\),為什么呢?首先我們想想對 \([l,r]\) 進行一次操作之后會對差分數組 \(d\) 有什么影響。分析之后不難發現:

  • 如果操作為全加 \(1\),那么 \(d_{l-1}\leftarrow d_{l-1}+1\)\(d_r\leftarrow d_r-1\)
  • 如果操作為全減 \(1\),那么 \(d_{l-1}\leftarrow d_{l-1}-1\)\(d_r\leftarrow d_r+1\)

然后再分析怎么讓所有 \(d_i=0\),不難發現,我們首先選擇 \(d_2,d_3,\dots,d_{n-1}\) 當中的一個正數和一個負數進行若干次操作,等不能執行這種操作之后,再拿一個正數和 \(d_n\) 進行操作,或者再拿一個負數和 \(d_1\) 進行操作(因為由定義可知,\(d_1>0\)\(d_n<0\))。然后你就可以發現這樣的操作次數為 \(\min(sum_1,sum_2)+|sum_1-sum_2|=\max(sum_1,sum_2)\)

於是這道題目就做完了,還弄了個雙倍經驗,簡直美汁汁。

Code

賽時代碼。

namespace Solution {
	const int N = 1e5 + 7;
	int n, cur, p[N], cf[N];
	
	iv Main() {
		read(n); F(int, i, 1, n) read(p[i]);
		F(int, i, 1, n) read(cur), p[i] = p[i] - cur;
		F(int, i, 1, n) cf[i] = p[i] - p[i - 1];
		ll sum1 = 0, sum2 = 0;
		F(int, i, 1, n) if(cf[i] > 0) sum1 += cf[i]; else sum2 += -cf[i];
		write(max(sum1, sum2));
		return;
	}
}

T3 Walking Home

Content

\(T\) 組詢問,每組詢問給定一個大小為 \(n\times n\) 的地圖,僅包含空地(用 . 表示)和障礙(用 H 表示),求在轉彎不超過 \(k\) 次的情況下從 \((1,1)\)\((n,n)\) 有多少種路線。

數據范圍:\(2\leqslant n\leqslant 50\)\(1\leqslant k\leqslant 3\)

Solution

你想着通過暴力 dfs 過這道題目,然而你發現最終 T 了兩個點。然后你就想着用 dp 來解決了。

我們設 \(dp_{x,y,i,j}\) 表示從 \((1,1)\)\((x,y)\)\(i=0/1/2\) 分別表示朝向為下/右/移動開始,轉彎了 \(j\) 次的路線數。另令 \(dx_0=1\)\(dx_1=0\)\(dy_0=0\)\(dy_1=1\) 方便后面的轉移。然后分兩種情況討論:

  • 不轉彎,則 \(dp_{x+dx_i,y+dy_i,i,j}\leftarrow dp_{x+dx_i,y+dy_i,i,j}+dp_{x,y,i,j}\),特別地,如果 \(x=1,y=1\),不要忘記加上 \(dp_{1,1,2,0}\)
  • 轉彎,只能夠在 \((x,y)\neq(1,1)\) 時進行,此時 \(dp_{x+dx_i,y+dy_i,i,j+1}\leftarrow dp_{x+dx_i,y+dy_i,i,j+1}+dp_{x,y,1-i,j}\)

初始化為 \(dp_{1,1,2,0}=1\),答案即為 \(\sum\limits_{i=0}^1\sum\limits_{j=1}^k dp_{n,n,i,j}\)

Code

賽時代碼。

namespace Solution {
	const int dx[2] = {1, 0};
	const int dy[2] = {0, 1};
	const int N = 57;
	int n, k, ans, dp[N][N][3][7];
	char a[N][N];
	
	iv Main() {
		MT {
			read(n, k), memset(dp, 0, sizeof(dp)), ans = 0;
			F(int, i, 1, n) scanf("%s", a[i] + 1);
			dp[1][1][2][0] = 1;
			F(int, i, 1, n) F(int, j, 1, n) if(a[i][j] != 'H') F(int, l, 0, 1) {
				int x = i + dx[l], y = j + dy[l];
				if(x < 1 || x > n || y < 1 || y > n || a[x][y] == 'H') continue;
				F(int, t, 0, k) {
					dp[x][y][l][t] += dp[i][j][l][t];
					if(i == 1 && j == 1 && t == 0) dp[x][y][l][t] += dp[1][1][2][0];
				}
				if(i != 1 || j != 1) F(int, t, 0, k - 1) dp[x][y][l][t + 1] += dp[i][j][1 - l][t];
			}
			int ans = 0;
			F(int, i, 0, 1) F(int, j, 1, k) ans += dp[n][n][i][j];
			println(ans);
		}
		return;
	}
}


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM