Problem A

思路分析

贪心,因为\(chips\)只能左移到最近的空格子,所以其实只需要考虑在最长的以1开头和以1结尾的串内(记作\(s\))移动即可(其他部分没用,不然只会做多余的移动)。 至于怎么移,可以想象把从第一个1串右端的所有1串保持成串的形式,轮流滚近第一个1串,恰好接上为止,可以证明,滚的次数恰好就是\(s\)内0的个数。 画个图比较好理解: 在这里插入图片描述 不难发现:答案就是\(s\)中0的个数,可以多结合几个样例分析一下。 ### 代码实现

#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+10;
void solve(){
int n,l=MAXN,r,cnt=0;
cin>>n;
vector<int> a(n);
for(int i=0;i<n;i++){
cin>>a[i];
//预处理出最长的$s$串的左右端点,注意l初始化为MAXN,并且统计出1的个数
if(a[i]==1){
r=i;
l=min(l,i);
cnt++;
}
}
cout<<r-l+1-cnt<<"\n";//求出0的个数,也就是最少的移动次数
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}

Problem B

思路分析

贪心,很显然,肯定是需要先打举例我们最近的怪物,再打次近的怪物,依此类推,只要在其中,有一个怪物打不死,那么我们就输了。 所以这里可以用到前缀和,预处理出前\(i\)个最近的怪物们的总血量(记作\(pre[i]\)),如果在对应的这段时间内,(也就是\(abs(a[i].pos)\)),我们打出的子弹数\(k*abs(a[i].pos)<pre[i]\),那么我们就输出\(NO\)直接判掉。 注意得先派个序,按距离从小到大,可以用结构体+\(cmp\)。 ### 代码实现

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int MAXN=1e6+10;
struct node{
ll hp,pos;
};
bool cmp(node a,node b){
return abs(a.pos)<abs(b.pos);
}
void solve(){
ll n,k,ans=0;
cin>>n>>k;
vector<node> a(n);
for(int i=0;i<n;i++)cin>>a[i].hp;
for(int i=0;i<n;i++)cin>>a[i].pos;
sort(a.begin(),a.end(),cmp);
for(int i=0;i<n;i++){
ans+=a[i].hp;
if(ans>k*abs(a[i].pos)){
cout<<"NO\n";
return;
}
}
cout<<"YES\n";
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}

Problem C

思路分析

其实就是看能否根据给出的子数组,构造出一个好数组(被翻译坑了)。 怎么构造???总和相同,但是相同位置的元素不同,并且元素都大于0。 有一个比较容易实现的想法:就是在原有的基础上,部分元素加1,显然,有一些元素需要减少,但是又不能减太多(不然就小于0了)。 这其中就有一个比较特别的元素:1。1只能加,不能减,那么要加多少个1(记作\(x\))就只能由其他非一元素(能够贡献的记作\(y\))贡献了。

代码实现

可以用前缀和,预处理出在各个区间内1的个数,以及各个区间内非一元素的最大贡献。 这里有用到一些小技巧:可以先在输入\(a[i]\)时,将\(a[i]--\),这样求出来\(sum[r]-sum[l-1]\)就是\(y\)了。 需要注意特判一下:如果\(l==r\),那么我们就无法构造出好数组(很显然)。

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int MAXN=1e6+10;
void solve(){
ll n,q,l,r,tot;
cin>>n>>q;
vector<ll> a(n+1),cnt(n+1),sum(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
a[i]--;
sum[i]=sum[i-1]+a[i]; //预处理出前i个元素的最大贡献
cnt[i]=cnt[i-1]+(a[i]==0); //预处理出前i个元素中1的个数
}
while(q--){
cin>>l>>r;
ll x=cnt[r]-cnt[l-1];
ll y=sum[r]-sum[l-1];
if(l==r||x>y)cout<<"NO\n"; //无法满足或者长度为1,就NO,否则YES
else cout<<"YES\n";
}
}
int main(){
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}

Problem D

思路分析

