感觉这场难度 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;
}
给定一个正五边形,顶点按顺时针方向依次为 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=5−n2,m1=5−m2,所以代码就很好写了。
#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;
}
定义好数为仅由 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;
}
给定一棵 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;
}
你正在玩一款冒险游戏,游戏中一共发生了 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
。若能,输出过程中你所持有的药水总瓶数最大值的最小可能值,并给出一种方案。具体地,按照药水给出的顺序,输出 0
和 1
,分别代表无视这瓶药水和捡起这瓶药水。
考虑贪心。用 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;
}
有 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×(dpi−1,j−1+dpi,j−1)
初始化是 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;
}