[考試記錄] 2024.7.15 csp-s模擬賽4

XiaoLe_MC發表於2024-07-15

2024.7.15 csp-s模擬賽4

T1 傳送帶

題面翻譯

有一個長度為 \(n\) 的一維網格。網格的第 \(i\) 個單元格包含字元 \(s_i\) ,是“<”或“>”。當彈球放在其中一個格子上時,它會按照以下規則移動:

如果彈球位於第 \(i\) 個格子上且 \(s_i\) 為 '<',則彈球在下一秒會向左移動一個單元格;如果 \(s_i\) 為 '>',則向右移動一個單元格。彈球移動後,字元 \(s_i\) 會反轉(即,如果 \(s_i\) 原來是 '<',則變為 '>',反之亦然)。無論是離開左邊界還是從右邊界,當彈球離開網格時,它都會停止移動。

您需要回答 \(t(1\le t \le 10^5)\) 個獨立的查詢。每一組查詢中有一個長度 \(n(1\le n\le 5\times10^5)\),及一個只包含 “<” 和 “>”的字串。彈球可以放在任意一個位置上。對於每個查詢,在同一行輸出 \(n\) 個數,第 \(i\) 個數表示彈球放置在第 \(i\) 格時離開網格的時長,用空格隔開。

題目描述

There is a one-dimensional grid of length $ n $ . The $ i $ -th cell of the grid contains a character $ s_i $ , which is either '<' or '>'.

When a pinball is placed on one of the cells, it moves according to the following rules:

  • If the pinball is on the $ i $ -th cell and $ s_i $ is '<', the pinball moves one cell to the left in the next second. If $ s_i $ is '>', it moves one cell to the right.
  • After the pinball has moved, the character $ s_i $ is inverted (i. e. if $ s_i $ used to be '<', it becomes '>', and vice versa).
  • The pinball stops moving when it leaves the grid: either from the left border or from the right one.

You need to answer $ n $ independent queries. In the $ i $ -th query, a pinball will be placed on the $ i $ -th cell. Note that we always place a pinball on the initial grid.

For each query, calculate how many seconds it takes the pinball to leave the grid. It can be shown that the pinball will always leave the grid within a finite number of steps.

輸入格式

Each test contains multiple test cases. The first line contains the number of test cases $ t $ ( $ 1 \le t \le 10^5 $ ). The description of the test cases follows.

The first line of each test case contains an integer $ n $ ( $ 1 \le n \le 5 \cdot 10^5 $ ).

The second line of each test case contains a string $ s_1s_2 \ldots s_{n} $ of length $ n $ consisting of characters '<' and '>'.

It is guaranteed that the sum of $ n $ over all test cases does not exceed $ 5 \cdot 10^5 $ .

輸出格式

For each test case, for each $ i $ ( $ 1 \le i \le n $ ) output the answer if a pinball is initially placed on the $ i $ -th cell.

樣例 #1

樣例輸入 #1

3
3
><<
4
<<<<
6
<><<<>

樣例輸出 #1

3 6 5 
1 2 3 4 
1 4 7 10 8 1

提示

In the first test case, the movement of the pinball for $ i=1 $ is shown in the following pictures. It takes the pinball $ 3 $ seconds to leave the grid.

The movement of the pinball for $ i=2 $ is shown in the following pictures. It takes the pinball $ 6 $ seconds to leave the grid.

解析

部分分

60pts

可以發現對於每個 \(i\) 開始移動的下標,他會沿著 \(i\) 開始的方向來回彈跳直到走出去,且幅度越來越大。所以很容易想到,對每個點跑雙指標模擬這個過程,擴充套件的過程就是暴力跳下標,理論時間複雜度 \(\mathcal{O}(N^2)\),但實際複雜度可能到 \(\mathcal{O}(N^3)\) 甚至到 \(\mathcal{O}(N^4)\),比如大於小於號交替的極限資料。

#include<bits/stdc++.h>
using namespace std;
int n; string s;
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>s;
	for(int i=1; i<=n; ++i){
		string k = s;
		int ans = 0, it = i;
		while(it && it <= n){
			if(k[it-1] == '<') k[--it] = '>';
			else k[it-1] = '<', ++it;
			++ans;
		}
		cout<<ans<<' ';
	} return 0;
}
80pts

