参加了一次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;
}