C. Least Prefix Sum Hello 2023(反悔贪心)

Problem - C - Codeforces

题意:

让你求出最小操作使得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,就反悔贪心。

现实生活中肯定也是吧,不会首先去计算拿多少,而是直接取,不够取了,把最浪费空间的那个扔掉一取更多的。

啥时候用呢,可能当你贪心的时候找最大值,第二大,第三大...然后就会发现很难写,这个时候可以用反悔贪心去贪局部以达到整体贪心的效果

*************************我是分割线~

题目找到咯

Problem - C2 - Codeforces

题目和第二题几乎一样,这里放一下代码就好了

#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;
} 
/*


*/ 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值