codeforces round 981 A~F 題解

Linear_L發表於2024-10-25

A

題意
兩個玩家正在進行遊戲,第 $ i $ 輪遊戲可以讓最初在原點的棋子向左或向右移動 $ 2i-1 $ 格(先手向左移動,後手向右移動)。問當棋子的座標的絕對值大於 $ N $ 的情況是誰的回合。

題解
由於 $ N $ 的範圍並不大,直接模擬過程就行。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

void solve()
{
	int n;
	cin>>n;
	int x=0;
	for(int i=1;i<=1000;i++)
	{
		if(i%2==1) x-=i*2-1;
		else x+=i*2-1;
		if(x>n||x<-1*n) 
		{
			if(i%2==1) cout<<"Sakurako"<<endl; 
			else cout<<"Kosuke"<<endl;
		    return ;
		}
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
 } 

B

題意
在 $ N × N $ 的方陣中每次可以選擇任意大小的方陣,讓正對角線上的所有元素都加上 $ 1 $ 。問需要進行多少次操作能讓這個方陣中所有元素都大於等於 $ 0 $ 。

題解
每次都選一整條斜線,讓上面的元素都加 $ 1 $ 即可。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int maxn=505;
int a[maxn][maxn];

void solve()
{
	int n;
	cin>>n;
	for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>a[i][j];
	int ans=0;
	for(int j=1;j<=n;j++)
	{
		int minnum=1e15;
		for(int k=0;j+k<=n&&1+k<=n;k++)
		{
			minnum=min(minnum,a[1+k][j+k]);
		}
		if(minnum>=0) minnum=0;
		else ans+=abs(minnum);
	}
	for(int i=2;i<=n;i++)
	{
		int minnum=1e15;
		for(int k=0;k+1<=n&&i+k<=n;k++)
		{
			minnum=min(minnum,a[i+k][1+k]);
		}
		if(minnum>=0) minnum=0;
		else ans+=abs(minnum);
	}
	cout<<ans<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
 } 

C

題意
一個長度為 $ N $ 的序列 $ a $ ,你可以無數次交換 $ a_i $ 和 $ a_{N-i+1} $ ,問可以達到的 $ a_i = a_{i+1} $ 最少數量為多少。

題解
神秘貪心,考慮交換是否比不交換更優即可,有點類似於排序之後跑 $ 01 $ 揹包的那種題型。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int maxn=2e5+10;
int a[maxn],n;
int dp[maxn][2];

void solve()
{
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=1;i<=n/2;i++)
	{
		if((a[i]==a[i-1])+(a[n+1-i]==a[n+2-i])>=(a[i]==a[n-i+2])+(a[n-i+1]==a[i-1]))
		  swap(a[i],a[n-i+1]);
	}
	int ans=0;
	for(int i=1;i<=n-1;i++) ans+=(a[i]==a[i+1]);
	cout<<ans<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
 } 

D
題意
在長度為 $ N $ 的序列 $ a $ 上儘可能多的劃分出這樣的的不重疊子區間 [$ L $ , $ R $] 使得 $ a_L + ... + a_R = 0 $ 。

題解
很典的字首和處理後進行動態規劃,$ dp_i $ 表示處理完前 $ i $個數能劃分出的不重疊子序列。很顯然寫出狀態轉移:

$ dp_i = dp_j +1 $ , $ s_i = s_j $

$ dp_i = dp_{i-1} $ , else

其中 $ s $ 表示字首和,這個用 $ map $ 存一下即可。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int maxn=2e5+10;
int a[maxn],s[maxn],n,dp[maxn];
map<int,int>ds,vis;

void solve()
{
	vis.clear();
	ds.clear();
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		cin>>a[i];
		s[i]=s[i-1]+a[i];
	}
	vis[0]=1;
	for(int i=1;i<=n;i++)
	{
		dp[i]=dp[i-1];
		if(vis[s[i]])
		{
			dp[i]=max(dp[i],dp[ds[s[i]]]+1);
			ds[s[i]]=i;
		}
		else
		{
			vis[s[i]]=1;
			ds[s[i]]=i;
		}
	}
	cout<<dp[n]<<endl;
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}
	
	return 0;
 } 

E
題意
對於一個長度為 $ N $ 的排列 $ p $ ,用盡可能少的如下操作:交換 $ p_i $ 與 $ p_j $。
使得整個排列中任意的 $ i $ 滿足如下兩個條件之一:

  1. $ p_i = i$
  2. $ p_{p_i} = i$

題解
首先根據排列關係可以建圖,那麼建圖後會發現,整張圖會形成若干個自環或其他環。那麼根據上面兩個條件可以發現,最後的終態是讓所有的點都形成自環或者是二元環。那麼交換操作就類似於斷兩條邊,然後重連兩條邊。顯然最優策略就是重連的兩條邊後形成二元環,自環無視掉即可。所以遍歷整張圖,找到每個環的大小 $ siz $ ,它對答案的貢獻就是 $ (siz - 1)/2 $ 。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int maxn=1e6+10;
int p[maxn],n;
bool vis[maxn]; 

vector<int>ds[maxn];
int ans=0;

void dfs(int u,int sum)
{
	if(vis[u])
	{
		ans+=(sum-1)/2;
		return ;
	}
	vis[u]=1;
	for(auto v:ds[u]) dfs(v,sum+1);
}

void solve()
{
	int n;
	cin>>n;
	ans=0;
	for(int i=1;i<=n;i++) vis[i]=0;
	for(int i=1;i<=n;i++) ds[i].clear();
	for(int i=1;i<=n;i++)
	{
		cin>>p[i];
		ds[i].push_back(p[i]);
	}
	for(int i=1;i<=n;i++)
	{
		dfs(i,0);
	}
	cout<<ans<<endl;
	
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}
	
	
	return 0;
 } 

F

題意

找到第 $ n $ 個能被 $ k $ 整除的斐波拉契項。

題解
打表題,首先先暴力找到第一個能被 $ k $ 整除的斐波拉契項,然後透過打表可以發現:在取 $ k $ 模數的斐波拉契數列的情況下,整個數列會出現迴圈情況,那麼直接找到第一個能被 $ k $ 整除的項然後乘 $ N $ 即可。

#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;

const int mod=1e9+7;
const int maxn=2e6+10;
int dp[maxn];

void solve()
{
	int n,k;
	cin>>n>>k;
	dp[1]=1%k;
	dp[2]=1%k;
	n=n%mod;
	if(dp[1]==0) 
	{
		cout<<n<<endl;
		return ;
	}
	int i=3;
	while(1)
	{
		dp[i]=(dp[i-1]+dp[i-2])%k;
		if(dp[i]==0)
		{
			cout<<(n*i)%mod<<endl;
			return ;
		}
		i++;
	}
}

signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(NULL);
	cout.tie(NULL);
	
	int t=1;
	cin>>t;
	while(t--)
	{
		solve();
	}	
	
	return 0;
 } 

相關文章