【题解】Atcoder ABC295 A-G

发布时间 2023-03-28 08:57:40作者: linyihdfj

A.Probably English

题目分析:

直接每一个单词判一下就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
	int n;scanf("%d",&n);
	bool flag = false;
	for(int i=1; i<=n; i++){
		string s;cin>>s;
		if(s == "and" || s == "not" || s == "that" || s == "the" || s == "you")	flag = true;
	}
	if(flag)	printf("Yes\n");
	else	printf("No\n");
	return 0;
}

B.Bombs

题目分析:

看这个数据范围,直接对于每一个位置判断是否有炸弹可以炸到就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
struct node{
	int l,r,val;
};
vector<node> v;
char s[100][100];
int main(){
	int n,m;scanf("%d%d",&n,&m);
	for(int i=1; i<=n; i++)	scanf("%s",s[i]+1);
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			if(s[i][j] >= '1' && s[i][j] <= '9'){
				v.push_back({i,j,s[i][j]-'0'});
			}
		}
	}
	for(int i=1; i<=n; i++){
		for(int j=1; j<=m; j++){
			if(s[i][j] == '#'){
				bool flag = false;
				for(auto g : v){
					if(abs(g.l - i) + abs(g.r - j) <= g.val)	flag = true;
				}
				if(flag)	printf(".");
				else	printf("#");
			}
			else printf(".");
		}
		printf("\n");
	}
}

C.Socks

题目分析:

若对于一种颜色它的出现次数为 \(S\),则它产生的贡献为 \(\lfloor \dfrac{S}{2} \rfloor\),每种颜色之间贡献独立所以直接求和就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
map<int,int> mp;
int a[N];
int main(){
	int n;scanf("%d",&n);
	for(int i=1; i<=n; i++){
		scanf("%d",&a[i]);
		mp[a[i]]++;
	}
	int ans = 0;
	for(int i=1; i<=n; i++){
		ans += mp[a[i]]/2;
		mp[a[i]] %= 2;
	}
	printf("%d\n",ans);
	return 0;
}

D.Three Days Ago

题目分析:

符合条件就是每种字母都出现偶数次,对于所有区间的题显然可以想到前缀和,然后任意两个前缀和的差值就对应一个区间。
而我们只关注出现的奇偶性所以显然可以只维护这个前缀里每个字母出现奇数还是偶数次,也就是直接状压,设状态为 \(S\),则只有前面的状态 \(S\) 才可以与当前的状态 \(S\) 相匹配形成一个符合条件的区间,所以直接扫一遍维护一下就好了。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 1e6+5;
int cnt[1<<11];
char s[N];
signed main(){
	scanf("%s",s+1);
	int n = strlen(s+1);
	int now=0,ans = 0;
	cnt[0]=1;
	for(int i=1; i<=n; i++){
		int p = s[i] - '0';
		now ^= (1<<p);
		ans += cnt[now];
		cnt[now]++;
	}
	printf("%lld\n",ans);
	return 0;
}

E.Kth Number

题目分析:

期望可以转化为:总价值除以总方案数。
所以我们可以直接设 \(f_i\) 表示 \(a_k = i\) 的方案数,那么答案就为:

\[\sum_{i=1}^m i \times f_i \]

这样其实就可以直接做了,因为直接计算等于 \(i\) 的方案数并不好做,所以可以转化为 \(a_k \le i\) 的方案数减去 \(a_k < i\) 的方案数。
而对于 \(a_k \le i\) 的方案数,其实就是要求小于等于 \(i\) 的数至少为 \(k\) 个,所以直接枚举有多少个,那么剩下的就是大于 \(k\) 的,就直接算就好了。
我们设原本 \(a\) 中小于等于 \(i\) 的数为 \(tot\) 个,\(a\) 中有 \(cnt\)\(0\),则 \(a_k \le i\) 的方案数为:

\[\sum_{j=k-tot}^{cnt} \binom{cnt}{j} \times i^{j} \times m^{cnt-j} \]

然后就可以全部算出来然后做了。

但是也有一种做法写起来更简单,我们设 \(g_i\) 表示 \(a_k \ge i\) 的方案数,那么我们的总价值其实就是:

\[\sum_{i=1}^m g_i \]

\(g_i\) 用上文的办法是很好求的,所以就更简单一些。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 2005;
const int MOD = 998244353;
int tot[N],n,m,k,cnt,a[N],ans[N],fac[N],inv[N];
int mod(int x){
	return (x % MOD + MOD)%MOD;
}
int binom(int n,int m){
	if(n < 0 || m < 0 || n < m)	return 0;
	return mod(fac[n] * mod(inv[m] * inv[n-m]));
}
int power(int a,int b){
	int res = 1;
	while(b){
		if(b & 1)	res = mod(res * a);
		a = mod(a * a);
		b >>= 1;
	}
	return res;
}
int get(int x,int t){  //<=t 的个数 >= x 的方案数  
	if(tot[t] >= x)	return power(m,cnt);
	int now = x - tot[t];
	if(now > cnt)	return 0;
	int ans = 0;
	for(int j=now; j <= cnt; j++){
		ans = mod(ans + mod(binom(cnt,j) * mod(power(t,j) * power(m-t,cnt-j))));
	}
	return ans;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	scanf("%lld%lld%lld",&n,&m,&k);
	fac[0] = 1;
	for(int i=1; i<=n; i++)	fac[i] = mod(fac[i-1] * i);
	inv[n] = power(fac[n],MOD-2);
	for(int i=n-1; i>=0; i--)	inv[i] = mod(inv[i+1] * (i+1));
	for(int i=1; i<=n; i++)	scanf("%lld",&a[i]),cnt += (a[i] == 0);
	for(int i=1; i<=n; i++){
		if(a[i] == 0)	continue;
		for(int j=a[i]; j<=m; j++){
			tot[j]++;
		}
	}
	for(int j=1; j<=m; j++){
		ans[k] = mod(ans[k] + j * (get(k,j) - get(k,j-1)));
	}
	int iv = power(power(m,cnt),MOD-2);
	ans[k] = mod(ans[k] * iv);
	printf("%lld\n",ans[k]);
	return 0;
}

