Codeforces Round 981 (Div. 3) 題解(A-E)

zerocloud01發表於2024-10-27

目錄
  • 分析
  • A
    • 思路
    • 程式碼
  • B
    • 思路
    • 卡題原因
    • 程式碼
  • C
    • 思路
    • 卡題原因
    • code
  • D
    • 思路
    • 卡題原因
    • 程式碼
  • E
    • 思路
    • wa原因
    • code

Codeforces Round 981 (Div. 3)

分析

這場整體發揮都不好, 雖然題也抽象, 但是對於這些題完全沒必要卡這麼久. 正常至少能看到 \(\mathbf{F}\)

A

思路

因為邊界 \(n\) 管轄 \(\pm\), 而 Sakurako 管轄 \(\{-1,-3,-5,-7...\}\), Kosuke 管轄 \(\{2,4,6,...\}\), 也就是說 Sakurako 管轄奇數, Kosuke 管轄偶數.

程式碼

void func(void)
{
	int n;
	cin >> n;
	cout << (n&1 ? "Kosuke\n" : "Sakurako\n");
}

B

思路

翻譯題意後可知, 每次操作對一個子正方形的主對角線(左上到右下)都 \(+1\).

那麼對於邊長為 \(n\) 的正方形, 每次只需要操作 \(2n-1\) 條線(從左下或者右上點的邊長 \(\le n\) 的正方形), 因為對於每條主對角線, 如果首尾相接就可以連起來, 那麼可以將操作合併.

並且因為只需要讓所有數為 \(+\), 所以只需統計每條上負數的最大值即可, 最後求和.

卡題原因

一時間沒想到怎麼處理對角線, 居然卡了半個小時.
注: 事實上下列程式碼也非常醜陋. 完全不符合我的程式碼風格.

程式碼

void func(void)
{
	int n,ans = 0;
	cin >> n;
	vector<vector<int>> a(n+2,vector<int>(n+2));
	for(int i=1;i<=n;++i)
	{
		for(int j=1;j<=n;++j)
		{
			int stp;	cin >> stp;
			if(stp < 0)	a[i][j] = -stp;
		}
	}
	for(int i=2;i<=n;++i)
	{
		int stp = 0;
		int x = i,y = 1;
		while(x <= n)
		{
			stp = max(stp,a[x][y]);
			x++, y++;
		}
		ans += stp;
	}
	for(int i=1;i<=n;++i)
	{
		int stp = 0;
		int x = 1,y = i;
		while(y <= n)
		{
			stp = max(stp,a[x][y]);
			x++, y++;
		}
		ans += stp;
	}
	cout << ans << '\n';
}

C

思路

貪心
因為每個元素只能和對稱元素交換, 所以可以把原始串拆成兩部分 —— \(1 \sim n/2, (n+1)/2 \sim n\), 如果奇數串, 因為對稱軸元素無法互動, 所以不用處理.

雖然題意描述是 \(a_j = a_{j+1}\) 擾動指數增加, 但是按 \(a_j = a_{j-1}\) 判斷也可以, 那麼對稱軸左側向左判, 右側向右判斷也無影響, 若是到了對稱軸:

  • 若是奇數, 中間數不變化, 所以不影響結果.
  • 若是偶數, 分界點左右數互相判斷, 也無影響.

\(fi(i) = n - i + 1\)
那麼每次只需要判斷 \(i - 1\)\(fi(i-1)\), \(i\)\(fi(i)\).
\(a_{i-1} = x_0, a_{fi(i-1)} = y_0, a_{i} = x_1, a_{fi(i)} = y_1\)

  • \(x_0 = y_0\), 那麼 \(x_1, y_1\) 在什麼位置都不影響結果.
  • \(x_1 = y_1\), 那麼 \(x_0, y_0\) 在什麼位置都不影響結果.
  • \(x_0 = x_1\), 那麼交換\(x_1, y_1\), 使得兩個 \(x\) 不接觸.
  • \(y_0 = y_1\), 那麼交換\(x_1, y_1\), 使得兩個 \(y\) 不接觸.

卡題原因

分析覺得需要儘量減少連續序列長度, 但是長度實際並沒有影響.

code

int fi(int k)// 計算反轉後的串
{
	return (n-k+1);
}

void func(void)
{
	
	cin >> n;
	vector<int> a(n+1),t(n+1);// 其實也沒有新開的必要, 因為每次也是判斷交換後的前一個數
	vector<PII> b(n/2+1);
	for(int i=n;i>=1;--i)	cin >> a[i];// 並沒有反讀的必要
	for(int i=1;i<=n/2;++i)
	{
		b[i].x = a[i],b[i].y = a[fi(i)];
	}
	int ans = 0;
	t[(n+1)/2] = a[(n+1)/2];
	for(int i=1;i<=n/2;++i)
	{
		t[i] = b[i].x,t[fi(i)] = b[i].y;
		if(t[i-1] == t[fi(i)+1] || b[i].x == b[i].y)	continue;
		if(b[i].x == t[i-1] || b[i].y == t[fi(i)+1])
		{
			swap(t[i],t[fi(i)]);
		}
	}
	for(int i=1;i<=n;++i)	if(t[i] == t[i-1])	ans ++;
	cout << ans << '\n';
}

D

思路

字首和 貪心

字首和維護上一次為 \(0\) 區間尾的下一個點開始到當前節點的字首和.

貪心尋找每次最先出現的滿足區間, 這樣對後續區間的影響最小, 因為其儘可能靠左, 佔用元素更少.

卡題原因

完全沒想到線上, 還是抄小秦的才明白.

程式碼

void func(void)
{
	int n,ans = 0,sum = 0;
	cin >> n;
	vector<int> a(n);
	map<int,int> mp;
	for(auto &i : a)	cin >> i;
	for(int i=0;i<n;++i)	
	{
		sum += a[i];
		mp[sum] ++;
		if(mp[sum] == 2 || sum == 0)
		{
			ans ++;
			sum = 0;
			mp.clear();
		}
		
	}
	cout << ans << '\n'; 
}

E

思路

對於每個 \(i\), 可以透過 \(a_i\) 找到指向的元素, 那麼互相指向的元素就可以構成一個環.

根據題意, 大小 \(\le 2\) 的環滿足條件, 題面需要所有環 \(\le 2\).

可以計算, 一個 $> 2 $ 的環, 需要 \((size-1) / 2\) 次操作才能滿足條件.
所以只需要計算每個環的操作此時, 求和即可.

wa原因

bitset開小了, 搞錯了開法, <>內的是實際元素大小.
為什麼小資料還一直報wa, 搞得我以為是模擬錯了. 要不是看小秦程式碼對拍, 當晚都補不出來.

code

void func(void)
{
	int n;
	cin >> n;
	vector<int> a(n+1);
	for(int i=1;i<=n;++i)	cin >> a[i];
	bitset<N/32> vis;
	int tp = 1,ans = 0,p = 0;
	for(int i=1;i<=n;++i)
	{
		if(vis[i])	continue;
		vis[i] = true;
		tp = i, p = a[tp];
		vis[p] = true;
		int cnt = 1;
		while(p != tp)
		{
			p = a[p];
			cnt ++;
			vis[p] = true;
		}
		ans += (cnt-1)/2;
	}
	cout << ans << '\n';
}

相關文章