codeforces global round 4 A-F2

参加了一次codeforces的比赛,写一写,剩下的题有空再补了。这篇文章最开始是从我的洛谷博客上写的,现在照搬过来https://www.luogu.org/blog/why112/codeforces-global-round-4-post

A Prime Minister

#define ll long long
#define rep(i,j,k)	for(int i=j;i<=k;i++)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#include<algorithm>
using namespace std;
int n;
int qwq[105];
int sum=0,co=1;
queue<int>yyy;
int main(){
	scanf("%d",&n);
	rep(i,1,n){
		scanf("%d",&qwq[i]);
		sum+=qwq[i];
	}
	int half=sum/2;
	if(qwq[1]>=half+1)	printf("1\n1");
	else{
		int aim=qwq[1];
		yyy.push(1);
		rep(i,2,n){
			if(qwq[i]<=qwq[1]/2){
				yyy.push(i);
				aim+=qwq[i];
				co++;
			}
		}
		if(co!=1){
			if(aim<=half)	printf("0\n");
			else{
				printf("%d\n",co);
				while(!yyy.empty()){
					printf("%d ",yyy.front());
					yyy.pop();
				}
			}
		}
		else	printf("0\n");
	}
	return 0;
}

刚开A题有点跳,所以说用了define rep这种比赛会被队友打死的写法······这个题就是签到题,按照题意说的敲就行。

B WOW Factor

这个题给出了一个长度最大为1e6的字符串,只包含v和o两个小写字母。两个v可以认为是一个w,问里面能找出多少个wow。这个题一开始想的是前缀和然后反着再来一遍,好愚蠢啊······直接统计一遍两个相邻的v的数量,然后再遍历一遍,碰到o就用前面的两个相邻的v的数量乘以后面的两个相邻的v的数量就行。没必要用前缀和保存下来各个值。细节见代码。

#define ll long long
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
char str[1000005];
ll ans=0;
int main(){
	scanf("%s",str);
	int len=strlen(str);
	ll total=0;
	for(int i=0;i<len;i++)	if(str[i]=='v'&&str[i+1]=='v')	total++;
	ll div=0;
	for(int i=0;i<len;i++){
		if(str[i]=='v'&&str[i+1]=='v')	div++;
		if(str[i]=='o')	ans+=div*(total-div);
	}
	cout<<ans<<endl;
	return 0;
}

C Tiles

这个题看起来很麻烦,给出一种瓷砖铺地板···这种瓷砖从对角线分开,一半黑色一半白色,可以随便旋转,但是同一条边的两侧不可以是同一种颜色。

经过研究我们可以发现,如果一个位置左边和上边的瓷砖确定了,那么这个位置只有一种摆放方式。换言之,如果我们把第一行和第一列的摆放方案确定了,其他位置也随之确定。所以说这个问题就转化为了求第一行和第一列的摆放方案。显然可知,是2的w+h次幂。这个题的代码直接上同余定理就行,反正数据贼小肯定不会超时。

#define ll long long
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
const int mod=998244353;
int w,h;
int main()
{
	scanf("%d%d",&w,&h);
	int add=w+h;
	ll ans=1;
	for(int i=0;i<add;i++){
		ans=ans*2%mod;
	}
	cout<<ans<<endl;
	return 0;
}

D Prime Graph

这道题给出一个n,然后有n个节点,分别是1,2,。。。,n,连成一个简单无向图,也就是说不能有多重边和自环。要求是每个点的度数必须是素数,而且边的总数也必须是素数。

我们很容易发现,我们可以一开始就把所有点连成一个环。如果n是素数,边的数量是n,是素数,每个点的度数是2,也是素数,就直接满足题意了。如果n不是素数,那么我们就在这个环中从1点开始,将其与对面的点连接,那么这两个点的度数都变成3,还是素数,重复这个操作,直到边数为素数。我队友证明了(太巨了)在这个操作进行到无法进行之前肯定能使边数为素数。这个证明大概就是n到n+n/2之间必定找到一个素数,如果一个正整数a满足这个定理,那么a的上一个素数到下一个素数之间的所有数都必定满足这个性质。具体的话有兴趣可以自己推一下。

#define ll long long
#include<iostream>
#include<cstdio>
#include<cstring>
#include<cmath>
#include<algorithm>
using namespace std;
int vis[2005];
int n;
inline void Es(){
	int a=2*n;
	int m=sqrt(a+0.5);
	memset(vis,0,sizeof(vis));
	for(int i=2;i<=m;i++)   if(!vis[i])
    	for(int k=i*i;k<=a;k+=i)    vis[k]=1;
}
int main(){
	scanf("%d",&n);
	Es();
	int coun=0;
	while(vis[n+coun])	coun++;
	printf("%d\n",n+coun);
	for(int i=1;i<n;i++)	printf("%d %d\n",i,i+1);
	printf("%d 1\n",n);
	int add=n/2,point=1;
	while(coun--){
		printf("%d %d\n",point,point+add);
		point++;
	}
	return 0;
}

E Archaeology

