• Codeforces Round #605 (Div. 3) 题解


    传送门

    A. Three Friends

    题意

    给三个数 (a,b,c),每个数都可以 (+1,-1) 或不变,问对每一个数操作至多一次后 (|a'-b'|+|a'-c'|+|b'-c'|) 的最小值。

    思路

    暴力枚举每个数操作后的值,然后记录最小结果就行了

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    int T,a[5];
    
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		int a,b,c;
    		scanf("%d%d%d",&a,&b,&c);
    		LL res=0x3f3f3f3f3f3f3f;
    		for(int i=-1;i<=1;i++)
    			for(int j=-1;j<=1;j++)
    				for(int k=-1;k<=1;k++){
    					int x=a+i,y=b+j,z=c+k;
    					res=min(res,1ll*abs(x-y)+abs(x-z)+abs(y-z));
    				}
    		printf("%lld
    ",res);
    	}
    	return 0;
    }
    

    B. Snow Walking Robot

    题意

    给一个字符串描述路径,要求从中删除尽量少的字符,使得从 ((0,0)) 沿着按照删改后的字符串走,可以走回 ((0,0)),并且除了 ((0,0)) 不会经过一个点两次

    思路

    统计'U'、'R'、'D'、'L'的个数,记作 (U,R,D,L)。从 ((0,0)) 出发,要走回 ((0,0)) 的话,那么显然 (U=D,R=L),那么首先需要使 (U=D=min(U,D),R=L=min(R,L)),然后按照先上,再右,再下,再左转一圈就可以走完。此时要注意,如果删改后 (U=D=0) 或者 (R=L=0),此时当然就只能走出一步然后马上回来,因为在这种情况下,只能原路返回,如果走多了,沿途的点也会被经过两次了,所以这种情况必须要特判掉。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN=1e5+10;
    int T;
    char s[MAXN];
    
    int main(){
    	scanf("%d",&T);
    	while(T--){
    		scanf("%s",s+1);
    		int n=strlen(s+1);
    		int U=0,R=0,D=0,L=0;
    		for(int i=1;i<=n;i++)
    			if(s[i]=='U') U++;
    			else if(s[i]=='R') R++;
    			else if(s[i]=='D') D++;
    			else if(s[i]=='L') L++;
    		U=D=min(U,D);
    		R=L=min(L,R);
    		if(!U&&!D&&R&&L) L=R=1;
    		if(!L&&!R&&U&&D) U=D=1;
    		printf("%d
    ",U+D+L+R);
    		for(int i=1;i<=U;i++) printf("U");
    		for(int i=1;i<=R;i++) printf("R");
    		for(int i=1;i<=D;i++) printf("D");
    		for(int i=1;i<=L;i++) printf("L");
    		printf("
    ");
    	}
    	return 0;
    }
    

    C. Yet Another Broken Keyboard

    题意

    给一个字符串 (s)(k) 个字母,问 (s) 的只包含这 (k) 种字母的子串个数。

    思路

    假设 (s[i,j]) 合法,那么它的子串一定合法,那么就会对答案产生贡献 (frac{(j-i+1)(j-i+2)}{2})
    所以选出每一段合法区间,然后累加到答案上去就行了。

    代码

    #include <bits/stdc++.h>
    using namespace std;
    typedef long long LL;
    const int MAXN=2e5+10;
    int n,k,can[300];
    char s[MAXN],temp[10];
    
    int main(){
    	scanf("%d%d",&n,&k);
    	scanf("%s",s+1);
    	for(int i=1,x;i<=k;i++){
    		scanf("%s",temp);
    		can[temp[0]]=1;
    	}
    	LL ans=0;
    	int j=0;
    	for(int i=1;i<=n;i++){
    		if(can[s[i]]) j++;
    		else{
    			ans+=1ll*j*(j+1)/2;
    			j=0;
    		}
    	}
    	ans+=1ll*j*(j+1)/2;
    	printf("%lld
    ",ans);
    	return 0;
    }
    

    D. Remove One Element

    题意

    给一个序列,你可以从种删去至多一个数,问最长连续上升子序列的长度。

    思路

    (f[i][0/1]) 为以第 (i) 位结尾,未删除数/已删除数能得到的最长连续上升子序列的长度。
    首先 (f[i][0]) 这个状态很好做,就是普通的最长连续上升子序列的长度:

    [f[i][0]=f[i-1][0]+1 (a[i]>a[i-1])\ f[i][0]=1 (a[i]le a[i-1]) ]

    (f[i][1]) 这个状态也不难,显然有两种情况会出现这种状态:
    1.把 (a[i]) 接到以 (a[i-1]) 结尾且已删除数的最长上升连续子序列后。
    2.把 (a[i]) 接到以 (a[i-2]) 结尾且未删除数的最长上升连续子序列后,显然这种情况是将 (a[i-1]) 删去了。

    [f[i][1]=f[i-1][1]+1 (a[i]>a[i-1])\ f[i][1]=f[i-2][0]+1 (a[i]>a[i-2])\ f[i][1]=1 (任何情况下) ]

    #include <bits/stdc++.h>
    using namespace std;
    const int MAXN=2e5+10;
    int n,a[MAXN],f[MAXN][2];
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    	int ans=0;
    	for(int i=1;i<=n;i++){
    		f[i][0]=f[i][1]=1;
    		if(a[i]>a[i-1]){
    			f[i][0]=f[i-1][0]+1;
    			f[i][1]=f[i-1][1]+1;
    		} 
    		if(a[i]>a[i-2]) f[i][1]=max(f[i][1],f[i-2][0]+1);
    		ans=max(ans,max(f[i][0],f[i][1]));
    	}
    	printf("%d
    ",ans);
    	return 0;
    }
    

    E. Nearest Opposite Parity

    题意

    给一个 (n) 个数的数列 (a_1,a_2,...,a_n),第 (i) 个数可以跳一次跳到第 (i'=i-a_i(i-a_igeq 1)) 个数或第 (i'=i+a_i(i+a_ileq n)) 个数上,问每个数最少跳几次,使得 (a_i)(a_{i'}) 的奇偶性不同。

    思路

    按理来说可以通过搜索来解决这道题的,但是直接搜索当然会超时,而我加上记忆化之后就 WA 了,所以我换了一种方法,建图之后一遍广搜可以解决。
    说一下怎么建图
    如果已知 (ans_i),那么如果 (j+a_j=i)(a_j\%2=a_i%2),那么 (ans_j=ans_i+1)
    所以如果 (a_i\%2=a_{i+a_i}\%2),那么就可以建立一条 (i+a_i->i) 的有向边,
    (i-a_i)(i) 同理
    显然可以轻松的到 (ans_i=1) 的所有位置,那么可以从这些位置开始广搜即可

    代码

    #include <bits/stdc++.h>
    using namespace std;
    const int inf=0x3f3f3f3f;
    const int MAXN=2e5+10;
    int n,a[MAXN],ans[MAXN];
    int head[MAXN],to[MAXN*2],nxt[MAXN*2],tot=0;
    queue<int> que;
    
    void add(int u,int v){
    	to[++tot]=v;nxt[tot]=head[u];head[u]=tot;
    }
    
    int main(){
    	scanf("%d",&n);
    	for(int i=1;i<=n;i++)
    		scanf("%d",&a[i]);
    	memset(ans,0x3f,sizeof(ans));
    	for(int i=1;i<=n;i++){
    		if(i-a[i]>=1&&a[i-a[i]]%2==a[i]%2) add(i-a[i],i);
    		if(i+a[i]<=n&&a[i+a[i]]%2==a[i]%2) add(i+a[i],i);
    	}
    	for(int i=1;i<=n;i++)
    		if(i-a[i]>=1&&a[i-a[i]]%2!=a[i]%2||i+a[i]<=n&&a[i+a[i]]%2!=a[i]%2)
    			ans[i]=1,que.push(i);
    	while(!que.empty()){
    		int u=que.front();que.pop();
    		for(int i=head[u];i;i=nxt[i])
    			if(ans[to[i]]>ans[u]+1){
    				ans[to[i]]=ans[u]+1;
    				que.push(to[i]);
    			}
    	}
    	for(int i=1;i<=n;i++) printf("%d ",ans[i]==inf?-1:ans[i]);
    	return 0;
    }
    

    F. Two Bracket Sequences

    题意

    给两个括号序列 (s1,s2),要求构造一个最短的规范的括号序列 (ans),且满足 (s1,s2)(ans) 的子序列。

    思路

    广搜
    假定已经有了一个构造了序列 ((i,j,k))
    (i)(s1) 的前 (i) 位是目前序列的子序列
    (j)(s2) 的前 (j) 位是目前序列的子序列
    (k)(k=左括号-右括号)
    那么就需要从 ((0,0,0)) 构造到 ((len_{s1},len_{s2},0))
    如果将这个三元组看作是三维空间中的点的话,其实要求的就是从 ((0,0,0))((len_{s1},len_{s2},0)) 的最短路径。
    因为这个从一个点到另一个点的距离就是 (1),那么求最短路可以直接广搜。
    现在来看两个点之间的关系
    已知括号序列 ((i,j,k))
    1.如果向该序列添加一个 '(' (可以理解为点 ((i,j,k)) 向另一个点走)
    如果 (s1[i+1]='(',s2[j+1]='('),可以构成序列 ((i+1,j+1,k+1)) (点 ((i,j,k)->(i+1,j+1,k+1)))
    如果 (s1[i+1]='(',s2[j+1] ot='('),可以构成序列 ((i+1,j,k+1)) (点 ((i,j,k)->(i+1,j,k+1)))
    如果 (s1[i+1] ot='(',s2[j+1]='('),可以构成序列 ((i,j+1,k+1)) (点 ((i,j,k)->(i,j+1,k+1)))
    如果 (s1[i+1] ot='(',s2[j+1] ot='('),可以构成序列 ((i,j,k+1)) (点 ((i,j,k)->(i,j,k+1)))
    2.如果向该序列添加一个 ')' (此时 (kgeq 0),因为要规范的括号序列,任意一个前缀中右括号都不能比左括号多)
    如果 (s1[i+1]=')',s2[j+1]=')'),可以构成序列 ((i+1,j+1,k-1)) (点 ((i,j,k)->(i+1,j+1,k-1)))
    如果 (s1[i+1]=')',s2[j+1] ot=')'),可以构成序列 ((i+1,j,k-1)) (点 ((i,j,k)->(i+1,j,k-1)))
    如果 (s1[i+1] ot=')',s2[j+1]=')'),可以构成序列 ((i,j+1,k-1)) (点 ((i,j,k)->(i,j+1,k-1)))
    如果 (s1[i+1] ot=')',s2[j+1] ot=')'),可以构成序列 ((i,j,k-1)) (点 ((i,j,k)->(i,j,k-1)))
    那么以 ((0,0,0)) 为起点,进行一次宽搜。
    记录下宽搜的路径,从终点 ((len_{s1},len_{s2},0)) 逆向构造出路径,就是答案了。

    #include <bits/stdc++.h>
    using namespace std;
    char s1[210],s2[210];
    int n,m;
    struct  Node{
    	int i,j,k;
    }f[210][210][410];
    queue<Node> que;
    
    void bfs(){
    	memset(f,-1,sizeof(f));
    	f[0][0][0]=(Node){0,0,0};
    	que.push((Node){0,0,0});
    	while(!que.empty()){
    		Node u=que.front();que.pop();
    		int i=u.i,j=u.j,k=u.k,vi,vj,vk;
    		vi=i+(s1[i+1]=='(');
    		vj=j+(s2[j+1]=='(');
    		vk=k+1;
    		if(vk<=n+m&&f[vi][vj][vk].i==-1){
    			f[vi][vj][vk]=u;
    			que.push((Node){vi,vj,vk});
    		}
    		vi=i+(s1[i+1]==')');
    		vj=j+(s2[j+1]==')');
    		vk=k-1;
    		if(vk>=0&&f[vi][vj][vk].i==-1){
    			f[vi][vj][vk]=u;
    			que.push((Node){vi,vj,vk});
    		}
    	}
    }
    
    int main(){
    	scanf("%s%s",s1+1,s2+1);
    	n=strlen(s1+1);
    	m=strlen(s2+1);
    	bfs();
    	string ans;
    	int i=n,j=m,k=0;
    	while(i||j||k){
    		Node u=f[i][j][k];
    		if(k>u.k) ans="("+ans;
    		else ans=")"+ans;
    		i=u.i;j=u.j;k=u.k;
    	}
    	cout<<ans<<endl;
    	return 0;
    }
    
  • 相关阅读:
    OnSharedPreferenceChangeListener不被调用原理及解决方案
    自测题的整理(持续更新)
    排序的练习题
    C++各种类型的简单排序大汇总~
    洛谷P1540 机器翻译
    队列
    文件和结构体
    洛谷P1079 Vigenère 密码
    洛谷P1328生活大爆炸版石头剪刀布
    洛谷P1553数字反转升级版
  • 原文地址:https://www.cnblogs.com/BakaCirno/p/12034009.html
Copyright © 2020-2023  润新知