F.substr = S

题目分析:

因为我们是要求出现的总次数,所以可以直接枚举 \(S\) 在哪里出现,然后求方案数。
这个应该是可以通过找规律等手段做出来的,但是代码细节有点复杂,所以就考虑直接数位 \(dp\) 就好了,而且同正解相比时间复杂度相同,只是常数大一些。

代码:

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N = 100;
int f[100][2][2],b[100],a[100],n,m;
char s[N];
int get(int pos,bool pre,bool flag){  //考虑到了前 pos 位,有/没有前导零,是否已经小于 
	if(f[pos][pre][flag])	return f[pos][pre][flag];
	if(b[pos] == 0 && pre)	return 0;
	if(pos == m + 1)	return 1;
	int l,r;
	if(flag)	l = 0,r = 9;
	else	l = 0,r = a[pos];
	if(b[pos] != -1) l = b[pos],r = b[pos];
	int ans = 0;
	for(int i=l; i<=r; i++){
		if(!flag && i > a[pos])	continue;
		ans += get(pos+1,pre & (i == 0),flag | (i < a[pos]));
	}
	f[pos][pre][flag] = ans;
	return ans;
}
int get_w(int x){
	if(x == 0)	return 1;
	int cnt = 0;
	while(x)	x /= 10,cnt++;
	return cnt;
}
int get(int x){
	if(x == 0)	return 0;
	n = strlen(s+1),m = get_w(x);
	if(m < n)	return 0;
	int now = m;
	while(x)	a[now] = x % 10,x /= 10,now--;
	int ans = 0;
	for(int i=1; i<=m-n+1; i++){
		for(int j=1; j<=m; j++)	b[j]=-1;
		for(int j=1; j<=n; j++)	b[i+j-1]=s[j]-'0';
		ans += get(1,1,0);
		for(int j=1; j<=20; j++)	f[j][0][1] = f[j][0][0] = f[j][1][0] = f[j][1][1] = 0;
	}
	return ans;
}
signed main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int t;scanf("%lld",&t);
	while(t--){
		int l,r;
		scanf("%s",s+1);
		scanf("%lld%lld",&l,&r);
		printf("%lld\n",get(r) - get(l-1));
	}
	return 0;
}

G.Minimum Reachable City

题目分析:

题目就是给定一棵树,每次在树上加一条返祖边,询问点所在连通块的编号最小节点的编号。
可以发现每次加一个返祖边其实就是形成一个环,而且形成的环的最小值就是这两个点的 \(lca\),所以可以考虑直接维护并查集然后加入一条边就是路径上的所有并查集合并,代表元为他们的 \(lca\) 的代表元。
每个点只会被合并一次,所以复杂度大概为 \(o(n\log n)\)

代码:

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
struct edge{
	int nxt,to;
	edge(){}
	edge(int _nxt,int _to){
		nxt = _nxt,to = _to;
	}
}e[2 * N];
int cnt,head[N],fa[N],ans[N],son[N],sz[N],dep[N],top[N],p[N];
void add_edge(int from,int to){
	e[++cnt] = edge(head[from],to);
	head[from] = cnt;
}
void dfs1(int now,int fath){
	dep[now] = dep[fath]+1;sz[now] = 1;
	for(int i=head[now]; i;i=e[i].nxt){
		int to = e[i].to;
		if(to == fath)	continue;
		dfs1(to,now);
		if(sz[to] > sz[son[now]])	son[now] = to;
		sz[now] += sz[to];
	}
}
void dfs2(int now,int topf){
	top[now] = topf;
	if(son[now])	dfs2(son[now],topf);
	for(int i=head[now]; i;i=e[i].nxt){
		int to = e[i].to;
		if(to == p[now] || to == son[now])	continue;
		dfs2(to,to);
	}
} 
int get_lca(int x,int y){
	while(top[x] != top[y]){
		if(dep[top[x]] < dep[top[y]])	swap(x,y);
		x = p[top[x]];
	}
	if(dep[x] < dep[y])	return x;
	return y;
}
int find(int x){
	if(fa[x] == x)	return x;
	return fa[x] = find(fa[x]);
}
int main(){
//	freopen("in.txt","r",stdin);
//	freopen("out.txt","w",stdout);
	int n;scanf("%d",&n);
	for(int i=2; i<=n; i++)	scanf("%d",&p[i]),add_edge(p[i],i);
	for(int i=1; i<=n; i++)	fa[i] = i,ans[i] = i;
	dfs1(1,0);
	dfs2(1,1);
	int q;scanf("%d",&q);
	while(q--){
		int x,y,opt;scanf("%d",&opt);
		if(opt == 1){
			scanf("%d%d",&x,&y);
			x = find(x),y = find(y);
			if(x != y){
				int lca = find(get_lca(x,y));
				while(x != lca){
					fa[x] = lca;x = find(p[x]);
				}
				while(y != lca){
					fa[y] = lca;y = find(p[y]);
				}
			}
		}
		else{
			int x;scanf("%d",&x);
			printf("%d\n",ans[find(x)]);
		}
	}
	return 0;
}