这个题也是给出一个长度最大为1e6的字符串,只包含a,b,c三个小写字母,而且不会出现有两个相邻的相同的字母。然后在这一个字符串中找一个子串,长度大于等于[n/2],并且是个回文串。如果找不到那么就输出“IMPOSSIBLE”。

仔细想想,肯定不会有IMPOSSIBLE啊。在长度大于3的字符串中,任选两段包含有两个字母的子段,必定至少有一个相同的字母。所以说找出一个回文子串,肯定能找到长度大于等于[n/2]的。如果长度小于3,随便从里面挑一个字母输出就行。

这个题同理也这样做,设left=0,right=len-1;然后比较前两个和后两个,有相同的就输出,然后移动left和right的位置,依然是这样找相同的字母。注意找完之后要输出一个中间的字母使长度为奇数,否则第23组测试样例就会搞死你······

还是我最先想到咋做这个题的,结果我队友先AC了······郁闷。就是被第23组样例搞了,,,最开始还以为是我的stack炸了,这个数据量也不至于炸吧。。。

#define ll long long
#include<iostream>
#include<cstdio>
#include<stack>
#include<cstring>
#include<algorithm>
using namespace std;
char str[1000005];
//stack<char>sta;
char qwq[5000005];
int main(){
	scanf("%s",str);
	int len=strlen(str);
	if(len<=3){
		printf("%c\n",str[0]);
		return 0;
	}
	int left=0,right=len-1,point=0;
	while(left<right-2){
		if(str[left]==str[right]){
			printf("%c",str[left]);
			//sta.push(str[right]);
			qwq[point]=str[right];
			left++;
			right--;
		}
		else	if(str[left]==str[right-1]){
			printf("%c",str[left]);
			//sta.push(str[right-1]);
			qwq[point]=str[right-1];
			left++;
			right-=2;
		}
		else	if(str[left+1]==str[right]){
			printf("%c",str[left+1]);
			qwq[point]=str[right];
			//sta.push(str[right]);
			left+=2;
			right--;
		}
		else	if(str[left+1]==str[right-1]){
			printf("%c",str[left+1]);
			qwq[point]=str[right-1];
			//sta.push(str[right-1]);
			left+=2;
			right-=2;
		}
		else{
			left++;
			right--;
		}
		point++;
	}
	/*while(!sta.empty(
		printf("%c",sta.top());
		sta.pop();
	}*/
	printf("%c",str[left]);
	point--;
	while(point>=0){
		printf("%c",qwq[point]);
		point--;
	}
	return 0;
}

F1 Short Colorful Strip

这个题的意思是给出一个长m的纸带,有n种颜色,每种颜色有一个标号,然后给出染色的最终状态。我们染色的话是从颜色1开始,按从1到n的顺序来进行染色。我们可以对一个区间进行染色,但是这个区间内必须是单一颜色的。纸带的初始颜色为0。问有多少种染色方案可以达到给出的染色最终状态。

这个题用记忆化搜索,对于每一次搜索,我们选定一个区间[left,right],然后搜索这个区间内的方案数。由于我们是按颜色标号顺序进行染色的,所以应该首先找出这个区间内的最小颜色标号p,然后我们知道,从这个颜色标号染色,可以染一格,两格···

在这个图里,我们可以看到从i到j被染成了颜色p,那左区间的方案数为a×b ,右区间的方案数为c×d,那么这整个区间的方案数就是左区间×右区间即a×b×c×d。那么根据这个原理就可以把代码写出来了。

#define ll long long
#define mod 998244353
#define rep(i,j,k)	for(int i=j;i<=k;i++)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
ll dp[505][505]={0};
int qwq[505];
int n,m;
ll dfs(int left,int right){
	if(dp[left][right])	return dp[left][right];
	if(left>=right)	return 1;
	int p=left;
	rep(i,left,right)	if(qwq[i]<qwq[p])	p=i;
	ll ans1=0,ans2=0;
	rep(i,left,p){
		ans1=(ans1+dfs(left,i-1)*dfs(i,p-1)%mod)%mod;
	}
	rep(i,p,right){
		ans2=(ans2+dfs(p+1,i)*dfs(i+1,right)%mod)%mod;
	}
	ll ans=ans1*ans2%mod;
	dp[left][right]=ans;
	return ans;
}
int main(){
	scanf("%d%d",&n,&m);
	rep(i,1,n)	scanf("%d",&qwq[i]);
	cout<<dfs(1,n)<<endl;
	return 0;
}

要注意在dfs中,p一开始一定是left而不是1,因为这是在一个区间内查找最先开始染色的部分。如果是1的话,就会出现死循环(而且也不符合逻辑)。感觉这个题就是纯套路····

F2 long clorful strip

和F1题意类似,只是颜色数最多500,纸带最长1e6,m>=n。这样的话,就有可能出现重复的颜色。我们知道,我们对一个区间染色的话,从标号最小开始,而且染色区间颜色必定是单一的。由于F1中不会有重复的颜色,所以F1中并不会出现无解的情况。然而在F2中,比如2 1 2这种情况,便是无解的情况。我们染2这个颜色,区间[1,3]必定得全部染成2,然而染2之前,情况可能有0 1 0,1 1 0,0 1 1,1 1 1,只有最后一种情况1 1 1可以染色,然而染色之后就变成2 2 2了,无法达到2 1 2,所以说这种情况便是无解的。

