首页 > 其他分享 >AtCoder Beginner Contest 287 解题报告

AtCoder Beginner Contest 287 解题报告

时间:2023-01-29 09:44:05浏览次数:57  
标签:AtCoder cnt Beginner Contest int tree pos MAXN dp

AtCoder Beginner Contest 287 解题报告

\(\text{By DaiRuiChen007}\)

Contest Link

A. Majority

map 分别统计两种字符串出现的次数并与 \(\left\lfloor\dfrac n2\right\rfloor\) 比较即可

时间复杂度 \(\Theta(n\log n)\)

#include<bits/stdc++.h>
using namespace std;
map <string,int> cnt;
signed main() {
	int n;
	cin>>n;
	for(int i=1;i<=n;++i) {
		string s;
		cin>>s;
		++cnt[s];
	}
	puts(cnt["For"]>(n/2)?"Yes":"No");
	return 0;
}

B. Postal Card

map 先标记所有的 \(T_i\),然后依次处理每个 \(S_i\) 的后 \(3\) 位,检查其是否被标记过,若被标记过则说明存在某个 \(T_j\) 与 \(S_i\) 的后 \(3\) 位匹配

时间复杂度 \(\Theta((n+m)\log m)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1001;
string s[MAXN];
map <string,bool> cnt;
signed main() {
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=n;++i) cin>>s[i];
	int ans=0;
	for(int i=1;i<=m;++i) {
		string t;
		cin>>t;
		cnt[t]=true;
	}
	for(int i=1;i<=n;++i) if(cnt[s[i].substr(3,3)]) ++ans;
	cout<<ans<<"\n";
	return 0;
}

C. Path Graph?

判断一个图是否是链:要求图联通,且度数为 \(1\) 的点恰好 \(2\) 个,剩下的点度数都为 \(2\)

可以证明这些条件是充要的

第一个问题用并查集维护,第二问题直接算度数即可

时间复杂度 \(\Theta(n\alpha(n))\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2e5+1;
int deg[MAXN],dsu[MAXN];
inline int find(int x) {
	if(dsu[x]==x) return x;
	return dsu[x]=find(dsu[x]);
}
signed main() {
	int n,m,tot=0;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) dsu[i]=i;
	for(int i=1;i<=m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		++deg[u],++deg[v];
		int x=find(u),y=find(v);
		if(x!=y) ++tot,dsu[x]=y;
	}
	if(tot<n-1) {
		puts("No");
		return 0;
	}
	int cnt=0;
	for(int i=1;i<=n;++i) {
		if(deg[i]>2) {
			puts("No");
			return 0;
		} else if(deg[i]==1) ++cnt;
	}
	puts(cnt==2?"Yes":"No");
	return 0;
}

D. Match or Not

考虑将原问题拆分为:\(S\) 的前 \(i\) 个和 \(T\) 的前 \(i\) 个可以匹配,且 \(S\) 的后 \(|T|-i\) 个可以和 \(T\) 的后 \(|T|-i\) 个匹配

而注意到对 \(S[1\cdots i]\) 和 \(T[1\cdots i]\) 匹配可以拆成:先对 \(S[1\cdots i-1]\) 和 \(T[1\cdots i-1]\) 匹配,然后对 \(S_i,T_i\) 进行匹配

因此第一个问题可以先前缀扫一遍算出所有 \(i\) 的答案,第二根问题类似扫一遍后缀即可

预处理之后每个问题把前后缀拼起来即可

