codeforces 391f3 Stock Trading

f1非常简单,网上也有很多题解,在此就不赘述了。

f3的做法是贪心。先差分出每天的股票价格变化,问题就转化为了求最多k段数字,使得其总和最大。

我们可以先想一想假如没有k的限制,那么我们会选所有正数。设所有正数的个数为tot,那么就得到了k>=tot的答案。k=tot与k=tot-1的区别就在于出现了2种选择:1.选择一个正数放弃;2.选择一个负数加入当前序列来连接两个正数。

操作1和操作2都可以视为其旁边的数与其合并。于是,问题就来了:怎么合并才能得到最优答案?

很明显,当Ai(Ai>0)且|Ai-1|>|Ai|(Ai-1<0),Ai+1>|Ai|(Ai+1<0)时,Ai不值得去取,就可以把Ai-1,Ai,Ai+1合并为一个;

当Ai<0时同理;

而A数组中至少有一个可以合并。

于是我们只要把当前数组大小减小到k*2以下,数组中所有正数的和即为答案。

由于每个数一旦被删,它左右两个点都要入队,再加以每次最多删去(lenA-k*2+1)/2个点的限制,这样就既保证了时间复杂度,又保证了答案的正确性。

注意:该算法在预处理时必须使差分后的数组大小为奇数,否则会死循环。

代码如下:

#include <ctime>
#include <queue>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define maxn 4000005
#define LL long long
using namespace std;
typedef pair<LL,int> ii;
ii tmp[maxn],Imid;
int n,m,An,tmpn;
int pre[maxn],next[maxn];
LL A[maxn],a[maxn];
bool flag[maxn];
bool inq[maxn];
queue<int>  Q;
inline bool isdigit(char ch){return ((ch<='9')and(ch>='0'));}
inline void read(int &x){
	char ch;
	bool flag=false;
	for (ch=getchar();!isdigit(ch);ch=getchar()) if (ch=='-') flag=true;
	for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
	x=flag?-x:x;
}
inline void read(LL &x){
	char ch;
	bool flag=false;
	for (ch=getchar();!isdigit(ch);ch=getchar()) if (ch=='-') flag=true;
	for (x=0;isdigit(ch);x=x*10+ch-'0',ch=getchar());
	x=flag?-x:x;
}
inline void write(int x){
    static const int maxlen=100;
    static char s[maxlen];
    if(!x){ putchar('0'); return; }
    if (x<0) {putchar('-');x=-x;}
    int len=0; for(;x;x/=10) s[len++]=x%10+'0';
    for(int i=len-1;i>=0;--i) putchar(s[i]);
}

int min(int a,int b){if (a<b) return a; return b;}
int max(int a,int b){if (a>b) return a; return b;}

LL Labs(LL a){if (a<0) return -a;return a;}

void prepare(){
read(n); read(m);
for (int i=1;i<=n;i++)
	read(a[i]);
An=0;
for (int i=2;i<=n;i++)
if (a[i]!=a[i-1])
{
	An++;
	A[An]=a[i]-a[i-1];
	if((An<2)&&(A[An]<=0)) An--;
	if((An>1) &&((A[An]>0)==(A[An-1]>0)) )
		{
			An--;
			A[An]+=A[An+1];
		}	
}
while (An>0 && A[An]<0) An--;
for (int i=0;i<An;i++)
	next[i]=i+1;
next[An]=0;
for (int i=1;i<=An;i++)
	pre[i]=i-1;
pre[0]=An;
A[0]=0;
}//预处理,必须保证差分后的数组大小为奇数,且为一正一负 

void addqueue(int u) {
    if (ii(Labs(A[u]),u)<=Imid && !inq[u]) {
        Q.push(u);
        inq[u]=true;
    }
}

void merge(int x){
int l=pre[x],r=next[x];
if ((l>0) and (Labs(A[l])<Labs(A[x]))) return ;
if ((r>0) and (Labs(A[r])<Labs(A[x]))) return ;//该点的绝对值必须比其前驱及后继的绝对值小 
if (!flag[x])return ;
if (l>0) pre[x]=pre[l]; 
if (r>0) next[x]=next[r];
next[pre[x]]=x;
pre[next[x]]=x;
flag[l]=0; flag[r]=0;
A[x]+=A[l]+A[r];								//合并 
if (l==0)
	{
		next[0]=next[x];
		pre[next[x]]=0;
		flag[x]=0;
	}											//因为该点为当前的第一个点,且绝对值小于后继的绝对值,不值得取 
if (r==0)
	{
		pre[0]=pre[x];
		next[pre[x]]=0;
		flag[x]=0;
	}											//同上 
    if (pre[x]>0) addqueue(pre[x]);         	//因为前驱的后继变化了,所以要把前驱放入队列中 
    if (next[x]>0) addqueue(next[x]);       	//因为后继的前驱变化了,所以要把后继放入队列中 
    addqueue(x);                            	//因为该点的前驱和后继变化了,所以要把该点放入队列中 	
}


void doit(){
memset(flag,1,sizeof(flag));
for (;;)
	{
		tmpn=0;
		for (int i=next[0];i;i=next[i])
			if(flag[i])
				{
					tmpn++;
					tmp[tmpn]=ii(Labs(A[i]),i);
				}
		if (tmpn-m*2<0) break;
		int mid=(tmpn-m*2+1)/2;
		nth_element(tmp+1,tmp+mid,tmp+tmpn+1);//nth_element(fir,pos,lst) 使tmp[1]到tmp[tmpn]中第mid小的元素位于tmp[mid],且其前面的值都比它小。
		Imid=tmp[mid];
		while (!Q.empty()) Q.pop();
		for (int i=next[0];i;i=next[i])  addqueue(i);
        while (!Q.empty()) {
            int u=Q.front();Q.pop();
            inq[u]=false;
            merge(u);
    	}
	}
LL ans=0LL;
for (int i=next[0];i;i=next[i])
	if (A[i]>0)
		ans+=A[i];
printf("%I64d\n",ans);
}


int main(){
prepare();
doit();
return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值