Educational Codeforces Round 162 (Rated for Div. 2) - VP记录
Preface
这次状态不是很好,B 题做的稍微久了一点。D 题一眼秒出做法,可惜很多细节没考虑到导致 WA 了三发。
下次应当在做题的时候多多考虑细节,手推数据。而不要想着撞大运撞对。
A. Moving Chips
每一次移动可以补齐一个 \(0\),所以找 \(1\) 中穿插的 \(0\) 的数量即可,注意两边的 \(0\) 要忽略。
点击查看代码
#include<cstdio>
using namespace std;
const int N=55;
int n;
bool a[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&a[i]);
int left=1,right=n;
while(!a[left]) left++;
while(!a[right]) right--;
if(left<=right)
{
int ans=0;
for(int i=left;i<=right;i++)
if(!a[i]) ans++;
printf("%d\n",ans);
}
else printf("0\n"); //all 0
}
return 0;
}
B. Monsters Attack!
这道题做的稍久一点。
因为向左和向右打没有任何区别,所以左右两边的怪物可以全部映射到一边(把左边翻转到右边)。
然后运用贪心,每次打距离最近的怪物。设 \(sum_i\) 表示在区间 \([1,i]\) 中怪物的血量之和,也是需要的子弹数量,\(i\) 同时也是这个区间内所有怪物走到 \(0\) 所用时间,\(i \times k\) 就是这段时间内可以打出去的子弹数量。
如果存在 \(i\) 使得 \(i\times k < sum_i\),代表在时间 \(i\) 以内,子弹数量不足以在有怪物到达 \(0\) 之前清空所有怪物,可怜的玩家就被吃掉啦。
否则,如果所有的 \(i \times k \ge sum_i\),那么就可以保证怪物一定会在到达之前被处理掉。
点击查看代码
#include<cstdio>
using namespace std;
const int N=3e5+5;
int n,k,hp[N],pos[N];
long long sum[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)
scanf("%d",&hp[i]);
for(int i=1;i<=n;i++)
{
scanf("%d",&pos[i]);
if(pos[i]<0) pos[i]=-pos[i];
sum[pos[i]]+=hp[i];
}
bool ans=true;
for(int i=1;i<=n;i++)
{
sum[i]+=sum[i-1];
if(sum[i]>1ll*i*k)
{
ans=false;
break;
}
}
printf("%s\n",ans?"YES":"NO");
for(int i=1;i<=n;i++)
sum[i]=0;
}
}
C. Find B
假设查询区间内没有 \(1\),那么 \(b\) 可以很容易构造出来,比如说 \(b\) 可以由一个特别大的数和一大堆 \(1\) 构成。
但是有 \(1\) 就不一样了。贪心地想,为了尽量降低这些 \(1\) 的影响,应该在构造的 \(b\) 中对这些数取 \(2\),因为总和一定,所以这样可以让剩下的数之和尽量大,剩下的数的发挥空间也就更大。
而剩下的数想要能够将总和补齐,最多也就是全部在 \(b\) 中降成 \(1\),如果剩下不是 \(1\) 的数全部降成 \(1\) 后仍然不能补齐总和,代表这个区间无法构造合法的 \(b\)。
具体来说,设区间长度为 \(len\),区间总和为 \(sum\),\(1\) 的数量为 \(one\),那么不是 \(1\) 的数的数量就是 \(len-one\),这些数的总和是 \(sum-one\),在 \(b\) 中,它们的和最多可以降低 \((sum-one)-(len-one)=sum-len\),而将全部的 \(1\) 升为 \(2\) 至少需要增加 \(one\),那么如果 \(sum-len \ge one\),输出 YES
,否则输出 NO
。
点击查看代码
#include<cstdio>
using namespace std;
const int N=3e5+5;
int n,q,a[N];
int one[N]; long long sum[N];
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i]);
if(a[i]==1) one[i]=one[i-1]+1;
else one[i]=one[i-1];
sum[i]=sum[i-1]+a[i];
}
for(int i=1;i<=q;i++)
{
int l,r; scanf("%d%d",&l,&r);
if(l==r) printf("NO\n");
else
{
int len=r-l+1,tone=one[r]-one[l-1];
long long tsum=sum[r]-sum[l-1];
if(sum-len>=tone) printf("YES\n");
else printf("NO\n");
}
}
}
return 0;
}
D. Slimes
真恶心,洛谷怎么敢评的黄?
首先,如果某段区间内所有史莱姆不是全部一样,那么最后一定可以合成为一个大史莱姆。
其次,因为史莱姆只能吃相邻的史莱姆,所以某个史莱姆一定是被左右两边的史莱姆吃掉的,这里我们先讨论被左边的史莱姆吃掉,被右边吃掉只需要翻转一下序列再求一下就可以了,甚至不用多写一个函数。
设需要吃掉史莱姆 \(i\) 所需要的最短区间为 \([l,i-1]\),\(l\) 就需要在保证 \(\sum_{j=l}^{i-1}a_j > a_i\) 的前提下尽量大。设 \(sum\) 为 \(a\) 的前缀和,上式可以转化成 \(sum_{i-1}-sum_{l-1}>a_i\),进一步转化为 \(sum_{i-1}-a_i>sum_{l-1}\)。当枚举 \(i\) 的时候,因为 \(i\) 确定,所以 \(sum_{i-1}-a_i\) 确定,又因为 \(sum_{l-1}\) 具有单调性,所以可以二分来找到满足上述条件的最大 \(l\)。
二分可以直接用 lower_bound
,然而因为合并的区间元素不能全部相同,所以还要记录“最后一个与 \(a_{i-1}\) 不同的数”的位置 \(end\),在这个位置以后直到 \(i-1\) 的所有数都相同,所以找的时候只能在这个位置及以前找。
同时还要特判 \(a_i\) 单被 \(a_{i-1}\) 吃的情况,此时虽然不能合并,但仍然能吃。
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=3e6+5;
int n,ans[N];
long long a[N],sum[N];
void Solve(bool is_rev)
{
int end=0;
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];
int cur=is_rev?n-i+1:i;
if(a[i-1]>a[i]) ans[cur]=1;
if(a[i]<sum[i-1] && end) //end>0保证区间合法
{
if(a[i]<a[i-1]) ans[cur]=min(ans[cur],1);
int pos=lower_bound(sum+1,sum+(end-1)+1,sum[i-1]-a[i])-sum-1+1;
ans[cur]=min(ans[cur],i-pos);
}
if(a[i]!=a[i-1]) end=i-1;
}
return;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
{
scanf("%lld",&a[i]);
ans[i]=0x3f3f3f3f;
}
Solve(false);
reverse(a+1,a+n+1);
Solve(true);
for(int i=1;i<=n;i++)
printf("%d ",ans[i]==0x3f3f3f3f?-1:ans[i]);
putchar('\n');
}
return 0;
}
E. Count Paths
设 \(cnt_i\) 表示对于当前节点,颜色为 \(i\) 的可用点数量。
执行 DFS,每次将答案加上 \(cnt_{col_x}\),代表这些点都能和 \(x\) 组成组。
在递归子树的过程中,因为 \(cnt\) 此时存的点都不在这棵子树内。且 \(x\) 会挡住其它所有颜色为 \(col_x\) 的点,只留自己一个,所以此时让 \(cnt_{col_x}\) 赋值为 \(1\)。
回溯到 \(x\) 的时候,接下来要访问除 \(x\) 子树外的其它节点,同理,子树中所有颜色为 \(col_x\) 的点都会被 \(x\) 挡住,所以子树中无论 \(cnt_{col_x}\) 是多少都不算数,直接令 \(cnt_{col_x}\) 为它在访问子树前的值加一(所以之前还要对这个值做一个备份),加一是因为 \(x\) 本身也可以用来组成对。
#include<cstdio>
using namespace std;
const int N=2e5+5;
int n,col[N];
long long ans;
struct Allan{
int to,nxt;
}edge[N<<1];
int head[N],idx;
inline void add(int x,int y)
{
edge[++idx]={y,head[x]};
head[x]=idx;
return;
}
int cnt[N];
void DFS(int x,int fa=0)
{
ans+=cnt[col[x]]; //能和x组成组
int bkup=cnt[col[x]];
for(int i=head[x];i;i=edge[i].nxt)
{
int y=edge[i].to;
if(y==fa) continue;
cnt[col[x]]=1; //顶掉了以前的所有颜色,只剩自己
DFS(y,x);
}
cnt[col[x]]=bkup+1;
return;
}
void ClearData()
{
for(int i=1;i<=n;i++)
head[i]=cnt[i]=0;
idx=ans=0;
return;
}
int main()
{
int T; scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++)
scanf("%d",&col[i]);
for(int i=1;i<n;i++)
{
int x,y; scanf("%d%d",&x,&y);
add(x,y),add(y,x);
}
DFS(1);
printf("%lld\n",ans);
ClearData();
}
return 0;
}
本文采用 「CC-BY-NC 4.0」 创作共享协议,转载请注明作者及出处,禁止商业使用。
作者:Jerrycyx,原文链接:https://www.cnblogs.com/jerrycyx/p/18525824