思路分析
贪心,因为\(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]; if(a[i]==1){ r=i; l=min(l,i); cnt++; } } cout<<r-l+1-cnt<<"\n"; } int main(){ int t; cin>>t; while(t--){ solve(); } return 0; }
|
思路分析
贪心,很显然,肯定是需要先打举例我们最近的怪物,再打次近的怪物,依此类推,只要在其中,有一个怪物打不死,那么我们就输了。
所以这里可以用到前缀和,预处理出前\(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; }
|
思路分析
其实就是看能否根据给出的子数组,构造出一个好数组(被翻译坑了)。
怎么构造???总和相同,但是相同位置的元素不同,并且元素都大于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]; cnt[i]=cnt[i-1]+(a[i]==0); } 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"; else cout<<"YES\n"; } } int main(){ int t; cin>>t; while(t--){ solve(); } return 0; }
|
思路分析
分析题意,很容易想到\(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){ if(x>l[i-1])return 0; 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; } 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[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; } if(check(right,i))ans[i]=min(ans[i],i-right); else if(check(left,i))ans[i]=min(ans[i],i-left); } 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; }
|
以后再补qwq