【题解】Atcoder ABC368 A~D
A - Cut
题意分析
给定 \(N\) 个整数,要求进行 \(K\) 次操作,每次将数列末尾的数放到最前面,输出操作过后的数列。
解题思路
虽然题目中说的是栈,但其实一眼就能看出来是队列。
每次把末尾的数放到最前面就对应了一个从队尾出队和从队头入队的操作。
不过这样比较绕,因为一般的队列都是队头出队队尾入队的。
因此我们不妨在存的时候就倒序存,操作就按照一般队列来,输出也倒着输出。
时间复杂度显然为 \(O(n)\)。
代码实现
#include<iostream>
using namespace std;
int n,k;
int front=1,rear;
int a[10010];
int main(){
cin>>n>>k;
rear=n+1;
for(int i=1;i<=n;i++){
cin>>a[n-i+1];
}
for(int i=1;i<=k;i++){
int f=a[front];
front++;
a[rear]=f;
rear++;
}
for(int i=rear-1;i>=front;i--){
cout<<a[i]<<" ";
}
return 0;
}
B - Decrease 2 max elements
题意分析
给定 \(N\) 个正整数,要求每次操作都将把这 \(N\) 个数降序排列,并将排列好的数列的前两项减 \(1\)。求操作多少次,能使序列中的正整数数量不多于 \(1\)。
解题思路
看到数据范围就明白了,这个题就是让打暴力的。
模拟题目中的操作,每次操作完成统计正整数的数量,如果大于 \(1\) 就继续操作,否则跳出。
代码实现
#include<iostream>
#include<algorithm>
using namespace std;
int n,cnt,ans;
int a[110];
bool cmp(int a,int b){
return a>b;
}
int main(){
cin>>n;
cnt=n;
for(int i=1;i<=n;i++){
cin>>a[i];
}
while(cnt>1){
cnt=0;
sort(a+1,a+1+n,cmp);
a[1]--;a[2]--;
for(int i=1;i<=n;i++){
if(a[i]>0) cnt++;
}
ans++;
}
cout<<ans;
return 0;
}
C - Triple Attack
题意分析
给定 \(N\) 个正整数和一个初始为 \(0\) 的变量 \(T\),从第一个数开始,每次操作将 \(T\) 增加 \(1\),如果 \(T\) 为 \(3\) 的倍数那么就给当前数减 \(3\),否则减 \(1\),直到当前数小于等于 \(0\) 后,换下一个数继续操作。
解题思路
数据范围告诉我们暴力显然是不现实的。
于是考虑 \(T\) 的性质。\(T\) 的值是用来判断是否为 \(3\) 的倍数的,也就是说 \(T\) 的大小完全用不到,用到的只是 \(T\) 除以 \(3\) 的余数。
那么我们就可以这样做。首先先把 \(T\) 在 \(3\) 的倍数以前的情况单独减去,然后将 \(T\) 每一轮循环所减的值 \(5\) 集中减去,然后再处理剩余值的情况。在此同时统计操作次数即可。
时间复杂度显然为 \(O(n)\)。
代码实现
#include<iostream>
#include<cstdio>
#define int long long
using namespace std;
const int maxn=2e5+10;
int n,t,thr;
int flag,ans;
int h[maxn],s[maxn],b[maxn];
signed main(){
scanf("%lld",&n);
for(int i=1;i<=n;i++){
scanf("%lld",&h[i]);
b[i]=h[i];
}
for(int i=1;i<=n;i++){
flag=0;
thr=2-t;//单独处理T在3的倍数以前的情况
for(int j=1;j<=thr;j++){
b[i]--;
t++;
if(b[i]<=0){
flag=j;
break;
}
}
if(flag){
s[i]=flag;//记录操作次数
continue;
}
//集中处理T的循环
s[i]+=thr;
s[i]+=((b[i]/5)*3);
b[i]%=5;
//处理完5后T会停在2的位置,因此小于等于3可以一次减掉
if(b[i]){
if(b[i]<=3){
s[i]++;
t++;
}
else if(b[i]==4){
s[i]+=2;
t+=2;
}
}
t%=3;//只保留余数
}
for(int i=1;i<=n;i++) ans+=s[i];
cout<<ans;
return 0;
}
注意事项
题目中也说了,注意开 long long。
D - Minimum Steiner Tree
题目大意
给定一棵树,以及 \(K\) 个必选结点。求如何在 \(K\) 个结点都被包含的情况下,构建一棵结点最少的树。
解题思路
考虑从一个根节点遍历这棵树。设当前遍历到的结点为 \(u\),\(u\) 的子结点为 \(v\)。当以 \(v\) 为根的子树有需要选的结点时,\(v\) 和 \(u\) 都需要选。由此遍历完一遍后,所有需要选的结点就都被打上标记了。
不过有一个点需要注意,那就是遍历根节点的选择。为了保证根节点一定是目标树上的结点而不是从哪里斜插过来的无用结点,我们这里直接用一个必选结点当做根结点来遍历,这样就不会有问题了。
时间复杂度为 \(O(n)\) 比某人的数剖要好多了。
代码实现
这里我们可以将 dfs 定义为 bool 类型,方便判断是否需要选取。
#include<iostream>
#include<cstdio>
using namespace std;
const int maxn=2e5+10;
struct Node{
int to,nxt;
}e[2*maxn];
int n,k,r,ans;
int h[maxn],tot;
bool vis[maxn],visv[maxn],visc[maxn];
void Add(int u,int v){
tot++;
e[tot].to=v;
e[tot].nxt=h[u];
h[u]=tot;
}
bool dfs(int root,int fa){
vis[root]=1;
for(int i=h[root];i;i=e[i].nxt){
int v=e[i].to;
if(v==fa) continue;
if(dfs(v,root)) visc[root]=1;
}
if(visv[root]||visc[root]){
visc[root]=1;
return 1;
}else return 0;
}
int main(){
scanf("%d%d",&n,&k);
for(int i=1;i<=n-1;i++){
int a,b;
scanf("%d%d",&a,&b);
Add(a,b);
Add(b,a);
}
for(int i=1;i<=k;i++){
int V;
scanf("%d",&V);
visv[V]=1;
r=V;
}
dfs(r,0);
for(int i=1;i<=n;i++){
if(visc[i]) ans++;
}
cout<<ans;
return 0;
}
注意事项
老生常谈双向边。

浙公网安备 33010602011771号