Educational Codeforces Round 116(div.2)

A. AB Balance

题目描述

​ 给一个长为 n n n仅包含字符 a , b a,b a,b字符串,你可以执行操作,将任意索引处的字符 a a a换成 b b b,将任意索引处的 b b b换成 a a a。要求你操作最少次,使得字符串中 a b ab ab子串和 b a ba ba子串数量相同

分析

​ 最多只会修改一次

代码

#include<bits/stdc++.h>
using namespace std;
char s[1100];
int n;
bool check()
{
	int cnt1=0,cnt2=0;
	for (int i=1;i<n;++i)
	{
		if (s[i]=='a'&&s[i+1]=='b')++cnt1;
		else if (s[i]=='b'&&s[i+1]=='a')++cnt2;
	}return cnt1==cnt2;
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>(s+1);
		n = strlen(s+1);
		if (check())
		{
			cout<<(s+1)<<"\n";
		}
		else
		{
			for (int i=1;i<=n;++i)
			{
				if (s[i]=='a')
				{
					s[i]='b';
					if (check())
					{
						cout<<(s+1)<<"\n";
						break;
					}else s[i]='a';
				}else
				{
					s[i]='a';
					if (check())
					{
						cout<<(s+1)<<"\n";
						break;
					}else s[i] = 'b';
				}
			}
		}
	}
}

B. Update Files

题目描述

​ 给你 n n n台计算机,编号为 1 1 1的计算机上有着更新文件。目的是将文件拷贝到所有的计算机上。将更新文件从一台计算机传输到另一台计算机的唯一方法是使用电缆链接。一次只能有一根接线板连接到计算机上。因此,从任何拥有更新文件的计算机,依靠电缆它们可以在一小时内准确地复制到其他计算机。

​ 给出计算机的总数 n n n,和电缆的总数 k k k。要求计算最少要几小时才能将文件拷贝到所有计算机上

分析

​ 一台计算机拷贝到两一台计算机,现在有两台计算机拥有更新未见,然后再一起拷贝到斯台计算机上。即,一开始是指数式增长。等到计算机的数量超过 k k k时,以最大限度 k k k进行增长。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll n,k;
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>k;
		ll cnt = 1;
		ll ans = 0;
		while (cnt<=k)
		{
			if (cnt>=n)break;
			++ans;
			cnt<<=1;
		}
		if (cnt>=n)
		{
			cout<<ans<<endl;
		}else 
		{
			cout<<ans+(n-cnt+k-1)/k<<endl;
		}
	}
}

C. Banknotes

题目描述

​ 给定一个数n和k,下面给出n个整数 a i a_i ai,分别拥有无限张代表 1 0 a i 10^{a_i} 10ai的纸币,问由k张纸币不能凑出的最小数是多少

分析

​ 贪心的思想,我们从低位到高位在保证不会进位的情况下取纸币,取 k + 1 k+1 k+1张即可。

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int inf = 1e18;
ll p10[22];
int a[12],b[12];
int n;ll k;
int main()
{
	ios::sync_with_stdio(0);
	p10[0]=1;
	for (int i=1;i<=18;++i)p10[i]=p10[i-1]*10LL;
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>k;
		for (int i=1;i<=n;++i)cin>>a[i];
		if (a[1]!=0)
		{
			cout<<"1\b";
			continue;
		}
		for (int i=1;i<n;++i)
			b[i] = p10[a[i+1]-a[i]]-1;
		b[n] = inf;ll ans = 0;
		for (int i=1;i<=n;++i)
		{
			if (b[i]>k)
			{
				ans += (k+1LL)*p10[a[i]];
				break;
			}
			else
			{
				ans += b[i]*p10[a[i]];
				k -= b[i];
			}
		}cout<<ans<<"\n";
	}
}

D. Red-Blue Matrix

题目描述

​ 给你一个 n × m n\times m n×m 的数字矩阵,然后要求你给每行染成蓝色或者红色。

然后再将这个矩阵分为左右两部分,

​ 要求满足以下两点

​ 1、左矩阵中,所有红色格的数字的最小值大于蓝色格子的最大值

​ 2、右矩阵中,所有蓝色格的数字的最小值大于红色格子的最大值

若存在这么一种染色和分割方法,输出具体方案,否则输出不存在。

分析

​ 一下子感觉极为复杂。我们先只考虑左半部分的矩阵。假设我们已经确定了左半部分的矩阵,即已经确定了分割线。在这种情况下我们如何染色,才能保证红色各自的数字的最小值大于蓝色数字的最大值呢?

能想到的策略是:我们先求出所有行的最大值。

将行编号按照最大值从大到小排序,可以肯定的是被染成红色的行,一定是排序数组的前缀!

我们只需枚举前缀,维护前缀行的最小值,保证这个最小值 > > >后面行的最大值即可

这一部分理解极为重要

将这个思想带入到求解右半部分的矩阵后我们发现,求解右半部分矩阵时,我们其实是按照最大值从小到大排序,然后枚举前缀作为染成红色的行。需要保证的是,后缀所有行的最小值 > > >当前枚举行的最大值

与求解左半部分的做法是一样的

如此,我们在确定分割线的情况下,可以得知左矩阵有哪些方案可以,右矩阵有哪些方案可以。只需要求解出是否有相同的方案即可。

但是这一步却是最为困难的!暴力做的话复杂度肯定爆炸。

考虑到方案相同的话,那么染成红色的行一定是一样多的,且分别是排序后的两数组的前缀!

那么,我们可以枚举前缀的长度,然后判断这两个前缀构成的集合是否相同。

具体判断方法是,不断增加前缀长度的同时,维护一个 s e t set set,相同的行第一次放入时正常放入,如果桌子前放入过了的话就删除掉他。这样当 s e t . e m p t y ( ) set.empty() set.empty()时,可以说两前缀组成的集合相同。

