【403 Error】Atcoder Beginner Contest 403 题解
零、前言
经过 5 5 5 个月 10 10 10 天的分别,本期 ABC 题解又和大家见面啦!
本次要讲解的是 ABC403 的题目,欢迎大家阅读。
本篇题解由庆祝第五次 AK 和重返 1900 1900 1900 分写的。
一、正文
第 A 题 Odd Position Sum
非常简单,直接模拟即可,把正常遍历的 i++
换成 i+=2
即可。
当然还有一种写法,每次把 a i × ( i % 2 ) a_i\times(i~\%~2) ai×(i % 2) 加到答案里也行。
注意本题和 G 题有联动。(联动很少见)
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){int n; cin>>n;int ans=0,x;for (int i=1; i<=n; i++){cin>>x;ans+=(i%2)*x;}cout<<ans;
}
第 B 题 Four Hidden
题意为有一个字符串 x x x,把四个字符改成 ?
后变成 y y y,给定 y y y 和另一个字符串 u u u,求 u u u 能否成为 x x x 的子串。
其实先枚举 y y y 中一个长度为 ∣ u ∣ |u| ∣u∣ 的子串( ∣ ∣ || ∣∣ 表示长度),然后判断这个子串能否和 u u u 相等,判断方法就是这两个字符串的第 i i i 位,要么相等,要么有一个 ?
。
代码:
#include <bits/stdc++.h>
using namespace std;
int main(){string t,u; cin>>t>>u; int ans=0;for (int i=0; i<=t.size()-u.size(); i++){bool b=1;for (int j=i; j<=i+u.size()-1; j++){if (t[j]==u[j-i]||t[j]=='?') b=b;else b=0;}ans|=b;}cout<<(ans?"Yes":"No");
}
第 C 题 403 Forbidden
题意说维护一个网站系统,开始用户没有任何权限,每次可以赋予一个用户一种权限或全部权限,还要查询某个用户是否有某种权限。
因为用户数和权限数很多,无法暴力添加,所以需要改进。
其实我们可以定义一种 0 0 0 号权限,表示用户是否拥有全部权限,每次查询 x x x 用户有没有 y y y 权限时只需查 x x x 用户是否有 y y y 或 0 0 0 号权限即可。
代码:
#include <bits/stdc++.h>
using namespace std;
map <int,int> mp[200010];
int main(){int n,m,q; cin>>n>>m>>q;for (int i=1; i<=q; i++){int op; cin>>op;if (op==1){int x,y; cin>>x>>y;mp[x][y]=1;}else if (op==2){int x; cin>>x; mp[x][0]=1;}else{int x,y; cin>>x>>y;if (mp[x][0]||mp[x][y]) cout<<"Yes\n";else cout<<"No\n";}}
}
第 D 题 Forbidden Difference
这道题分值 425 425 425 分,所以一定非常非常难。
这道题很多人上来卡住了,其实我也是。
这道题看上去虽然非常奇怪,但是可以先做一步转换,求最多留下几个数,再对相同的数合并,这样数被赋予了权值。
其次,我们发现,如果留下 x x x,那么 x + D x+D x+D 和 x − D x-D x−D 无法保留,容易 想到可以把数对 D D D 取模分组,问题转化成了一个数列,不能选择相邻的数,问选择的数的最大值是多少(有权值),所以要用 DP 解决。
小 Tip:本题有坑,如果 D = 0 D=0 D=0,答案就是 m a x ( 0 , c n t x − 1 ) max(0,cnt_x-1) max(0,cntx−1) 的和, c n t x cnt_x cntx 是数字 x x x 出现的次数。
代码:
#include <bits/stdc++.h>
using namespace std;
int cnt[1000010],dp[1000010][2];
vector <int> vc[1000010];
int main(){int n,m,ans=0; cin>>n>>m;for (int i=1; i<=n; i++){int x; cin>>x; cnt[x]++;}if (m==0){int cntt=0;for (int i=0; i<=1e6; i++){cntt+=max(0,cnt[i]-1);}cout<<cntt;return 0;}for (int i=0; i<=1e6; i++){vc[i%m].push_back(cnt[i]);}for (int i=0; i<=m; i++){for (int j=1; j<=vc[i].size(); j++){dp[j][0]=max(dp[j-1][0],dp[j-1][1]);dp[j][1]=dp[j-1][0]+vc[i][j-1];}ans+=max(dp[vc[i].size()][0],dp[vc[i].size()][1]);}cout<<n-ans;
}
第 E 题 Forbidden Prefix
题意是有两个可重字符串集 X , Y X,Y X,Y,每次向其中一个集合里添加字符串,添加之后求有多少个字符串属于 Y Y Y 却没有属于 X X X 的前缀。
字符串,前缀,可重集,这不是 Trie 又是什么?我们维护一个 Trie。
我们先解决插入问题,因为插入非常简单,我们找到字符串所在的路径然后路径加一即可。如果在加入过程中遇到删除标记就停止并把之前的加都减回去。
如果我们要删除呢?也是找到所在路径,如果路径上有删除标记就结束(无用操作),接下来我们假设走到了 i d id id,我们把 i d id id 的标记清零,打上标记,然后再把路径上所有的点都减去这个标记即可。
小 Tip 如果插入,如果结束 i d id id 有删除标记也要回删,所以注意代码顺序。
代码:
#include <bits/stdc++.h>
using namespace std;
int tr[500010][26],shan[500010],cnt[500010],tot=1;
void ins(string s){int id=1;for (auto x:s){if (!tr[id][x-'a']) tr[id][x-'a']=++tot;cnt[id]++; id=tr[id][x-'a'];if (shan[id]){ id=1;for (auto x:s){if (shan[id]) break;cnt[id]--; id=tr[id][x-'a'];}return ;}}cnt[id]++;
}
void del(string s){int id=1;for (auto x:s){if (shan[id]) return ;if (!tr[id][x-'a']) tr[id][x-'a']=++tot;id=tr[id][x-'a'];}int tmp=cnt[id]; shan[id]=1; cnt[id]=0; id=1; for (auto x:s){cnt[id]-=tmp;id=tr[id][x-'a'];}
}int main(){ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);int t; cin>>t;while (t--){int op; string s; cin>>op>>s;if (op==2) ins(s);else del(s);cout<<cnt[1]<<"\n";}
}
第 F 题 Shortest One Formula
题意为求一个最段的,由 + × ( ) 1 +\times(~)~1 +×( ) 1 组成的表达式,使得结果为 n n n。
考虑 DP,设 d p i dp_i dpi 为 n = i n=i n=i 时的表达式长度,但是这样有优先级的问题。
可以考虑升维, d p i , 0 / 1 dp_{i,0/1} dpi,0/1 为 n = i n=i n=i 时,最外层计算是 + + + 或 × \times × 组成的最短表达式长度,这样我们就可以转移了。
如果用加: d p i , 0 = m i n ( d p j , 0 / 1 + d p i − j , 0 / 1 ) dp_{i,0}=min(dp_{j,0/1}+dp_{i-j,0/1}) dpi,0=min(dpj,0/1+dpi−j,0/1)。
如果用乘(和括号) d p i , 1 = m i n ( d p j , l + d p i / j , r + 2 l + 2 r ) dp_{i,1}=min(dp_{j,l}+dp_{i/j,r}+2l+2r) dpi,1=min(dpj,l+dpi/j,r+2l+2r)。
可是要输出答案,这可有点问题,到底怎么可以记录答案呢,实际上可以记 p r e pre pre 数组,表示从哪里转移而来,具体记录方式见代码,然后我们可以用类似递归的方式输出答案了。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
int dp[2010][2],pre[2010][2][4];
void out(int id,int sta){if (!pre[id][sta][0]){cout<<id;return ;}if (sta==1){out(pre[id][sta][0],pre[id][sta][1]);cout<<"+";out(pre[id][sta][2],pre[id][sta][3]);}else{if (pre[id][sta][1]) cout<<"(";out(pre[id][sta][0],pre[id][sta][1]);if (pre[id][sta][1]) cout<<")";cout<<"*";if (pre[id][sta][3]) cout<<"(";out(pre[id][sta][2],pre[id][sta][3]);if (pre[id][sta][3]) cout<<")";}
}
signed main(){ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);int n; cin>>n;memset(dp,0x3f,sizeof(dp));dp[1][0]=1;dp[11][0]=2;dp[111][0]=3;dp[1111][0]=4;for (int i=2; i<=2000; i++){for (int j=1; j<=i; j++){if (i%j==0){for (int l=0; l<2; l++){for (int r=0; r<2; r++){int val=dp[j][l]+l*2+dp[i/j][r]+r*2+1;if (val<dp[i][0]){dp[i][0]=val;pre[i][0][0]=j;pre[i][0][1]=l;pre[i][0][2]=i/j;pre[i][0][3]=r;}}}}}for (int j=1; j<i; j++){for (int l=0; l<2; l++){for (int r=0; r<2; r++){int val=dp[j][l]+dp[i-j][r]+1;if (val<dp[i][1]){dp[i][1]=val;pre[i][1][0]=j;pre[i][1][1]=l;pre[i][1][2]=i-j;pre[i][1][3]=r;}}}}} for (int i=n; i<=n; i++){if (dp[i][0]<dp[i][1]) out(i,0);else out(i,1);}
}
附赠一份 1 1 1 到 2000 2000 2000 所有 n n n 的答案表以供大家研究。
第 G 题 Odd Position Sum Query
感谢大家坚持读到最后,还记得讲 A 时说本题与 A 联动,那么现在就来看一看这道题。
强制在线维护一个数组,支持加入元素和查询排名为奇数的数的和。
容易 想到动态开点值域线段树,我们只需要弄明白到底记录什么信息就可以了。
首先,一定要记录答案,但是合并时,左侧节点的大小若是奇数,那么答案并不是简单的求和了,会发现右侧的偶数位会变成奇数位,所以还要记录大小和偶数位答案,有了这三个信息,合并也就很简单了。
小 Tip 注意有重复数字。
代码:
#include <bits/stdc++.h>
using namespace std;
#define int long long
#define mid (l+r>>1)
struct node{int sz,jans,oans;
}tr[12000010];
int tot=1,ls[12000010],rs[12000010];
node merge(node a,node b){node ans;ans.sz=a.sz+b.sz;ans.jans=a.jans+(a.sz%2?b.oans:b.jans);ans.oans=a.oans+(a.sz%2?b.jans:b.oans);return ans;
}
void update(int id,int l,int r,int qid,int val){if (l==r){tr[id].sz++;if (tr[id].sz%2==1) tr[id].jans+=val;else tr[id].oans+=val;return ;}if (qid<=mid){if (!ls[id]) ls[id]=++tot;update(ls[id],l,mid,qid,val);}else{if (!rs[id]) rs[id]=++tot;update(rs[id],mid+1,r,qid,val);}tr[id]=merge(tr[ls[id]],tr[rs[id]]);
}signed main(){ios::sync_with_stdio(0);cin.tie(0); cout.tie(0);int t,pre=0; cin>>t;for (int i=1; i<=t; i++){int x; cin>>x;x=(x+pre)%1000000000+1;update(1,1,1e9,x,x);pre=tr[1].jans;cout<<pre<<"\n";}
}
二、一些有用的链接
Atcoder Beginner Contest 403
Atcoder 难度评级
出题组(招人中)
感谢大家的阅读,我们下期再见