ABC333题解(A~F)

前置

感觉这场难度 A

[ABC333A] Three Threes

题意简述

给定一个一到九之间的整数 n n n,将 n n n 输出 n n n 次。

解题思路

直接输出即可。

代码示例

#include
using namespace std;
#define int long long
int n;
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) cout<<n;
	return 0;
}

[ABC333B] Pentagon

题意简述

给定一个正五边形,顶点按顺时针方向依次为 A , B , C , D , E A,B,C,D,E A,B,C,D,E。现在给定顶点 s 1 , s 2 , t 1 , t 2 s1,s2,t1,t2 s1,s2,t1,t2,如果 s 1 s1 s1 s 2 s2 s2 的长度等于 t 1 t1 t1 t 2 t2 t2 的长度,输出 Yes。否则输出 No

解题思路

题目本身没有难度,咋做都行。但是有一种简便的方法:记由 s 2 s2 s2 出发顺时针走到 s 1 s1 s1 需要经过 n 1 n_1 n1 条边,逆时针需要经过 n 2 n_2 n2 条边;用同样的方法定义 t 2 t2 t2 t 1 t1 t1 经过的边数,记作 m 1 , m 2 m_1,m_2 m1,m2。若 m i n ( n 1 , n 2 ) = m i n ( m 1 , m 2 ) min(n_1,n_2)=min(m_1,m_2) min(n1,n2)=min(m1,m2),则输出 Yes。否则输出 No。也就是说我们将两点距离转换为了经过的边数。同时又有 n 1 = 5 − n 2 , m 1 = 5 − m 2 n_1=5-n_2,m_1=5-m_2 n1=5n2,m1=5m2,所以代码就很好写了。

代码示例

#include
using namespace std;
#define int long long
string s1,s2;
signed main(){
	cin>>s1>>s2;
	if(min(abs(s1[0]-s1[1]),5-abs(s1[0]-s1[1]))==min(abs(s2[0]-s2[1]),5-abs(s2[0]-s2[1]))){
		cout<<"Yes"<<endl;
	}else cout<<"No"<<endl;
	return 0;
}

[ABC333C] Repunit Trio

题意简述

定义好数为仅由 1 构成的数字,比如 1,11,111 等。

给定一个正整数 n n n,输出在所有能够表示成三个好数之和的数字中,第 n n n 小的。

解题思路

稍微手玩一下样例,可以发现能够表示为三个好数之和的数,一定是先一段连续的 1,再一段连续的 2,最后一段连续的 3(未出现则算作连续 0个)。

于是可以直接找下一个好数。如果当前数仅由 3 构成,则在最前面加一个 1,并将除了最后一位以外的所有位设置为 1。否则,找到第一个不为 3 的位置,设它现在是 x x x

x = 1 x=1 x=1,将 x x x 设为 2,并将它后面所有位(除最后一位)设为 2

x = 2 x=2 x=2,则将 x x x 加一。

比较方便的是用一个 vector 模拟这个过程。

代码示例

#include
using namespace std;
#define int long long
vector<int> G;
int n;
signed main(){
	cin>>n;
	G.push_back(3);
	n--;
	while(n--){
		if(G[G.size()-1]==3){
			for(int i=1;i<G.size();i++) G[i]=1;
			G.push_back(1);
		}else{
			for(int i=0;i<G.size();i++){
				if(G[i]!=3){
					G[i]++;
					if(G[i]==2) for(int j=1;j<i;j++) G[j]=2;
					break;
				}
			}
		}
	}
	for(int i=G.size()-1;i>=0;i--) cout<<G[i];
	return 0;
}

[ABC333D] Erase Leaves

题意简述

给定一棵 n n n 个节点的树,每次操作可以删除一个叶子节点。问最少删除多少次可以删掉 1 1 1

解题思路

若要删除 1 1 1,则必须让 1 1 1 成为叶子节点。也就是说仅可以保留 1 1 1 与其他节点的一条边。换句话说就是可以保留 1 1 1 的一棵子树,其余全部要删除。那么我们肯定想保留一棵最大的,把其余的删掉。找最大子树可以 DFS 也可以并查集,怎么搞都行,下面的代码两种写法都给出了。

代码示例

DFS 版本。

#include
using namespace std;
#define int long long
int n,ans=0,dp[300010]={0};
//这里的dp就是记录子树大小
vector<int> G[300010];
void dfs(int u,int fa){
	dp[u]=1;
	if(G[u].size()==1&&u!=1) return;
	for(int v:G[u]){
		if(v==fa) continue;
		dfs(v,u);
		dp[u]+=dp[v];
	}
}
signed main(){
	cin>>n;
	for(int i=1;i<=n-1;i++){
		int u,v;
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
	}
	dfs(1,0);
	for(int x:G[1]) ans=max(ans,dp[x]);
	cout<<n-ans<<endl;
	return 0;
}

并查集版本。

#include
using namespace std;
#define int long long
int n,fa[300010],sz[300010],ans=0;
vector<int> G[300010];
int getfa(int x){
	return (x==fa[x]?x:fa[x]=getfa(fa[x]));
}
signed main(){
	cin>>n;
	for(int i=1;i<=n;i++) fa[i]=i,sz[i]=1;
	for(int i=1;i<=n-1;i++){
		int u,v,fau,fav;
		cin>>u>>v;
		fau=getfa(u),fav=getfa(v);
		if(fau==1||fav==1) continue;
		fa[fav]=fau,sz[fau]+=sz[fav];
	}
	for(int i=2;i<=n;i++) ans=max(ans,sz[i]);
	cout<<n-ans<<endl;
	return 0;
}

[ABC333E] Takahashi Quest

题意简述