值得注意的是,我们在对左右矩阵的数组进行排序时,对左矩阵的数组按照该行左矩阵部分的最大值从大到小排序。右矩阵的数组按照该行右矩阵部分最大值从小到大排序时,如果遇到两个行值相等的情况,应当让出现位置在左矩阵数组中更前的行放在前面。

#include<bits/stdc++.h>
using namespace std;
const int inf = 1e9;
const int maxn = 5e5+100;
vector<int> premx[maxn],suffmx[maxn];
vector<int> premn[maxn],suffmn[maxn];
vector<int> gra[maxn];
int s1[maxn],s2[maxn];
int cp1[maxn],cp2[maxn];
int n,m;
char res[maxn];
int cp3[maxn];
int id[maxn];
void solve()
{
	for (int div=1;div<m;++div)
	{
		for (int i=1;i<=n;++i)
		{
			s1[i] = s2[i] = i;
			cp1[i] = premx[i][div];
			cp2[i] = suffmx[i][div+1];
		}
		sort(s1+1,s1+1+n,[](int na,int nb)
		{
			return cp1[na]>cp1[nb];
		});
		for (int i=1;i<=n;++i)id[s1[i]]=i;
		sort(s2+1,s2+1+n,[](int na,int nb)
		{
			if (cp2[na]==cp2[nb])return id[na]<id[nb];
			return cp2[na]<cp2[nb];
		});
		cp3[n+1]=inf;
		for (int i=n;i>=1;--i)cp3[i] = min(cp3[i+1],suffmn[s2[i]][div+1]);
		set<int> se;
		int pre1=inf;
		for (int i=1;i<n;++i)
		{
			if (se.count(s1[i]))se.erase(s1[i]);
			else se.insert(s1[i]);
			if (se.count(s2[i]))se.erase(s2[i]);
			else se.insert(s2[i]);
			pre1 = min(pre1,premn[s1[i]][div]);
			if (se.empty()&&pre1>cp1[s1[i+1]]&&cp2[s2[i]]<cp3[i+1])
			{
				cout<<"YES\n";
				for (int j=1;j<=i;++j)res[s1[j]]='R';
				for (int j=i+1;j<=n;++j)res[s1[j]]='B';
				for (int i=1;i<=n;++i)cout<<res[i];
				cout<<" "<<div<<endl;
				return ;
			}
		}
	}cout<<"NO\n";
}
int main()
{
	ios::sync_with_stdio(0);
	int T;cin>>T;
	while (T--)
	{
		cin>>n>>m;
		for (int i=1;i<=n;++i)
		{
			premx[i].clear();
			premn[i].clear();
			suffmx[i].clear();
			suffmn[i].clear();
			gra[i].clear();
			for (int j=0;j<=m+1;++j)
			{
				gra[i].push_back(0);
				premx[i].push_back(0);
				premn[i].push_back(inf);
				suffmx[i].push_back(0);
				suffmn[i].push_back(inf);
			}
			for (int j=1;j<=m;++j)
			{
				cin>>gra[i][j];
				premx[i][j] = max(premx[i][j-1], gra[i][j]);
				premn[i][j] = min(premn[i][j-1], gra[i][j]);
			}
			for (int j=m;j>=1;--j)
			{
				suffmx[i][j] = max(suffmx[i][j+1], gra[i][j]);
				suffmn[i][j] = min(suffmn[i][j+1], gra[i][j]);
			}
		}
		solve();
	}
}

E. Arena

题目描述

​ 给一个长为 n n n的数组 a a a 1 ≤ a i ≤ x 1\le a_i\le x 1aix

进行若干轮操作:对于每一个 a i → a i − ( n − 1 ) a_i\rightarrow a_i-(n-1) aiai(n1) 。然后去除 a i ≤ 0 a_i\le 0 ai0的数字,更新 n n n为数组的大小

直到数组大小为 ≤ 1 \le 1 1

求,有多少种数组,最后数组大小为 0 0 0

分析

​ 考虑 d p dp dp , d p [ i ] [ j ] : dp[i][j]: dp[i][j]: n − i , x = j n-i,x=j ni,x=j时的答案

我们思考,在经历了一轮后,那些所有 a i ≤ i − 1 a_i\le i-1 aii1的数字全部被淘汰,假设淘汰的一共有 k k k个数字 k ≤ i k\le i ki

那么剩下来的就是 i − k i-k ik个数字,且每一个数字的最大限为 j − i + 1 j-i+1 ji+1

这就递归到了子问题!

代码

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod = 998244353;
const int maxn = 550;
ll dp[maxn][maxn];
ll C[maxn][maxn];
int n,x;
inline ll POW(ll base,ll p)
{
	ll ans = 1;
	while (p)
	{
		if (p&1)ans = ans*base%mod;
		base = base*base%mod;
		p>>=1;
	}return ans;
}
int main()
{
	ios::sync_with_stdio(0);
	for (int i=1;i<=500;++i)
	{
		C[i][0]=1;
		for (int j=1;j<i;++j)
			C[i][j] = (C[i-1][j] + C[i-1][j-1])%mod;
		C[i][i]=1;
	}
	cin>>n>>x;
	for (int i=0;i<=x;++i)dp[0][i]=1;
	for (int i=1;i<=n;++i)
	{
		for (int j=0;j<=x;++j)
		{
			if (j<=i-1)
			{
				dp[i][j] = POW(j,i);
				continue;
			}
			for (int k=0;k<=i;++k)
				dp[i][j] = (dp[i][j]+C[i][k]*dp[i-k][j-i+1]%mod*POW(i-1,k)%mod)%mod;
		}
	}
	cout<<dp[n][x]<<endl;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值