无解情况的判定:由题意我们可以知道,如果说有解的话,必须是标号小的颜色区间包含标号大的颜色区间,或者说并列,比如1 2 1或者1 2都是有解的,像是2 1 2便是无解的。如果说相邻的颜色相同,我们可以将其合并成一个,比如说1 1 2 3 3的结果和1 2 3的结果是相同的。所以说,我们可以知道,在有解的情况下,去重之后,纸带的最大长度是2n-1,即1 2 … 2 1这种情况。所以说我们输入的时候进行去重操作之后,如果纸带长度还大于等于2n,必定是无解的情况。同时我们也记录每种颜色的最左下标和最右下标,在对区间[L,R]进行搜索时,如果其中的颜色的最左下标和最右下标不在区间内,那么必定是无解的情况。

这个题的状态转移方程和F1类似的思路,我们可以知道,在一个区间中,被划分为三块区域。如图:

相比F1,我们要染的颜色中也是一个区间,那么根据F1稍作修改,那么就是左区间的方案数×染色区间的方案数×右区间方案数。左区间和右区间的操作和F1基本差不多,然而在中间的染色,我们应该是dfs(L+1,M1-1)×dfs(M1+1,M2-1)×…×dfs(Mn+1,R-1)。这个题就出来了。不过这个题还是很细节的,比如说我们有可能返回为0这个操作,那么我们记忆化搜索的时候,应该先把数组都初始化为-1,如果不是-1那么就直接返回,而不是初始化为0.据猜测可能是有大量的递归会找到为0的操作,然而每次都得在区间内重新扫描一遍确定区间内部优先染色的颜色,然后再判定区间相交问题,所以一开始会被卡住。。。

我写代码,先是自定义了结构体sets(最开始用的类,还以为类拖慢了速度),然后构建一个数组yyy,yyy[p]中存有颜色p的最左下标和最右下标以及颜色p的所有下标。然后利用这个配合状态转移方程进行搜索。

#pragma once
#pragma GCC optimize("Ofast")
#define mod	998244353
#define ll long long
#define rep(i,j,k)	for(int i=j;i<=k;i++)
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct sets{
		int length,left,right;
		int position[1005];
		sets(){
			length=0;
			left=1005;
			right=0;
		} 
		void insert(int a){
			position[length++]=a;
			if(a<left)	left=a;
			else	if(a>right)	right=a;	
			if(length==1)	right=left;
		}
};
int n,m;
int qwq[1000005];
sets yyy[510];
int vis[1010][1010];
ll dfs(register int left,register int right){
	//cout<<"starts:left="<<left<<"right="<<right<<endl;
	if(left>right)	return 1;
	if(vis[left][right]!=-1)	return vis[left][right];
	int pri=qwq[left];
	rep(i,left+1,right){
		if(qwq[i]<pri){
			pri=qwq[i];
		}
	}
	if(left==right){
		if(yyy[pri].left<left||yyy[pri].right>right){//区间相交必定是不可到达的状态 
			//printf("left=%d,right=%d,yyy[pri].left=%d,yyy[pri].right=%d,pri=%d\n",left,right,yyy[pri].left,yyy[pri].right,pri);
			vis[left][right]=0;
			return 0;
		}
		else{
			//printf("There returned 1\n");
			vis[left][right]=1;
			return 1;
		}
	}
	ll ans1=0,ans2=0,ans3=1;
	if(left==yyy[pri].left)	ans1=1;
	else{
		rep(i,left,yyy[pri].left){
			ans1=(ans1+dfs(left,i-1)*dfs(i,yyy[pri].left-1)%mod)%mod;
		}
	}
	if(yyy[pri].right==right)	ans2=1;
	else{
		rep(i,yyy[pri].right,right){
			ans2=(ans2+dfs(yyy[pri].right+1,i)*dfs(i+1,right)%mod)%mod;
		} 
	}
	if(yyy[pri].length==1)	ans3=1;
	else{	
		rep(i,0,yyy[pri].length-1){
			ans3=ans3*dfs(yyy[pri].position[i]+1,yyy[pri].position[i+1]-1)%mod;
		}
	}
	//cout<<ans1<<" "<<ans2<<" "<<ans3<<endl;
	vis[left][right]=(ans1*ans2%mod)*ans3%mod;
	return vis[left][right];
}
int main(){
	int te;
	scanf("%d%d",&n,&m);
	rep(i,1,m){
		scanf("%d",&te);
		if(i!=1&&te==qwq[i-1]){//去重 
			i--;
			m--;
		}
		else{
			qwq[i]=te;
			yyy[te].insert(i);//记录一种颜色的最左和最右下标 
		}
	}
	if(m>=2*n){
		printf("0\n");
		return 0;
	}
	memset(vis,-1,sizeof(vis));
	printf("%I64d\n",dfs(1,m));
	return 0;
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值