经过 5 5 5 个月 10 10 10 天的分别,本期 ABC 题解又和大家见面啦!
本次要讲解的是 ABC403 的题目,欢迎大家阅读。
本篇题解由庆祝第五次 AK 和重返 1900 1900 1900 分写的。
非常简单,直接模拟即可,把正常遍历的 i++
换成 i+=2
即可。
当然还有一种写法,每次把 a i × ( i % 2 ) a_i\times(i~\%~2) ai×(i % 2) 加到答案里也行。
注意本题和 G 题有联动。(联动很少见)
代码:
#include
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;
}
题意为有一个字符串 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
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");
}
题意说维护一个网站系统,开始用户没有任何权限,每次可以赋予一个用户一种权限或全部权限,还要查询某个用户是否有某种权限。
因为用户数和权限数很多,无法暴力添加,所以需要改进。
其实我们可以定义一种 0 0 0 号权限,表示用户是否拥有全部权限,每次查询 x x x 用户有没有 y y y 权限时只需查 x x x 用户是否有 y y y 或 0 0 0 号权限即可。
代码:
#include
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";
}
}
}
这道题分值 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
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;
}
题意是有两个可重字符串集 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
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";
}
}
题意为求一个最段的,由 + × ( ) 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
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 的答案表以供大家研究。
感谢大家坚持读到最后,还记得讲 A 时说本题与 A 联动,那么现在就来看一看这道题。
强制在线维护一个数组,支持加入元素和查询排名为奇数的数的和。
容易 想到动态开点值域线段树,我们只需要弄明白到底记录什么信息就可以了。
首先,一定要记录答案,但是合并时,左侧节点的大小若是奇数,那么答案并不是简单的求和了,会发现右侧的偶数位会变成奇数位,所以还要记录大小和偶数位答案,有了这三个信息,合并也就很简单了。
小 Tip 注意有重复数字。
代码:
#include
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 难度评级
出题组(招人中)
感谢大家的阅读,我们下期再见