【Codeforces】Codeforces之丰【部分题解】

Codeforces之丰

[by_041]

Codeforces Round #721 (Div. 2)

Codeforces Round #721 (Div. 2)

A. And Then There Were K

Problem - A - Codeforces

  • 题意是求满足【 n & ( n − 1 ) & ( n − 2 ) & ( n − 3 ) & . . . ( k ) = 0 n\&(n−1)\&(n−2)\&(n−3)\&...(k)=0 n&(n1)&(n2)&(n3)&...(k)=0】要求最大的k值
  • 位运算→二进制下处理
  • 要与后所有位是0,k在n最高位处是0,而在这个过程中一定会经过只有此位为1的数( 2 x , x = f l o o r ( l o g 2 n ) 2^x,x=floor(log_2n) 2x,x=floor(log2n)),所以只要输出【 2 x − 1 2^x-1 2x1】就行了
#include<bits/stdc++.h>

using namespace std;



int main()
{
    
    
	int T,n,nn,i;
	bitset<10>num;
	cin>>T;
	while(~scanf("%d",&n))
	{
    
    
		nn=1;i=n;
		while(i>>=1)nn<<=1;
		printf("%d\n",nn-1);
	}
	return 0;
}

B1. Palindrome Game (easy version)

Problem - B1 - Codeforces

  • 题意就是给一个01字串每次可以进行两种操作

    • 花费1dollar:将一个0转化为1
    • 不花费:将字串reverse,前提是上一步不是reverse而且当前串不回文

    Alice先手,Bob后手,字串全为1结束,俩人都是智者,对于给出的情况判断谁稳赢

    对于 easy version 给出的字串原本就是回文的

  • 明显博弈论,首先看看以前的经验:

    • 博弈论解法
      1. 确定其中一方必胜情况
      2. 增加一步反转情况
      3. 重复2操作,反推得到给出现状的博弈结果
      4. 实现博弈结果判定
  • 对于0有两种情况:

    • cas1 : …0…|…0…(对称位置也是0)
    • cas2 : …0…|…1…(对称位置不是0)在此处的原串中不会出现就是了
  • 没有0:一定平局

  • 对于只有cas1的0又分为两种情况:

    • 处理一般的 …0…|…0… ,必需要当下的选手付出1dollar

      • 另一方旋转(reverse),此方再付1dollar,将剩下同样的情况还给对方,虽致使对方可能在下一步(如果有,即还有0的情况下)要至少再付出1dollar,不过对于最后一手来说是对方赚(2dollar)的,所以对方会引导最后的这一步发生在该方的步子里
      • 或者,另一方付出1dollar,将剩下同样的情况继续留给次方,这样付出相平,在不改变手顺的情况下去除了一个cas1的情况

      由此,当场面只有这种非对称中心的对称的一对或几对0存在时,先手将亏2dollar,后手胜

    • 对于处理对称中心是0的情况

      • Alice若在第一步选择花费1dollar将其转化为0,就成为了只有上一种情况的后手,在有上一种情况的时候A赚2-1=1dollar,A必胜,在没有上一种情况的时候A亏1dollar,B必胜
      • 若A不转化,因为原串为回文串,必须转化一个0到1,其他的选择只会更劣
  • 因为是 easy version 所以只需要考虑cas1存在的情况,cas2存在的情况放到 hard version 考虑

#include<iostream>

using namespace std;


int main()
{
    
    
	int T,n,cas1,cas2,anss=0;//anss means is good to Alice
	char str[1007];
	cin>>T;
	while(T--)
	{
    
    
		scanf("%d",&n);
		getchar();
		for(int i=0;i<n;i++)
			str[i]=getchar();
		str[n]='\0';
		// cout<<str<<endl;
		cas1=cas2=0;
		for(int i=0,m=(n+1)/2-1;i<=m;i++)
		{
    
    
			if(str[i]==str[n-i-1])
			{
    
    
				if(str[i]=='0')
				{
    
    
					cas1+=2;//..0..|..0.. *2
				}
			}
			else//B1 will no come to this
			{
    
    
				cas2++;//..1..|..0.. or ..0..|..1..
			}
		}
		cas1-=((n&1)&&(str[(n+1)/2-1]=='0'));
//		 printf("cas1 = %2d ; cas2 = %2d ;\n",cas1,cas2);
		anss=(cas1&1?(cas1==1?-1:1):(cas1==0?0:-2));
		if(cas2>0)
		{
    
    
			if(anss>=0)
				anss+=cas2;
			else
				anss=cas2-2+abs(anss);
		}
		if(anss)
		{
    
    
			if(anss>0)
				puts("ALICE");
			else
				puts("BOB");
		}
		else
			puts("DRAW");
	}
	return 0;
}

