题意:
让你求出最小操作使得s[m]<=s[i](1<=i<=n)
操作:任选a[i],使得a[i]=-a[i]
分析:
推公式的题吧
画一画样例,可以得到s[m]是最底端。其实知道这个没啥用,知道这个不会有任何头绪的
可以发现如果在m之前的所有数包括m都是负数,m之后的数都是正的,那么s[m]为最小,但是我们现在需要求最小操作。
那就看看满足条件我们可以改的最多的数是多少个
令s[i,j]为a[i]+a[i+1]+...+a[j]的这一段和
可以推公式得出
s[i,m]<=0(2<=i<=m)
s[m+1,i]>=0(m<=i<=n)
所以我们分别来计算,因为前缀是可以通过改变前面来影响后面的。
在保证任意的(2<=i<=m)都要s[i,m]<=0,正着枚举后面会影响很多,并且也不一定是最优。在网上看到有一种类似于反悔贪心的策略去做。
首先倒着枚举:若每一次的s[i,m]<=0,就可以向前移,如果突然一下s[i,m]>0,那此时所遇到的a[i]一定是正数,我们需要<=0,根据贪心策略,选所枚举到的正数里面找一个最大的变化。既不会影响到之前枚举过的s[i,m]<=0,也实现了操作次数最小化。下一个s[m+1,i]同理。
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#include<iostream>
#include<map>
#include<cstring>
#include<vector>
#include<algorithm>
#include<cmath>
#include<queue>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PAII;
const int N=2e6+10,M=5050,INF=1e18,mod=1e9+7;
int v[N],s[N];
signed main(){
IOS;
int T;
//T=1;
cin>>T;
while(T--)
{
int n,m;
cin>>n>>m;
for(int i=1;i<=n;i++) cin>>v[i];
if(n==1)
{
cout<<"0\n";
continue;
}
priority_queue<int> a;
priority_queue<int,vector<int>,greater<int> > b;
int sum=0;
int res=0;
for(int i=m;i>=2;i--)
{
sum+=v[i];
a.push(v[i]);
if(sum>0)
{
res++;
auto t=a.top();
a.pop();
sum-=t*2;
}
}
sum=0;
for(int i=m+1;i<=n;i++)
{
sum+=v[i];
b.push(v[i]);
if(sum<0)
{
res++;
auto t=b.top();
b.pop();
sum+=abs(t*2);
}
}
cout<<res<<"\n";
}
return 0;
}
/*
后面不用管
*/
反悔贪心:
这里想说一下反悔贪心,就是类似于在枚举过程中并不知道这个策略是否是贪心正解,先选上,如果在过程中不满足条件,删掉一个“吃价值”最大的(也是基于贪心),不断的让整体变成贪心最优解。
类似上一题,以前一段为例,并不知道把什么样的数去改成相反的,那就在条件的基础上(s[i,m]<=0),如果不满足条件了,把之前所加进来的最浪费(最不贪心)的那种情况减掉,对于上面的情况就是当s[i,m]>0了,那就找枚举到的正数里面的最大值,改成负数,实现贪心最优解。
例题:
题目我就不放了,大概说一下题目意思
一个兔子有开心值,采有的蘑菇会使它的开心值增加,有的蘑菇会使它的开心值减少,问你在开心值>=0的情况下,最多采多少个蘑菇(按顺序)
分析:
这个题一看好像纯dp,但是不能这么一眼就确定下来了..
这里减少最多开心值的蘑菇是最吃开心值的,所以我们要避免去选,但是找最大值,次大值...会很麻烦,这里我们就选用反悔贪心来考虑。
兔子一直采好了,然后在它开心值<0的时候把最不开心的时候给删掉,然后兔子继续采,直到采不动。
代码:
priority_queue<int,vector<int>,greater<int> >q;
cin >> n ;
int cnt = 0;
int sum = 0;
for(int i = 1 ; i <= n ; i ++ )
{
cin >> a[i];
sum += a[i];
if(a[i] > 0) cnt++;
else
{
q.push(a[i]);
cnt++;
if(sum < 0){
int t = q.top();
q.pop();
sum -= t;
cnt--;
}
}
}
cout<<cnt<<"\n";
看到上面的两题,简而言之就是:
正常去写题目,按照限制条件去写。比如第一个s[i,m]>=0,那就保证s[i,m]>=0呗,如果不满足条件了,把最大的正数变成负数进行反悔贪心
第二个题目,那就正常采蘑菇,如果开心值<0,就反悔贪心。
现实生活中肯定也是吧,不会首先去计算拿多少,而是直接取,不够取了,把最浪费空间的那个扔掉一取更多的。
啥时候用呢,可能当你贪心的时候找最大值,第二大,第三大...然后就会发现很难写,这个时候可以用反悔贪心去贪局部以达到整体贪心的效果
*************************我是分割线~
题目找到咯
题目和第二题几乎一样,这里放一下代码就好了
#pragma GCC optimize(1)
#pragma GCC optimize(2)
#pragma GCC optimize(3,"Ofast","inline")
#define IOS ios::sync_with_stdio(false), cin.tie(0);
#include<iostream>
#include<map>
#include<set>
#include<cstdio>
#include<cstring>
#include<vector>
#include<stack>
#include<algorithm>
#include<cmath>
#include<queue>
#include<deque>
using namespace std;
#define int long long
typedef long long ll;
typedef pair<int,int> PAII;
const int N=2e6+10,M=5050,INF=1e18,mod=1e9+7;
int a[N];
signed main(){
//IOS;
int T;
T=1;
//cin>>T;
while(T--)
{
priority_queue<int,vector<int>,greater<int> > q;
int n;
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
int cnt=0,sum=0;
for(int i=1;i<=n;i++)
{
if(a[i]>=0)
{
cnt++;
sum+=a[i];
}
else
{
q.push(a[i]);
sum+=a[i];
cnt++;
if(sum<0)
{
auto t=q.top();
cout<<t<<endl;
q.pop();
sum+=abs(t);
cnt--;
}
}
}
cout<<cnt<<"\n";
}
return 0;
}
/*
*/