时间复杂度 \(\Theta(|S|+|T|)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e5+1;
char s[MAXN],t[MAXN];
bool pre[MAXN],suf[MAXN];
signed main() {
	scanf("%s%s",s+1,t+1);
	int m=strlen(s+1),n=strlen(t+1);
	pre[0]=suf[0]=true;
	for(int i=1;i<=n;++i) {
		if(s[i]==t[i]||s[i]=='?'||t[i]=='?') pre[i]=true;
		pre[i]&=pre[i-1];
	}
	for(int i=1;i<=n;++i) {
		if(s[m-i+1]==t[n-i+1]||s[m-i+1]=='?'||t[n-i+1]=='?') suf[i]=true;
		suf[i]&=suf[i-1];
	}
	for(int i=0;i<=n;++i) {
		puts((pre[i]&&suf[n-i])?"Yes":"No");
	}
	return 0;
}

E. Karuta

多次求 \(\operatorname{lcp}\),直接建立 Trie,统计每个位置被访问的次数

对于每个 \(S_i\) 求出:从 \(\operatorname{start}\to \operatorname{end}_i\) 的路径中被访问次数 \(\ge 2\)(\(S_i\) 访问一次 \(S_j\) 访问一次)的所有节点中深度最大的一个即可

时间复杂度 \(\Theta(\sum|S_i|)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e5+1;
struct node {
	int son[26],cnt,dep;
	inline int& operator [](const int &i) { return son[i]; }
	inline int& operator [](const char &ch) { return son[ch-'a']; }
}	tr[MAXN];
int siz=0;
inline void insert(string s) {
	int pos=0;
	for(auto ch:s) {
		if(!tr[pos][ch]) {
			tr[pos][ch]=++siz;
			tr[tr[pos][ch]].dep=tr[pos].dep+1;
		}
		pos=tr[pos][ch];
		++tr[pos].cnt;
	}
}
string s[MAXN];
signed main() {
	int n;
	cin>>n;
	for(int i=1;i<=n;++i) {
		cin>>s[i];
		insert(s[i]);
	}
	for(int i=1;i<=n;++i) {
		int pos=0,ans=0;
		for(auto ch:s[i]) {
			pos=tr[pos][ch];
			if(tr[pos].cnt>1) ans=tr[pos].dep;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

F. Components

首先有一个简单的 dp,\(dp_{i,0/1,j}\) 表示以 \(i\) 为根的子树,\(i\) 不选或选的情况下构成 \(j\) 个联通块的方案数,对于一条边 \(u\to v\),转移的时候按如下的方式卷积:

\[\begin{aligned} dp'_{u,0,j+k}&\gets dp_{u,0,j}\times dp_{v,0,k}\\ dp'_{u,0,j+k}&\gets dp_{u,0,j}\times dp_{v,1,k}\\ dp'_{u,1,j+k}&\gets dp_{u,1,j}\times dp_{v,0,k}\\ dp'_{u,1,j+k-1}&\gets dp_{u,1,j}\times dp_{v,1,k}\\ \end{aligned} \]

最后卷积完令 \(dp_{u}\gets dp'_{u}\),继续和下一个 \(v\) 卷积

这样的复杂度是 \(\Theta(n^3)\) 的

但注意到我们每次卷积的上界其实不需要到 \(n\),而是当前的 \(siz_u\) 和 \(siz_v\) 即可

这样优化之后可以证明时间复杂度是 \(\Theta(n^2)\) 的

因此维护 \(dp_{i,0/1,j}\),每次以 \(siz_u,siz_v\) 为两维的上界暴力卷积转移即可

时间复杂度 \(\Theta(n^2)\)

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5001,MOD=998244353;
vector <int> G[MAXN],dp[MAXN][2];
int siz[MAXN];
inline void dfs(int p,int f) {
	siz[p]=1;
	dp[p][0]=vector<int>{1,0};
	dp[p][1]=vector<int>{0,1};
	for(int v:G[p]) {
		if(v==f) continue;
		dfs(v,p);
		vector <int> ret[2];
		ret[0].resize(siz[v]+siz[p]+1,0);
		ret[1].resize(siz[v]+siz[p]+1,0);
		for(int j=0;j<=siz[p];++j) {
			for(int k=0;k<=siz[v];++k) {
				ret[0][j+k]=(ret[0][j+k]+dp[v][0][k]*dp[p][0][j]%MOD)%MOD;
				ret[0][j+k]=(ret[0][j+k]+dp[v][1][k]*dp[p][0][j]%MOD)%MOD;
				ret[1][j+k]=(ret[1][j+k]+dp[v][0][k]*dp[p][1][j]%MOD)%MOD;
				if(j!=0||k!=0) ret[1][j+k-1]=(ret[1][j+k-1]+dp[v][1][k]*dp[p][1][j]%MOD)%MOD;
			}
		}
		siz[p]+=siz[v];
		dp[p][0]=ret[0],dp[p][1]=ret[1];
	}
}
signed main() {
	int n;
	scanf("%lld",&n);
	for(int i=1;i<n;++i) {
		int u,v;
		scanf("%lld%lld",&u,&v);
		G[u].push_back(v),G[v].push_back(u);
	}
	dfs(1,0);
	for(int i=1;i<=n;++i) printf("%lld\n",(dp[1][0][i]+dp[1][1][i])%MOD);
	return 0;
}

G. Balance Update Query

以所有 \(a_i\) 作为下标,建立动态开点权值线段树

维护区间内 \(\sum b_i\) 和 \(\sum a_i\times b_i\) 的值,修改 \(b_i\) 就是普通的单点修操作,修改 \(a_i\) 相当于在原本的 \(a_i\) 上减去 \(b_i\) 并且在新的 \(a_i\) 的上加上 \(b_i\),因此两种修改操作都可以转化成权值线段树上的单点修改

查询答案的时候在线段树上二分即可,过程类似主席树:

  • 如果 \(k\) 小于等于右子树的 \(\sum b_i\),那么直接优先走右子树
  • 否则走左子树,递归 \(k\gets k-\sum b_i\),并且加上全取右子树的 \(\sum a_i\times b_i\) 即可

时间复杂度 \(\Theta((n+q)\log V)\),\(V\) 为 \(\{a_i\}\) 的值域

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+1,V=1e9;
struct SegmentTree {
	struct node {
		int sum,cnt,lson,rson;
		node() { sum=0,cnt=0,lson=rson=0; }
	}	tree[MAXN*40];
	int siz;
	inline void pushup(int pos) {
		tree[pos].sum=tree[tree[pos].lson].sum+tree[tree[pos].rson].sum;
		tree[pos].cnt=tree[tree[pos].lson].cnt+tree[tree[pos].rson].cnt;
	}
	SegmentTree() { siz=0; }
	inline void Modify(int u,int k,int l,int r,int &pos) {
		if(!pos) pos=++siz;
		if(l==r) {
			tree[pos].cnt+=k;
			tree[pos].sum+=u*k;
			return ;
		}
		int mid=(l+r)>>1;
		if(u<=mid) Modify(u,k,l,mid,tree[pos].lson);
		else Modify(u,k,mid+1,r,tree[pos].rson);
		pushup(pos);
	}
	inline int Query(int k,int l,int r,int pos) {
		if(!pos) return 0;
		if(tree[pos].cnt==k) return tree[pos].sum;
		if(l==r) return l*k;
		int mid=(l+r)>>1,val=tree[tree[pos].rson].cnt;
		if(val>=k) return Query(k,mid+1,r,tree[pos].rson);
		return Query(k-val,l,mid,tree[pos].lson)+tree[tree[pos].rson].sum;
	}
}	S;
int a[MAXN],b[MAXN];
signed main() {
	int n,rt=0;
	scanf("%lld",&n);
	for(int i=1;i<=n;++i) {
		scanf("%lld%lld",&a[i],&b[i]);
		S.Modify(a[i],b[i],0,V,rt);
	}
	int q;
	scanf("%lld",&q);
	while(q--) {
		int opt;
		scanf("%lld",&opt);
		if(opt==1) {
			int x,y;
			scanf("%lld%lld",&x,&y);
			S.Modify(a[x],-b[x],0,V,rt);
			a[x]=y;
			S.Modify(a[x],b[x],0,V,rt);
		}
		if(opt==2) {
			int x,y;
			scanf("%lld%lld",&x,&y);
			S.Modify(a[x],y-b[x],0,V,rt);
			b[x]+=y-b[x];
		}
		if(opt==3) {
			int x;
			scanf("%lld",&x);
			if(S.tree[rt].cnt<x) puts("-1");
			else printf("%lld\n",S.Query(x,0,V,rt));
		}
	}
	return 0;
}

Ex. Directed Graph and Query

首先看到这个问题先想离线询问,对于 \(t\) 从 \(1\) 枚举到 \(n\),考虑标号 \(\le t\) 的图中 \(s_i\to t_i\) 是否联通,如果联通就用 \(t\) 更新

注意到这个过程实际上和 Floyd 传递闭包的过程是一致的,因此我们在 Floyd 传递闭包每次枚举完某个中转点 \(k\) 时,更新联通的 \(s_i\to t_i\) 的答案,注意 Floyd 传递闭包只能保证路径中转点 \(\le k\),还需要判断 \(s_i,t_i\le k\)

时间复杂度 \(\Theta\left(nq+\dfrac{n^3}\omega\right)\)

#include<bits/stdc++.h>
using namespace std;
const int MAXN=2001,MAXQ=1e4+1;
bitset <MAXN> f[MAXN];
int s[MAXQ],t[MAXQ],ans[MAXQ];
signed main() {
	int n,m;
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;++i) f[i].set(i);
	for(int i=1;i<=m;++i) {
		int u,v;
		scanf("%d%d",&u,&v);
		f[u].set(v);
	}
	int q;
	scanf("%d",&q);
	for(int i=1;i<=q;++i) scanf("%d%d",&s[i],&t[i]);
	for(int k=1;k<=n;++k) {
		for(int i=1;i<=n;++i) {
			if(f[i][k]) f[i]|=f[k];
		}
		for(int i=1;i<=q;++i) {
			if(ans[i]==0&&(s[i]<=k&&t[i]<=k)&&(f[s[i]][t[i]])) ans[i]=k;
		}
	}
	for(int i=1;i<=q;++i) printf("%d\n",ans[i]>0?ans[i]:-1);
	return 0;
}

标签:AtCoder,cnt,Beginner,Contest,int,tree,pos,MAXN,dp
From: https://www.cnblogs.com/DaiRuiChen007/p/17071779.html

相关文章

  • AtCoder Beginner Contest 287
    A-Majority(abc287a)题目大意给定\(n\)个人对某个提案的意见,问大多数意见是支持还是反对解题思路统计比较即可。神奇的代码#include<bits/stdc++.h>usingnam......
  • AtCoder Beginner Contest 287
    纯纯手速场C首先这张图必须是一棵树,必有\(M=N-1\)。接下来只需求出树的直径,判断其长度(边数)是否为\(N-1\)即可。https://atcoder.jp/contests/abc287/submissions/3......
  • Atcoder Beginner Contest 287
    赛时吃了三个法师,不过问题不大。赛时AB简单字符串处理。C中需要满足:\(m=n-1\)只有两个度数为\(1\)的点,剩下点的度数都为\(2\)。记得判连通!!D根据题目要求观......
  • AtCoder Beginner Contest 130
    AtCoderBeginnerContest130https://atcoder.jp/contests/abc130补补之前的A-Rounding#include<bits/stdc++.h>usingnamespacestd;intmain(){inta......
  • Atcoder ABC244E - King Bombee 题解
    原题:AtcoderABC244E-KingBombee题意给你一张图,从\(S\)到\(T\),经过\(k\)条边,经过\(X\)号点偶数次的方案数。做法设\(f_{i,j,k}\)表示经过\(i\)条边,......
  • AtCoder Beginner Contest 159
    A-TheNumberofEvenPairs相加为偶数只有偶加偶和奇加奇两种情况,其实就是在\(n\)个数中取两个(\(C\binom{2}{n}\)),在\(m\)个数中取两个(\(C\binom{2}{m}\))。B-String......
  • AtCoder Beginner Contest 126
    AtCoderBeginnerContest126https://atcoder.jp/contests/abc126A-ChangingaCharacter#include<bits/stdc++.h>usingnamespacestd;intmain(){int......
  • AtCoder Beginner Contest 162
    A-Lucky7大水题,模拟即可。#include<iostream>#include<cstdio>usingnamespacestd;intmain(){strings;cin>>s;if(s[0]=='7'||s[1]==......
  • AtCoder Beginner Contest 172
    A-Calc(abc172a)题目大意给定一个\(a\),输出\(a+a^2+a^3\)解题思路模拟即可。神奇的代码#include<bits/stdc++.h>usingnamespacestd;usingLL=long......
  • 【最短路】Atcoder Beginner Contest 286----E
    题目:E-Souvenir(atcoder.jp)题解:首先这道题可以很容易看出来是求最短路。最开始自己是用bfs写的,出现了WA,TLE,RE等错误。因为对于每种情况会有Q次询问,如果每次询问都......