B2. Palindrome Game (hard version)

Problem - B2 - Codeforces

  • 题意还是给一个01字串每次可以进行两种操作

    • 花费1dollar:将一个0转化为1
    • 不花费:将字串reverse,前提是上一步不是reverse而且当前串不回文

    Alice先手,Bob后手,字串全为1结束,俩人都是智者,对于给出的情况判断谁稳赢

    对于 hard version 给出的字串不一定原本就是回文的

  • 同上,对于0有两种情况:

    • cas1 : …0…|…0…(对称位置也是0)
    • cas2 : …0…|…1…(对称位置不是0)在此处的原串中不会出现就是了
  • 没有0:一定平局

  • 对于只有cas1的0又分为两种情况:

    • 处理一般的 …0…|…0… ,必需要当下的选手付出1dollar

      • 另一方旋转(reverse),此方再付1dollar,将剩下同样的情况还给对方,虽致使对方可能在下一步(如果有,即还有0的情况下)要至少再付出1dollar,不过对于最后一手来说是对方赚(2dollar)的,所以对方会引导最后的这一步发生在该方的步子里
      • 或者,另一方付出1dollar,将剩下同样的情况继续留给次方,这样付出相平,在不改变手顺的情况下去除了一个cas1的情况

      由此,当场面只有这种非对称中心的对称的一对或几对0存在时,先手将亏2dollar,后手胜

    • 对于处理对称中心是0的情况

      • Alice若在第一步选择花费1dollar将其转化为0,就成为了只有上一种情况的后手,在有上一种情况的时候A赚2-1=1dollar,A必胜,在没有上一种情况的时候A亏1dollar,B必胜
      • 若A不转化,因为原串为回文串,必须转化一个0到1,其他的选择只会更劣
  • 对于有cas2的情况:

    • Alice看到当前局面不是回文串高兴坏了,立马连续cas2次选择反转(reverse)操作,Bob只得cas2次花费1dollar消除这种情况使自己早日推理连续亏损的状态

    • 到最后一次时

      • 若Alice放弃翻转字串,用1dollar选择将最后一个不对称的0转化为1,则会让Bob面临上面只有cas1的情况
      • 若A继续反转,则会让自己面临只有cas1的情况

      我们可以看到,在只有cas1的时候,先手的结局有亏2dollar、亏1dollar和赚1dollar,而Alice为了让自己得利,会在只剩cas1后手有利时放弃最后一次的翻转,否则不然

  • 根据上述分析就能算出A最后的亏盈(在下方程序中的anss)了,根据正负性判断结果输出即可

#include<iostream>

using namespace std;


int main()
{
    
    
	int T,n,cas1,cas2,anss=0;//anss means is good to Alice
	char str[1007];
	cin>>T;
	while(T--)
	{
    
    
		scanf("%d",&n);
		getchar();
		for(int i=0;i<n;i++)
			str[i]=getchar();
		str[n]='\0';
		// cout<<str<<endl;
		cas1=cas2=0;
		for(int i=0,m=(n+1)/2-1;i<=m;i++)
		{
    
    
			if(str[i]==str[n-i-1])
			{
    
    
				if(str[i]=='0')
				{
    
    
					cas1+=2;//..0..|..0.. *2
				}
			}
			else
			{
    
    
				cas2++;//..1..|..0.. or ..0..|..1..
			}
		}
		cas1-=((n&1)&&(str[(n+1)/2-1]=='0'));
//		 printf("cas1 = %2d ; cas2 = %2d ;\n",cas1,cas2);
		anss=(cas1&1?(cas1==1?-1:1):(cas1==0?0:-2));
		if(cas2>0)
		{
    
    
			if(anss>=0)
				anss+=cas2;
			else
				anss=cas2-2+abs(anss);
		}
		if(anss)
		{
    
    
			if(anss>0)
				puts("ALICE");
			else
				puts("BOB");
		}
		else
			puts("DRAW");
	}
	return 0;
}

