codeforces round 981 A~F 题解

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;
 } 
posted on 2024-10-25 02:11  Linear_L  阅读(55)  评论(0编辑  收藏  举报