【403 Error】Atcoder Beginner Contest 403 题解

零、前言

经过 5 5 5 个月 10 10 10 天的分别,本期 ABC 题解又和大家见面啦!

本次要讲解的是 ABC403 的题目,欢迎大家阅读。

本篇题解由庆祝第五次 AK 和重返 1900 1900 1900 分写的。
【403 Error】Atcoder Beginner Contest 403 题解_第1张图片

一、正文

第 A 题 Odd Position Sum

非常简单,直接模拟即可,把正常遍历的 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;
} 

第 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 
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 
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 xD 无法保留,容易 想到可以把数对 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,cntx1) 的和, 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;
} 

第 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 
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+dpij,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 的答案表以供大家研究。

第 G 题 Odd Position Sum Query

感谢大家坚持读到最后,还记得讲 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 难度评级

出题组(招人中)

感谢大家的阅读,我们下期再见

你可能感兴趣的:(比赛日记,Atcoder)