Codeforces Round #723 (Div. 2)

A. Mean Inequality

  • Problem - A - Codeforces

  • 题意就是给 2 n 2n 2n个数排成环,要求一个排列使得相邻的三个数不等差(不存在一个数是两侧数的平均数)

  • 把该数组分为排序后均分为两个长度为n的部分(大于中位数和小于中位数的),交叉摆放就行(大的中间夹的都是小的,小的中间夹的都是大的),因为大的的平均数一定大于中位数,小的的平均数一定小于中位数,所以这样是一定可行的

  • 注意初始数组(未拆分前)分配空间要有2n个元素

#include<bits/stdc++.h>

using namespace std;



int main()
{
    
    
	int T,n,nn,a[55],b[55];//空间啊啊啊啊啊啊,2n啊啊啊啊
	cin>>T;
	while(T--)
	{
    
    
		scanf("%d",&n);
		nn=n*2;
		for(int i=0;i<nn;i++)
			scanf("%d",a+i);
		sort(a,a+nn);
		for(int i=0;i<n;i++)
			b[i]=a[i+n];

		printf("%d %d",a[0],b[n-1]);
		for(int i=1;i<n;i++)
			printf(" %d %d",a[i],b[n-i-1]);
		putchar('\n');
	}
	return 0;
}

B. I Hate 1111

  • Problem - B - Codeforces

  • 题意是给T个数,分别判断每个数能否被拆分成若干个 { 11 , 111 , 1111 , 11111 , . . . } \{11,111,1111,11111,...\} { 11,111,1111,11111,...}的和

  • 由于 { 1111 , 11111 , . . . } \{1111,11111,...\} { 1111,11111,...}都可以被 { 11 , 111 } \{11,111\} { 11,111}组成,所以只要看其被减去若干个111后存不存在被11整除的情况即可

  • 所以问题就变成了是否存在俩正整数使得【 111 × a + 11 × b = n 111\times a+11\times b=n 111×a+11×b=n】成立,于是就有了以下俩方法:(分别 O ( n 111 ) 、 O ( 1 ) O(\frac n{111})、O(1) O(111n)O(1)

#include<bits/stdc++.h>

using namespace std;


//1111,11111,111111...都可以被111和11组成,所以只要拆分成若干的111和11就行

int main()
{
    
    
	int T,n;
	cin>>T;
	while(T--)
	{
    
    
		scanf("%d",&n);
		while(n>=0)
		{
    
    
			if(n%11==0)
			{
    
    
				puts("YES");
				n=0;
				break;
			}
			n-=111;
		}
		if(n)
			puts("NO");
	}
	return 0;
}
#include<bits/stdc++.h>

using namespace std;


//1111,11111,111111...都可以被111和11组成,所以只要拆分成若干的111和11就行

int main()
{
    
    
	int T,n;
	cin>>T;
	while(T--)
	{
    
    
		scanf("%d",&n);
		int x111=n%11;		//111=11*10+1,所以n可拆分成111和11的时候111的数是可以直接算的
		n-=x111*111;
		if(n>=0&&n%11==0)
			puts("YES");
		else
			puts("NO");
	}
	return 0;
}

C1. Potions (Easy Version)

C2. Potions (Hard Version)

  • Problem - C2 - Codeforces

  • 俩题一样,就是C2数据范围比C1大

  • 题意是给一串数列,一人从左往右走一遍,中间可以选择是否选取走到的数字,过程中不能出现所拿的数字是负数,求到终点时最多拿了几个数字

  • 首先非负的数字一定要拿,每次拿负数后看一下会不会使当前的值变为负数,否则从之前包括当前位置的负数中选取最负的一个丢弃,可以用优先队列(priority_queue)完成这个操作的维护

  • 注意数据的和会很大,需要用到long long

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

int main()
{
    
    
	int n,anss=0;
	scanf("%d",&n);
	priority_queue<ll,vector<ll>,greater<ll> >q;
	ll sum=0;
	for(int i=0,val;i<n;i++)
	{
    
    
		scanf("%d",&val);
		if(val>=0)
		{
    
    
			sum+=val;
			anss++;
		}
		else
		{
    
    
			q.push(val);
			sum+=val;
			anss++;
			if(sum<0)
			{
    
    
				sum-=q.top();
				anss--;
				q.pop();
			}
		}
	}
	cout<<anss<<endl;
	return 0;
}

D. Kill Anton

  • Problem - D - Codeforces

  • 题意是给一个只含有{'A','N','O','T'}的字串要求求给字串的一个用交换相邻两个字符的操作数最多的排列,值得注意的是这是个可逆的操作,也就是说我们可以从初始串入手经过有效次

  • 其实题意就是要相对于原序列的求逆序对最大的序列

  • 先从简单但稍微不那么明显的情况入手(这里说的是有一个重复的字符的情况,如果一个都没有就显而易见的倒序输出就行了)

    A N O A → O N A A   ( o r   A A O N   i s   t h e   s a m e ) ANOA\rarr ONAA\space(or\space AAON\space is\space the\space same) ANOAONAA (or AAON is the same),字母太多没看出啥,那就继续,换一个角度,只注意单独一种字符的位置(这里以 A A A为例),观察:【 x 0 . . . x i A x i + 1 . . . x j A x j + 1 . . . x k → ? x_0...x_iAx_{i+1}...x_jAx_{j+1}...x_k\rarr ? x0...xiAxi+1...xjAxj+1...xk?——①】,不难发现如果移动一个 A A A并固定其位置后,都可让其中另一个 A A A与这个 A A A中间的字符交换(也就是达到俩 A A A相邻的情况)会让结果更大,甚至在左边的 A A A移动到右边的 A A A的右侧时也一样成立。用三个 A A A的例子:【 x 0 . . . x i A 1 x i + 1 . . . x j A 2 x j + 1 . . . x k A 3 x k + 1 . . . x e n d → ? x_0...x_iA_1x_{i+1}...x_jA_2x_{j+1}...x_kA_3x_{k+1}...x_{end}\rarr ? x0...xiA1xi+1...xjA2xj+1...xkA3xk+1...xend?——②】,在 x i + 1 x_{i+1} xi+1 x e n d x_{end} xend的子序列就是①的情况,处理完这个子序列,我们将合并的 A 2 A 3 A_2A_3 A2A3看作一个 A 2 A_2 A2,剩下的序列又可以看成一个①情况,得到的结果可以再合并。。。这个道理在更多 A A A的时候也成立,所以我们可以得到结论:所求的结果一定是相同字符合并在一起的情况

  • 从上面的结论出发,我们可以得到一个算法:全排列{'A','N','O','T'} 4 ! = 24 4!=24 4!=24种),求出所有情况下需要交换的次数sep( O ( n ) O(n) O(n)),保留最大的sep对应的排列ord,作为结果输出。时间复杂度为 O ( 24 n ) → O ( n ) O(24n)\rarr O(n) O(24n)O(n)

  • 计算这个值的时候只要便利原字符串吧排序在当前字符前的其他字符的数量相加就行,排完这个字符就把这个字符的数量-1从而维护后面的交换次数的计算

  • 其中要注意这个步数可能超过int范围!!!

