[题解]AtCoder Beginner Contest 406(ABC406) A~F

因为上线时间有限所以补题和题解都延误了(^^;
写得很急,如有错误/不清晰的点请在评论区提出。

A - Not Acceptable

见代码。时间复杂度\(O(1)\)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
int a,b,c,d;
signed main(){
	cin>>a>>b>>c>>d;
	if(a>c) cout<<"Yes\n";
	else if(a==c&&b>d) cout<<"Yes\n";
	else cout<<"No\n";
	return 0;
}

B - Product Calculator

设当前屏幕上的数字为\(fac\),要乘的数是\(a\),则:
如果\(fac\times a\ge 10^k\),则将\(fac\)置为\(1\);否则将\(fac\)累乘\(a\)

\(fac\times a\)可能会爆long long,可以使用__int128,或者可以转化一下条件:

  • \(fac\times a\ge 10^k\iff a\ge \lceil\frac{10^k}{fac}\rceil\)

时间复杂度\(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
int n,k,fac=1;
signed main(){
	cin>>n>>k;
	int t=1;
	for(int i=1;i<=k;i++) t*=10;
	while(n--){
		int a;
		cin>>a;
		if(a>=(t-1)/fac+1) fac=1;
		else fac*=a;
	}
	cout<<fac<<"\n";
	return 0;
}

C - ~

显然峰值和谷值是交替出现的。

我们要找到的就是一个区间,使得这个区间:

  • 恰好有\(1\)个峰值和\(1\)个谷值。
  • 峰值在谷值前。
  • 区间头尾均不是峰值/谷值。

定义\(nxt[i]\)\(a[i\sim n]\)中第一个峰/谷的位置。

枚举所有\(i\)使得\(a[i]<a[i+1]\),定义\(n_1=nxt[i+1],n_2=nxt[n_1+1],n_3=nxt[n_2+1]\)

如果\(n_1,n_2\)都存在,则:

  • 如果\(n_3\)存在,则累加\(n_3-n_2\)的贡献。
  • 如果\(n_3\)不存在,则累加\(n-n_2\)的贡献。

时间复杂度\(O(n)\)

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define N 300010
using namespace std;
int n,a[N],b[N],nxt[N],pre[N],ans;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cin>>a[i];
	for(int i=2;i<n;i++) if((a[i]>a[i-1]&&a[i]>a[i+1])||(a[i]<a[i-1]&&a[i]<a[i+1])) b[i]=1;
	for(int i=1;i<=n;i++) pre[i]=(b[i]?i:pre[i-1]);
	for(int i=n;i>=1;i--) nxt[i]=(b[i]?i:nxt[i+1]);
	for(int i=1;i<n;i++){
		if(a[i]<a[i+1]){
			int n1=nxt[i+1],n2=nxt[n1+1],n3=nxt[n2+1];
			if(n1&&n2) ans+=(n3?n3-n2:n-n2);
		}
	}
	cout<<ans<<"\n";
	return 0;
}

D - Garbage Removal

  • 赛时思路(稍有修改):为行和列各开一个set存里面的垃圾的坐标\((x,y)\),每处理一个行的操作,就遍历该行的垃圾,并在其所在列中删除它。列同理。

    为了方便查找,代码将\((x,y)\)压成一个整数了。

    时间复杂度\(O(n+m+k+q)\)

  • 题解思路:同一行/列的询问,从第\(2\)次开始就是\(0\)不变了。所以为行和列各开一个桶来统计该行/列是否被查询过,如果查询过直接输出\(0\),否则遍历该行/列的垃圾,如果没有被清除,则产生\(1\)的贡献,并标记该垃圾已被清除。

    由于每行、每列的垃圾最多被遍历\(1\)次,所以时间复杂度为\(O(n+m+k+q)\)

赛时思路
#include<bits/stdc++.h>
#define int long long
#define N 200010
using namespace std;
int n,m,k,q;
unordered_map<int> hang[N],lie[N];
signed main(){
	cin>>n>>m>>k;
	while(k--){
		int x,y;
		cin>>x>>y;
		hang[x].insert(x*N+y),lie[y].insert(x*N+y);
	}
	cin>>q;
	while(q--){
		int op,x;
		cin>>op>>x;
		if(op==1){
			cout<<hang[x].size()<<"\n";
			for(auto i:hang[x]) lie[i%N].erase(i);
			hang[x].clear();
		}else{
			cout<<lie[x].size()<<"\n";
			for(auto i:lie[x]) hang[i/N].erase(i);
			lie[x].clear();
		}
	}
	return 0;
}

题解代码见https://atcoder.jp/contests/abc406/editorial/13053

E - Popcount Sum 3

数位dp,下文使用DFS实现,规定最低位为\(1\)

这些是我的数位dp笔记:上篇下篇EX

由于要对满足条件的数求和,所以如果我们搜索到最后再统计贡献的话,将很难记忆化。因为就算两个搜索的状态“当前的数位”和“到目前为止\(1\)的个数”都相同,由于已经填好的数位不同,两个状态的答案也一定不同。但是这两个状态往后填写的情况是完全相同的。

所以我们不妨修改策略,让每个状态仅统计:

  • \(sum\):其搜索子树中所有答案 从当前数位到最低位的子串 之和。
  • \(cnt\):其搜索子树中的答案个数。

\(sum\)的定义说明每个状态仅需关注它后面所填的值,不需要管之前填写的内容。与CF1073E Segment Sum题解)这道题非常相似,可以类比一下两题的做法。