你正在玩一款冒险游戏,游戏中一共发生了 n n n 次事件,第 i i i 次事件可以由两个整数 t i , x i t_i,x_i ti,xi 描述。具体地:

t i = 1 t_i=1 ti=1,则有一瓶 x i x_i xi 型号的药水出现,你可以选择捡起它或者无视。

t i = 2 t_i=2 ti=2,则有一个 x i x_i xi 类型的怪物出现。如果你拥有 x i x_i xi 型号的药水,则你可以消耗一瓶药水打败怪兽,否则你就被打败了。

请问你最终能否通关游戏。若不能,输出 -1。若能,输出过程中你所持有的药水总瓶数最大值的最小可能值,并给出一种方案。具体地,按照药水给出的顺序,输出 01,分别代表无视这瓶药水和捡起这瓶药水。

解题思路

考虑贪心。用 multiset 维护一个药水的待选区。如果出现了药水,我们可以将其放到待选区。如果出现了怪兽,从待选区中取出最近塞入的药水来对付它。因为我们想要过程中持有的药水总数尽可能少,所以最好是刚拿到药水就用掉,因此要选择最近的药水对付怪兽。

如何记录方案?考虑把药水放进待选区的同时将这次事件的编号记录下来,开一个 s u m sum sum 数组表示 i i i 事件发生后持有的药水数量。那么对于一瓶药水,从我们捡起它到用掉它的时刻,我们都持有它。所以用差分的想法记录答案。再开一个 n u m num num 数组记录每瓶药水是否拾取,最后输出答案即可。

代码示例

#include
using namespace std;
#define int long long
int q,ans=0,num[200010],op[200010],sum[200010];
multiset<int> s[200010];
signed main(){
	cin>>q;
	for(int i=1;i<=q;i++){
		int x;
		cin>>op[i]>>x;
		if(op[i]==1) s[x].insert(i);//塞入待选区
		else{
			if(s[x].empty()){
            //没有药水,则不能通关
				cout<<-1<<endl;
				return 0;
			}
			num[*s[x].rbegin()]=1;
            //标记,这瓶药水要选
			sum[*s[x].rbegin()]++;
			sum[i]--;
            //差分,记录药水持有数量
			s[x].erase(s[x].find(*s[x].rbegin()));
            //从待选区中删除
		}
	}
	for(int i=1;i<=q;i++) sum[i]+=sum[i-1],ans=max(ans,sum[i]);
    //做差分,记录答案
	cout<<ans<<endl;
	for(int i=1;i<=q;i++) if(op[i]==1) cout<<num[i]<<" ";
	return 0;
}

[ABC333F] Bomb Game 2

题意简述

n n n 个人按 1 , 2 , ⋯   , n 1,2,\cdots,n 1,2,,n 的顺序排成一队。重复执行以下操作直到队伍中只剩一个人:

∙ \bullet 队头的人有 1 2 \frac{1}{2} 21 的概率出队,也有 1 2 \frac{1}{2} 21 的概率跑到队尾。

分别求出每个人成为最后剩下的那个人的概率。

解题思路

很显然是一道推式子的题目。

首先,我们设 d p i , j dp_{i,j} dpi,j 表示队列剩余 i i i 个人时,第 j j j 个人成为最后剩下的人的概率。那么最终答案就是 d p n , 1 , d p n , 2 , ⋯   , d p n , n dp_{n,1},dp_{n,2},\cdots,dp_{n,n} dpn,1,dpn,2,,dpn,n

再来考虑转移。根据状态的定义,我们可以得到

d p i , j = 1 2 × ( d p i + 1 , j + 1 + d p i , j + 1 ) dp_{i,j}=\frac{1}{2}\times (dp_{i+1,j+1}+dp_{i,j+1}) dpi,j=21×(dpi+1,j+1+dpi,j+1)

分别对应第一个人出队和第一个人跑到队尾的情况。然后发现这玩意儿不对啊,这样我们怎么求 d p n , i dp_{n,i} dpn,i 啊?所以得倒着推,把式子变成这样:

d p i , j = 1 2 × ( d p i − 1 , j − 1 + d p i , j − 1 ) dp_{i,j}=\frac{1}{2}\times (dp_{i-1,j-1}+dp_{i,j-1}) dpi,j=21×(dpi1,j1+dpi,j1)

初始化是 d p 1 , 1 = 1 dp_{1,1}=1 dp1,1=1。注意 d p i , 1 = d p i , n 2 dp_{i,1}=\frac{dp_{i,n}}{2} dpi,1=2dpi,n。这个时候我们发现转移成环了,不能直接转移。但是未知数和方程数量相同,所以可以用解方程的思想来做,先把 d p i , 1 dp_{i,1} dpi,1 解出来,再转移一遍,就可以得到所有 d p i , j dp_{i,j} dpi,j 的值了。

代码示例

#include
using namespace std;
#define int long long
int n,dp[3010][3010],mod=998244353,inv;
int KSM(int a,int b){
	int ans=1;
	while(b){
		if(b&1) ans=ans*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return ans;
}
signed main(){
	cin>>n;
	inv=KSM(2,mod-2);
	dp[1][1]=1;
	for(int i=2;i<=n;i++){
		int sum=0,num=0;
		for(int j=1;j<=i-1;j++) num=inv*(num+dp[i-1][j])%mod,sum=(sum+num)%mod;
		dp[i][1]=(1-sum+mod)*KSM((2-KSM(inv,i-1)+mod)%mod,mod-2)%mod;
		for(int j=2;j<=i;j++) dp[i][j]=inv*(dp[i-1][j-1]+dp[i][j-1])%mod;
	}
	for(int i=1;i<=n;i++) cout<<dp[n][i]<<" ";
	return 0;
}

你可能感兴趣的:(CF/ATC题解,深度优先,图论,算法)