#include<bits/stdc++.h>

using namespace std;

typedef long long ll;

int main()
{
    
    
	int T,n,i,j;
	ll sep,anss;
	cin>>T;
	char str[100007];
	char ord[5]={
    
    'A','N','O','T'},ans_str[5]={
    
    'A','N','O','T'};
	while(T--)
	{
    
    
		scanf("%s",str);
		n=strlen(str);
		anss=0;

		map<char,int>m,mm;
		for(int i=0;i<n;i++)
			m[str[i]]++;
		do{
    
    
			mm=m;
			sep=0;
			for(i=0;i<n;i++)
			{
    
    
				for(j=0;ord[j]!=str[i];j++)
				{
    
    
					sep+=mm[ord[j]];
				}
				mm[str[i]]--;
				if(sep>anss)
				{
    
    
					strcpy(ans_str,ord);
					anss=sep;
				}
			}
			// cout<<sep<<" - ";
			// mm=m;
			// for(int i=0;i<4;i++)
			// {
    
    
			// 	while(mm[ord[i]]--)
			// 		putchar(ord[i]);
			// }
			// putchar('\n');
		}while(next_permutation(ord,ord+4));

		// cout<<anss<<" - ";
		for(int i=0;i<4;i++)
		{
    
    
			while(m[ans_str[i]]--)
				putchar(ans_str[i]);
		}
		putchar('\n');
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_42710619/article/details/117385396
今日推荐