假设当前填到第\(pos\)位,则有转移:

\[\large sum[u]=\sum\limits_{i\in son[u],第pos位填w} sum[i]+w\times cnt[i]\times 2^{pos-1} \]

最后输出\(sum[root]\)即可。

可以使用\(f[pos][pc]\)来记忆化,其中\(pc\)是填到目前\(1\)的个数。

时间复杂度\(O(|\Sigma|\times \lg n\times k)\)。其中\(|\Sigma|=2\),表示字符集大小。

点击查看代码
#include<bits/stdc++.h>
#define int long long
#define mod 998244353
#define K 70
using namespace std;
int t,n,k,a[K];
bitset<K> fv[K];
pair<int,int> f[K][K];
//first表示cnt,second表示sum
pair<int,int> dfs(int pos,bool limit,int pc){
	if(pc>k) return {0,0};
	if(!pos) return {(pc==k),0};
	if(!limit&&fv[pos][pc]) return f[pos][pc];
	int rig=limit?a[pos]:1;
	pair<int,int> ans{0,0};
	for(int i=0;i<=rig;i++){
		auto res=dfs(pos-1,limit&&i==rig,pc+i);
		(ans.second+=res.second)%=mod;
		(ans.second+=i*(1ll<<(pos-1))%mod*res.first%mod)%=mod;
		(ans.first+=res.first)%=mod;
	}
	if(!limit) fv[pos][pc]=1,f[pos][pc]=ans;
	return ans;
}
int solve(int x){
	for(int i=0;i<K;i++) fv[i]=0;
	int len=0;
	while(x) a[++len]=x&1,x>>=1;
	return dfs(len,1,0).second;
}
signed main(){
	cin>>t;
	while(t--){
		cin>>n>>k;
		cout<<solve(n)<<"\n";
	}
	return 0;
}

F - Compare Tree Weights

核心操作在于单点修改权值以及查询子树和。

我们按DFS序给每个节点重新编号,容易发现一个子树中节点的DFS序是连续的,所以可以放到树状数组/线段树上操作。

其实就是把树剖模板题里“子树操作”弱化后搬到这来了。

代码的时间复杂度\(O((n+q)\log n)\),树状数组初始化优化一下可以做到\(O(n+q\log n)\)

点击查看代码
#include<bits/stdc++.h>
#define N 300010
using namespace std;
int n,q,tim,dfn[N],siz[N],u[N],v[N],dep[N];
vector<int> G[N];
inline int lowbit(int x){return x&-x;}
struct BIT{
	int s[N];
	void add(int x,int k){while(x<=n) s[x]+=k,x+=lowbit(x);}
	int query(int x){int ans=0;while(x) ans+=s[x],x-=lowbit(x);return ans;}
	int query(int l,int r){return query(r)-query(l-1);}
}bit;
void dfs(int u,int fa){
	dfn[u]=++tim,siz[u]=1,dep[u]=dep[fa]+1;
	for(int i:G[u]) if(i!=fa) dfs(i,u),siz[u]+=siz[i];
}
void add(int u,int v){G[u].emplace_back(v);}
signed main(){
	cin>>n;
	for(int i=1;i<n;i++) cin>>u[i]>>v[i],add(u[i],v[i]),add(v[i],u[i]);
	dfs(1,0);
	for(int i=1;i<n;i++) if(dep[u[i]]<dep[v[i]]) swap(u[i],v[i]);
	for(int i=1;i<=n;i++) bit.add(i,1);
	cin>>q;
	while(q--){
		int o,x,y;
		cin>>o;
		if(o==1){
			cin>>x>>y,bit.add(dfn[x],y);
		}else{
			cin>>x;
			int s=bit.query(n),t=bit.query(dfn[u[x]],dfn[u[x]]+siz[u[x]]-1);
			cout<<abs(2*t-s)<<"\n";
		}
	}
	return 0;
}
posted @ 2025-05-23 20:32  Sinktank  阅读(87)  评论(0)    收藏  举报
★CLICK FOR MORE INFO★ TOP-BOTTOM-THEME
Enable/Disable Transition
Copyright © 2023 ~ 2025 Sinktank - 1328312655@qq.com
Illustration from 稲葉曇『リレイアウター/Relayouter/中继输出者』,by ぬくぬくにぎりめし.