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