Educational Codeforces Round 153 (Rated for Div. 2)(A~E)

终究是棋差一招

A - Not a Substring 

        题意:给定一个长度为n的括号序列,求能否构造出一个长度为2n的正确括号序列,使得这个序列当中不包含有给定的括号序列。

        思路:没仔细想,根据情况构造出类似于 "((()))" 或者"()()()"的序列即可。

void solve() 
{
	int n;
	cin>>n;
	for(int i = 0 ; i < n ; i ++){
		string s;
		cin>>s;
		int len = s.size();
		if(s == "()"){
			cout<<"NO\n";
		}
		else{
			if(s[0] == ')'){
				if(s[1] == '('){
					cout<<"YES\n";
					for(int j = 0 ; j < len ; j ++){
						cout<<"(";
					}
					for(int j = 0 ; j < len ; j ++){
						cout<<")";
					}			
					cout<

1860B - Fancy Coins 

        题意:给定 m , k , a1 , ak 。 有两种硬币,第一种硬币的价值为1 ,共有a1个普通硬币和无限个Fancy 硬币 。 第二种硬币的价值为 k , 共有ak个普通硬币和无限个Fancy硬币。求在凑出m个硬币的情况之下,使用Fancy硬币数量的最小值。
        思路:显然,我们需要尽可能的用到第二种硬币。先把免费的价值为1的硬币先用了,然后再看需要用到多少个价值为k的硬币。假设用完之后还需要x个硬币,那么会分两种情况:1、前面价值为1的硬币先不用,然后凑到k ,再用一个价值为k的硬币补上。2、用x个价值为1的Fancy硬币补上。
        

void solve() 
{
	int m , k , a1 , ak;
	cin>>m>>k>>a1>>ak;
	if(k > m){
		cout<

1860C - Game on Permutation 

        题意:给定一个长度为n的序列,有Alice 和 Bob 玩博弈游戏 。 首先Alice将筹码放到序列任意一个数上面,接下来Bob 需要将筹码放到当前筹码所在元素的左边且大小小于当前筹码所在元素。然后两个人轮流进行同样的操作,直到没法操作的时候胜利。求整个序列当中有多少个数,当Alice选择它时能够取胜。
        思路:考虑必败的情况:1、当Alice选择的数前面没有比他更小的了,Bob直接取胜。2、当Alice选择的数前面存在必胜的数,Bob转移到该数上面便取胜了。其余情况均是必胜的。整个过程当中需要判断能够转移到前面的任意一个数或者能否转移到前面必胜的数。因此只需要存前面最小的数和必胜态最小的数即可。
        

void solve() 
{
	int n;
	cin>>n;
	int win[n];
	int min = 0;
	int wmin = 1e9;
	for(int i = 0 ; i < n ; i ++){
		int num;
		cin>>num;
		if(min == 0){
			min = num;
			win[i] = 0;
		}
		else{
			if(num < min){
				min = num;
				win[i] = 0;
			}
			else{
				if(num > wmin){
					win[i] = 0;
				}
				else{
					win[i] = 1;
					wmin = num;
				}
			}
		}
	}
	int ans = 0;
	for(int i = 0 ;i < n ; i ++){
		ans += win[i];
	}
	cout<

1860D - Balanced String 
 

        题意:给定一个01串 s ,每次操作可以交换任意两个数。求当整个串的‘01’和‘10’子序列(可以不连续)相等时的最小操作数。

        思路:直接考虑dp[i][j][k],其中 i 表示前 i 位 , j 表示前 i 位中有多少个 1 , k 表示 10 比 01 子序列多多少个(由于10可能会比01数量少,因此需要有个偏移值,这边直接设偏移值为3000),dp值表示为01翻转的操作数。当第 i 位数为 0 时,10子序列会增加 j 个 , 同理,当第 i 位数为 1 时,01子序列会增加(i - j) 个 。于是得到状态转移方程:

当s[i] == ‘1’ 时 : dp[i][j + 1][k - cnt0] = min(dp[i][j + 1][k - cnt0] , dp[i - 1][j][k] ) \\\\\\ dp[i][j][k + cnt1] = min(dp[i][j][k + cnt1] , dp[i - 1][j][k] + 1)

当s[i] == '0' 时 :dp[i][j + 1][k - cnt0] = min(dp[i][j + 1][k - cnt0] , dp[i - 1][j][k] + 1) \\\\\\ dp[i][j][k + cnt1] = min(dp[i][j][k + cnt1] , dp[i - 1][j][k] )

由于是交换01数,所以最终整个串的1的数量已知,操作数为翻转01数的二分之一。最终只需要输出dp[n][cnt1][3000] / 2 即可。

        

int dp[105][105][6000]; //第i位,前面有 j 个1, 10比01多 z - 3000 个的修改次数 
void solve() 
{
	memset(dp , 0x3f , sizeof dp);
	dp[0][0][3000] = 0;
	string s;
	cin>>s;
	s = " " + s;
	int t = s.size();
	int cnt = 0;
	for(int i = 1; i < t; i ++){
		if(s[i] == '1')
			cnt++;
	}
	for(int i = 1; i < t ; i ++){
		for(int j = 0 ; j < i ; j ++){
			int cnt1 = j;
			int cnt0 = i - 1 - j;
			for(int z = 500 ; z <= 5500 ; z ++){
				if(s[i] == '1'){
					dp[i][j + 1][z - cnt0] = min(dp[i][j + 1][z - cnt0] , dp[i - 1][j][z]);//不改
					dp[i][j][z + cnt1] = min(dp[i][j][z + cnt1] , dp[i - 1][j][z] + 1);//改为0
				}
				else{
					dp[i][j + 1][z - cnt0] = min(dp[i][j + 1][z - cnt0] , dp[i - 1][j][z] + 1);//改为1
					dp[i][j][z + cnt1] = min(dp[i][j][z + cnt1] , dp[i - 1][j][z]);//不改
				}
			}
		}
	}
	cout<

读了jiangly大佬的代码以后,有了更牛逼的写法:

假设s[i] == 1,那么增加的 11 和 01 子序列的总数是一定是 i 。整个序列当中0和1的数量是已知的,那么11 、 00 子序列的数量也是可求的,又因为最终10 和 01 子序列的数量一样,所以也是可求的。那么最终11 和 01 子序列的总和也是已知的。因此我们只需要用dp[i][j][k] ,其中i为前 i 位, j 为前i位中1的总数,k为 11和01子序列的总数,其值表示01交换的操作数。

其状态转移方程为:dp[i][j + 1][k + i] = min(dp[i][j + 1][k + i] , dp[i - 1][j][k] + s[i] == '0' )

再用空间压缩的技巧可以把第一维给去掉。

const int INF = 1e9;
void solve() 
{
	string s;
	cin>>s;
	int n = s.size();
	int cnt1 = count(s.begin() , s.end() , '1');
	int cnt0 = n - cnt1;
	int need = (n * (n - 1) / 2 - cnt1 * (cnt1 - 1) / 2 - cnt0 * (cnt0 - 1) / 2) / 2 + cnt1 * (cnt1 - 1) / 2; // 11 和 01 子序列总和
	int dp[cnt1 + 5][need + 5];
	memset(dp , 0x3f , sizeof dp);
	dp[0][0] = 0;
	for(int i = 0 ; i < n ; i ++){//i表示有多少位数
		for(int j = cnt1 - 1 ; j >= 0 ; j --){//j表示前面有多少个1
			for(int k = 0 ; k + i <= need ; k ++){//k表示11 和 01 子序列的总和
				dp[j + 1][k + i] = min(dp[j + 1][k + i] , dp[j][k] + (s[i] == '0'));//将s[i]设为1
			}
		}
	}
	cout<

1860E - Fast Travel Text Editor 

        题意:给定一个字符串,其中存在一个光标位于相邻两个字符中间。光标的移动方式如下:1、向左移动一个单位。(不会移动到第一个前面)2、向右移动一个单位(不会移动到最后一个后面)3、假设光标相邻两个字符为"xy”,那么可以移动到字符串中任意一个"xy"字符中间。

共有q个询问,每个询问给出整数 f 和 t,要求给出光标从 f 位置移动到 t 位置需要最少多少步。假设下标从1开始,f = 1 就代表了光标在第一个字符和第二个字符中间。

        思路:最短路问题,每条边的权值为1。由于字符串的长度很大(5e4) , 而光标"xy”的可能值较少(26 * 26)。因此可以利用二维的[26][26]来代替字符串当中的下标。于是我们可以设dis[26][26][n]来代表字符串“xy”到整个字符串某个位置的距离。最终求解的时候只需要遍历"xy",求出“xy”到达f位置+“xy”到达t位置的最小值即可。其复杂度为O(26*26*5e4)。对于求dis[26][26][n]我们可以利用BFS来保证最短路。

        

void solve() 
{
	vectorp[26][26];//字符"xy"之间的光标所在的位置
	string s;
	cin>>s;
	int n = s.size();
	for(int i = 1 ; i < n ; i ++){
		p[s[i - 1] - 'a'][s[i] - 'a'].pb(i);
	}
	int dis[26][26][n + 5];//字符"xy"之间的光标到下标z的距离
	memset(dis,-1,sizeof dis);
	for(int i = 0 ; i < 26 ; i ++){
		for(int j = 0 ; j < 26 ; j ++){
			if(!p[i][j].empty()){
				queueq;
				bool isv[26][26];
				memset(isv , false , sizeof isv);
				isv[i][j] = true;
				for(auto x : p[i][j]){
					dis[i][j][x] = 0;
					q.push(x);
				}
				while(!q.empty()){
					int x = q.front();//当前光标下标为 x
					q.pop();
					if(x > 1 && dis[i][j][x - 1] == -1){//光标左移
						dis[i][j][x - 1] = dis[i][j][x] + 1;
						q.push(x - 1);
					}
					if(x < n - 1 && dis[i][j][x + 1] == -1){//光标右移
						dis[i][j][x + 1] = dis[i][j][x] + 1;
						q.push(x + 1);
					}
					int l = s[x - 1] - 'a' , r = s[x] - 'a';//将"xy"之间的光标移动到另一对"xy"之上
					if(!isv[l][r]){
						isv[l][r] = true;
						for(auto t : p[l][r]){
							if(dis[i][j][t] == -1){
								dis[i][j][t] = dis[i][j][x] + 1;
								q.push(t);
							}
						}
					}
				}
			}
		}
	}
	int q;
	cin>>q;
	while(q--){
		int x , y;
		cin>>x>>y;
		int ans = abs(x - y);
		for(int i = 0 ; i < 26; i ++){
			for(int j = 0 ; j < 26 ; j ++){
				if(!p[i][j].empty())
				ans = min(ans , dis[i][j][x] + dis[i][j][y] + 1);
			}
		}
		cout<

        

你可能感兴趣的:(算法,c++,数据结构)