titi题意:
对于一个数列,一共可以进行k次操作,每次操作有两种选择,要么删掉最小的两个,要么删掉最大的一个,求最后最多还有多少
思路:一开始想用双向队列,优先队列,但是好像操作要么是自身stl库没有,要么就是实现起来复杂度太高,放弃
其实可以将k看成一个和,前面后面各分其中1part,然后得到剩余的,那么其实只要枚举分的方式,然后对于每种方式求出其对应的值更新答案
求值怎么求呢,可以考虑用前缀和优化,删去前面两个,就是减去sum[i*2],删去最后就是直接把被减数的sum往前挪就好了
#include<iostream>
#include<cstring>
#include<algorithm>
#define int long long
using namespace std;
const int N=2e5+10;
int a[N];
int t;
int sum[N];
int tot;
int n,k;
int ans;
signed main()
{
cin>>t;
while(t--)
{
cin>>n>>k;
tot=0;
ans=0;
for(int i=1;i<=n;i++)
{
cin>>a[i];
}
sort(a+1,a+n+1);
for(int i=1;i<=n;i++)
{
sum[i]=sum[i-1]+a[i];
}
for(int i=0;i<=k;i++)
{
if(2*i+k-i>n)continue;
ans=max(ans,sum[n-(k-i)]-sum[i*2]);
}
cout<<ans<<endl;
}
}
思路:找峰值谷值就好啦
怎么找其实还是挺容易写错的:对于和我自己相同的当成一个就好了,然后根据当前我是处于增加,还是减少,设置标志,进行向下的循环,以我标志的状态和接下去一个与我的状态是否符合作为标准,直到出现下一个转折点,然后把ans++,同时记得i=j-1,因为马上i++;
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
using namespace std;
const int N=5e6+10;
int a[N];
int t,n;
int cot;
int ans;
bool check1(int j)
{
return a[j]<=a[j+1];
}
bool check2(int j)
{
return a[j]>=a[j+1];
}
int main()
{
cin>>t;
while(t--)
{
cin>>n;
cot=0;
ans=0;
int flag=3;
int fflag=0;
for(int i=0;i<n;i++)
{
cin>>a[i];
if(i&&a[i]!=a[i-1])fflag=1;
}
for(int i=0;i<n;i++)
{
int j=i;
while(j+1<n&&a[j+1]==a[i])j++;
if(j+1<n)
{
if(a[j]<a[j+1])flag=1;
else flag=0;
j++;
while(j+1<n&&(flag?check1(j):check2(j)))j++;
ans+=1;
i=j-1;
}
else break;
}
if(fflag==0)cout<<1<<endl;
else cout<<ans+1<<endl;
}
}
题意:给了一个数列,然后呢大家一开始都只能进行增加操作,然后是减少增加操作交替进行,一共有k个数,(1-k)可以对这个数列进行合理的增加和减少,问k此操作后,所有可能结果中,最大的最小值是多少
分析:那么肯定使用二分,二分一个最小值,如果可以构造出来一个数列使得其所有值都大于等于这个mid,那么就可以继续探索大于mid的了
那么判断函数怎么写呢
首先获取在这个数列中小于mid,需要进行增加操作的个数
1.个数大于k,必然不可能 个数等于零,直接正确
2.判断到底可不可以,我们先对整个序列进行了排序,然后遵循着小的增加大的,也即是(a[0]+k,a[1]+k-1...),只要判断最小的a[p]-p+k是否大于mid,就可以知道成不成立,这个可以通过预处理得出
2.个数等于n,就是说所有都小,同时剩下来的k的操作数是个奇数,那么根据先减再增,必然失败
3.因为大于mid其本身就ok,尽量减少对他们的操作,先让那些小的来消耗掉k,怎么消耗呢?我们是把这个数列进行排序,部分经过+k后会大于mid 那么接下去可以通过-1 (+1-2),来进行消耗,也就是每一个可以消耗掉 1+(k+a[i]-i-mid)*2
这个式子对于所有的小于mid的求和,就是剩余的k为:k-cnt-2*((k-mid)*(cnt)+sum[cnt-1])
sum是预处理出来的前缀和
4.经过上一步之后,如果还有剩余的k,如果k是个奇数,那么进行+-+必然可以,或者剩余两个数进行操作,那么进行分担后也必然可以
5.否则就判断那个唯一一个进行k此操作后是否>=mid
#include<iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<vector>
#define int long long
using namespace std;
const int N=1e8;
vector<int>a,pre,mn;
typedef long long ll;
int n,q,k;
long long remain;
signed main()
{
cin >> n >> q;
vector<int> a(n);
for(int i = 0; i < n; i++) cin >> a[i];
sort(a.begin(), a.end());
auto mn = a;
for(int i = 0; i < n; i++) mn[i] -= i;
auto pre = mn;
for(int i = 1; i < n; i++) pre[i] += pre[i - 1];
for(int i = 1; i < n; i++) mn[i] = min(mn[i], mn[i - 1]);
auto check = [&](int mid){
int cnt = lower_bound(a.begin(), a.end(), mid) - a.begin();
if (cnt == 0) return true;
if (cnt > k) return false;
if (cnt == n && (k - cnt) % 2) return false;
if (mn[cnt - 1] + k < mid) return false;
if (cnt == n){
int remain = k - cnt - 2 * (cnt * (k - mid) + pre[cnt - 1]);
return remain <= 0;
}
int remain = k - cnt - 2 * (cnt * (k - mid) + pre[cnt - 1]);
if (remain <= 0 || remain % 2 == 1) return true;
if (n - cnt >= 2) return true;
if (a.back() - remain / 2 >= mid) return true;
return false;
};
while(q--)
{
cin>>k;
if(n==1)
{
if(k%2==0)cout<<a[0]-k/2<<" ";
else cout<<a[0]-k/2+k<<" ";
continue;
}
int l = 1, r = 1e10;
while(l < r){
int mid = (l + r + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
cout << r << ' ';
}
cout << '\n';
}