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;
}