60pts 的最佳化版。我們不去一個一個跳下標,而是預處理出下一個和上一個大於小於號的位置,直接轉移。由此對每個點用雙指標模擬這個過程即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 5e5 + 5;
int n, nxt[N], lst[N], dayu, xiaoyu;
string s;
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>s;
	xiaoyu = n+1, dayu = n+1;
	for(int i=n; i>=1; --i){
		if(s[i-1] == '>'){
			nxt[i] = xiaoyu;
			dayu = i;
		}
		else{
			nxt[i] = xiaoyu;
			xiaoyu = i;
		}
	}
	dayu = 0, xiaoyu = 0;
	for(int i=1; i<=n; ++i){
		if(s[i-1] == '<'){
			lst[i] = dayu;
			xiaoyu = i;
		} else{
			lst[i] = dayu;
			dayu = i;
		}
	}	
	for(int i=1; i<=n; ++i){
		int l = i, r = i, ans = 0;
		if(s[i-1] == '<'){
			while(l && r <= n){
				l = lst[l], ans += r - l;
				if(!l) break;
				r = nxt[r], ans += r - l;
			}
		}
		else{
			while(l && r <= n){
				r = nxt[r], ans += r - l;
				if(r > n) break;
				l = lst[l], ans += r - l;
			}
		}
		cout<<ans<<' ';
	}
	return 0;
}

正解

兩個方法複雜度都為 \(\mathcal{O}(N)\),法二常數小。

法一

觀察來回彈跳時對答案產生的貢獻,比如 \(i\)< 開始,向左跳到第一個 >,然後變向,向右跳到 \(i\) 右邊第一個 <,以此類推。所以最後的答案就是:

\[\begin{split} ans_i&=(i-l_1)+(r_1-l_1)+(r_1-l_2)+\cdots +(r_{k-1}-l_{k-1})+r_k\\ &=i+2*\sum r_k-l_k \end{split} \]

維護 \(l\)\(r\) 的字首和,討論起始方向計算即可。

法二

考慮相鄰答案的轉移。考慮串 ><>><,第一個 > 走到右邊第一個 < 時變向,所以第一個 > 對答案產生的貢獻為 \(2\times (r-i)-1\)\(r\) 為第一個 < 的座標。接著看下一個字元 <,可以看作第一個字元 > 向右走到 < 然後回到 >,由於貢獻是累加起來的,所以 \(sum+=2\times (r-i)-1\)。然後把 \(sum\) 貢獻到答案裡去。

由於當前只考慮了向右跳過去跳回來的貢獻,所以還要跑一遍向左跳過去跳回來的情況。

#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 5e5 + 5;
string s;  int n, ans[N];
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>s;
	for(int i=1, j=1, sum=0; i<=n; ++i){
		while(j <= n && s[j-1] == '>') ++j;
		if(j > n) break;
		sum += 2 * (j++ - i) + 1;
		ans[i] += sum;
	}
	for(int i=n, j=n, sum=0; i>=1; --i){
		while(j >= 1 && s[j-1] == '<') --j;
		if(j < 1) break;
		sum += 2 * (i - j--) + 1;
		ans[i] += sum;
	}
	for(int i=1; i<=n; ++i) cout<<ans[i]<<' ';
	return 0;
}

T2 math

Description

沉迷於數學的小G最近口胡出一種資料生成方法: 開始,小G有一個長度為 \(n\) 的正整數序列,序列中的第 \(i\) 個數為 \(a_i\in [1,10^9]\)。 然後,小G會透過某種方式生成一個長度為 \(n\) 的非負整數序列,序列中的第 \(i\) 個數為 \(b_i\in[0,+\infty]\)。 最終,小G會得到一個整數 \(x=\sum\limits_{i=1}^{n}a_ib_i\bmod k\)

小G想知道這種資料生成方法可以生成哪些不同的非負整數。

Sample

樣例輸入

2 8
4 12

樣例輸出

2
0 4

解析

很好的數論題,但考場上並沒有推出這一結論,而是找歸路分類討論,wa了幾個點,78pts。