分析题意,很容易想到\(i-nd\)史莱姆只能被左边的或者右边的吃掉,先分析左边的情况。 假设\(i\)(偷懒,其实是\(i-nd\)史莱姆,下面也一样)被左边的史莱姆吃掉,先做一些定义: \(ans[i]\):被吃掉的最小操作次数。 \(l[i]\):第\(i\)个数字的上一个与\(a[i]\)不同的位置。 \(left\):记录一个区间的左端点。

如果存在\(left\),使得: 1、\([left,i-1]\)区间的和>\(ans[i]\)。 2、\([left,i-1]\)区间数字种类\(>=2\)(因为相同不能互相吞并,那么最少需两种大小不同的史莱姆)。 那么操作次数就是:\(i-left\)

可以发现\(i-1\)其实是固定的(对于每一个\(i\)来说),并且\(left\)越小,区间和越大,反之,区间和越小,所以想到可以用二分。 那么我们就需要用前缀和求出区间和,再用二分求出端点位置。

但是怎么判断\([left,i-1]\)区间数字种类\(>=2\),这时候\(l[i]\)就派上用场了。 二分的左右端点分别为\(left\)(这里的\(left\)与上面的\(left\)无关,是完全不同的两个),\(right\)。中间值为\(mid\)。 如果\(mid>l[i-1]\),那么第二个条件就不成立(此时区间数字种类只有1).

基本上就分析完了,但是如果是右边怎么办,我们只需要将数组反转即可。

代码实现

一些细节可以看注释

#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const int MAXN=2e6+10;
vector<ll> a(MAXN),pre(MAXN),l(MAXN),ans(MAXN),b(MAXN);
inline bool check(int x,int i){
//其实跟下面的条件2判断一样,也就是前面的一段全都是同一个数字
//思路是参考RegenFallen大佬的,但我感觉这一行不用加上去,因为不可能二分到位置为0,试了一下,删去也AC了,所以加不加无所谓
//if(x<=0)return 0;

//条件2:区间内数字种类小于2
if(x>l[i-1])return 0;
//条件1
if(pre[i-1]-pre[x-1]>a[i])return 1;
else return 0;
}
inline void solve(){
int n;
cin>>n;
for(int i=1;i<=n;i++){
pre[i]=l[i]=0;
}
for(int i=1;i<=n;i++){
cin>>a[i];
b[n-i+1]=a[i]; //先反转,存起来
ans[i]=1e9; //ans[i]先预处理为1e9
}
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+a[i]; //预处理出前缀和
if(a[i]==a[i-1])l[i]=l[i-1]; //预处理出l[i],可以举几个例子:0333332222
else l[i]=i-1;
}
for(int i=1;i<=n;i++){
if(a[i]<a[i-1]){
//特殊情况判断
ans[i]=1;
continue;
}
ll left=1,right=i-1;
while(left+1<right){
ll mid=(left+right)/2;
if(check(mid,i))left=mid;
else right=mid;
}
//左右端点都得判,先判右,再判左,可以保证求出来ans[i]最小,并且有可能右端点不满足(check==false),所以左端点也必须得判断。
if(check(right,i))ans[i]=min(ans[i],i-right);
else if(check(left,i))ans[i]=min(ans[i],i-left);
}
//接下来就是相同的操作,但是注意ans数组的下标要反转
for(int i=1;i<=n;i++){
a[i]=b[i];
}
for(int i=1;i<=n;i++){
pre[i]=l[i]=0;
}
for(int i=1;i<=n;i++){
pre[i]=pre[i-1]+a[i];
if(a[i]==a[i-1])l[i]=l[i-1];
else l[i]=i-1;
}
for(int i=1;i<=n;i++){
if(a[i]<a[i-1]){
ans[n-i+1]=1;
continue;
}
ll left=1,right=i-1;
while(left+1<right){
ll mid=(left+right)/2;
if(check(mid,i))left=mid;
else right=mid;
}
if(check(right,i))ans[n-i+1]=min(ans[n-i+1],i-right);
else if(check(left,i))ans[n-i+1]=min(ans[n-i+1],i-left);
}
for(int i=1;i<=n;i++){
cout<<(ans[i]==1e9?-1:ans[i])<<" ";
}
cout<<"\n";
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
int t;
cin>>t;
while(t--){
solve();
}
return 0;
}

Problem E

以后再补qwq