很好的數論題。考慮 \(n=1\) 的情況,有 \(a*b \equiv x \pmod{k}\),可化為 \(ab+ky=x\)。若該獅子有整數解,那麼當且僅當 \(\gcd(a,k)\mid x\)。因此答案 \(x\) 只能是 \(gcd\) 的整數倍,並且小於 \(k\)。所以計算出所有 \(a\)\(k\)\(gcd\),列舉輸出答案即可。複雜度 \(\mathcal{O}((n+v)\log n)\)

#include<bits/stdc++.h>
using namespace std;
int n, k, gcd, a, ans;
int main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>k; gcd = k;
	for(int i=1; i<=n; ++i) cin>>a, gcd = __gcd(a, gcd);
	ans = k / gcd; cout<<ans<<'\n';
	for(int i=0; i*gcd < k; ++i) cout<<i*gcd<<' ';
	return  0;
}

T3

letax 懶得打了。

image

解析

很好的DP題。暴力40pts。

首先對每個 \(a!=0\) 的點進行排序,這是顯然的。然後我就在思考怎麼單調佇列 \(\mathcal{O}(n)\) 轉移,然後發現這個絕對值把 i 和 j 綁在一起了,然後就想怎麼把絕對值去掉,然後就考慮平方一下,然後成功地又把 i 和 j 綁一起了。於是打BL。其實單調佇列也可以維護,只不過要把座標轉為切比雪夫距離,最後記錄路徑計算答案即可。

不會切比雪夫):?考慮簡單DP。設當前需要轉移的點為 \(i\) ,那麼 \(j\) 可以在 \(i\) 的左上、左下、右上和右下四種情況。需要在轉移時判斷點在那個方位嗎?不需要,因為取絕對值的原因,所以 xy 座標給的貢獻必須大於 0,所以只要在這四種轉移裡取 max 即可。這是本題極為關鍵的一點。

#include<bits/stdc++.h>
using namespace std;
#define int long long
constexpr int N = 2e3 + 2;
int n, m, x[N*N], y[N*N], a[N*N], b[N*N], cnt, dp[N*N], p1[N*N], num2[N*N], tot;
int rnk[N][N], srt[N*N], p[N*N], num[N*N], tail, way[N*N], ans, nxt[4], lst[4];
bool cmp(int q, int w){ return a[q] < a[w]; }
int dx[4] = {1, 1, -1, -1}, dy[4] = {1, -1, 1, -1};
signed main(){
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin>>n>>m;
	for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j){
		cin>>a[++cnt];
		x[cnt] = i, y[cnt] = j;
		rnk[i][j] = cnt;
		srt[cnt] = cnt;
	}
	for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j)
		cin>>b[rnk[i][j]];
	sort(srt+1, srt+1+cnt, cmp);
	int bg = 1; while(!a[srt[bg]]) ++bg;
	for(int i=bg; i<=cnt; ++i){
		if(a[srt[i]] != a[srt[i-1]] && a[srt[i-1]]){ bg = i; break; }
		dp[srt[i]] = b[srt[i]];
		for(int j=0; j<4; ++j)
			nxt[j] = max(nxt[j], dp[srt[i]] + x[srt[i]]*dx[j] + y[srt[i]]*dy[j]);
		ans = max(ans, dp[srt[i]]);
	}
	for(int i=bg; i<=cnt; ++i){
		if(a[srt[i]] != a[srt[i-1]]) for(int j=0; j<4; ++j)
			lst[j] = nxt[j], nxt[j] = 0;
		for(int j=0; j<4; ++j)
			dp[srt[i]] = max(dp[srt[i]], b[srt[i]] - x[srt[i]]*dx[j] - y[srt[i]]*dy[j] + lst[j]);
		for(int j=0; j<4; ++j)
			nxt[j] = max(nxt[j], dp[srt[i]] + x[srt[i]]*dx[j] + y[srt[i]]*dy[j]);
		ans = max(ans, dp[srt[i]]);
	}
	return cout<<ans, 0;
}

T4

image

image

個人能力有限,暫時該不出來,所以先咕~。

相關文章