题意:给一个长度为n的序列,要求选出m个不相交的部分,要求总和最大,如果m>= n个数中正数的个数,那么输出所有正数的和。。
V1:n,m<=5000.
V2:n,m<=50000.
V3:n,m<=500000.
V1:
一个简单的DP。
明显有f[i][j]表示做到第i个,选择了j段。
那么可以推导:
新开一段f[i][j]=f[i−1][j−1]+a[i]
不选f[i][j]=f[i−1][j]
承接前一段f[i][j]=max(f[i−1][j]+a[i]),其实并不是这样做的,这样子写很明显是错的,想想为什么。
因为这样不能保证连续k个数相邻(假设第j段取了k个)。
那么我们就要换一种思想。
设f[i][j]表示前i个数字,选择了j段,不一定选j。
g[i][j]表示一定选j。
那么有:
g[i][j]=max(f[i−1][j]+a[i],g[i−1][j]+a[i])
f[i][j]=max(f[i−1][j],g[i][j])
是我很久以前做的了,所以是pascal的,方法不是我上面的这个,是f_zyj的。
uses math;
var
dp,pre,a:array[0..1000000]of int64;
k,p,m,n,ans:int64;
i,j:longint;
begin
readln(n,m);
dp[0]:=-maxlongint;
for i:=1 to n do
begin
readln(a[i]);
dp[i]:=-maxlongint;
end;
pre[0]:=0;
ans:=maxlongint;
for j:=1 to m do
begin
ans:=-maxlongint;;
for i:=1 to n do
begin
if i=1 then dp[i]:=pre[i-1]+a[i]
else
begin
if dp[i-1]>pre[i-1] then
dp[i]:=dp[i-1]+a[i]
else dp[i]:=pre[i-1]+a[i];
end;
pre[i-1]:=ans;
ans:=max(ans,dp[i]);
end;
pre[n]:=ans;
end;
writeln(pre[n]);
end.
V2,V3其实都是同一种方法,时间复杂度nlogn。
具体方法是贪心。
把所有连续正数,负数段处理出来,接下来的问题就是如何处理。
如果正数段<=m直接选,>m我们贪心做。
每次找到绝对值最小的一个,然后用小根堆和左右两个合并,可以证明最优:
设S(i)表示第i个数(绝对值最小)(合并以后)的绝对值。
1.如果i是负数,那么相邻两个是正数,权值减少S(i)。
2.如果i是正数,那么相邻两个是负数,权值仍然减少S(i).
一直处理直到正数<=m为止。
V2也是bzoj2288的原题。
V2:
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define abs(x) (x>0?x:-x)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e6+5;
int n,m,l[N],r[N];
typedef long long ll;
ll a[N];
struct node
{
int x;
bool operator <(const node &y) const
{
return abs(a[x])>abs(a[y.x]);
}
};
priority_queue<node>q;
int main()
{
scanf("%d%d",&n,&m);
ll y=0,ans=0;
int tot=0;
fo(i,1,n)
{
ll x;
scanf("%lld",&x);
if(!x)continue;
if (!y)
{
y=x;
continue;
}
if (x>0)
{
if (y>0)y+=x;
else a[++tot]=y,y=x;
}
else
{
if (y<0)y+=x;
else a[++tot]=y,y=x;
}
}
a[++tot]=y;
int cnt=0;
fo(i,1,tot)
{
if (a[i]>0)++cnt,ans+=a[i];
l[i]=i-1,r[i]=i+1;
q.push((node){i});
}
l[0]=0;r[0]=1;
l[tot+1]=tot,r[tot+1]=tot+1;
int tmp,L,R;
while (cnt>m)
{
int x=q.top().x;
q.pop();
if (l[r[x]]!=x||r[l[x]]!=x)continue;
if (!a[x]||(a[x]<0&&(l[x]<1||r[x]>tot)))continue;
ans-=abs(a[x]),--cnt;
a[x]=a[x]+a[l[x]]+a[r[x]];
L=l[l[x]],R=r[r[x]];
l[x]=L,r[x]=R,r[L]=x,l[R]=x;
q.push((node){x});
}
printf("%lld\n",ans);
}
至于V3,其实也差不多,只是把首尾相接然后一起做罢了。
稍微有点小细节修改一下就OK
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<queue>
#define abs(x) (x>0?x:-x)
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=1e6+5;
int n,m,l[N],r[N];
typedef long long ll;
ll a[N];
struct node
{
int x;
bool operator <(const node &y) const
{
return abs(a[x])>abs(a[y.x]);
}
};
priority_queue<node>q;
int main()
{
scanf("%d%d",&n,&m);
ll y=0,ans=0;
int tot=0;
fo(i,1,n)
{
ll x;
scanf("%lld",&x);
if(!x)continue;
if (!y)
{
y=x;
continue;
}
if (x>0)
{
if (y>0)y+=x;
else a[++tot]=y,y=x;
}
else
{
if (y<0)y+=x;
else a[++tot]=y,y=x;
}
}
a[++tot]=y;
while (tot>1&&((a[tot]>0&&a[1]>0)||(a[tot]<0&&a[1]<0)))
a[1]+=a[tot],tot--;
int cnt=0;
fo(i,1,tot)
{
if (a[i]>0)++cnt,ans+=a[i];
l[i]=i-1,r[i]=i+1;
q.push((node){i});
}
l[1]=tot;r[tot]=1;
int tmp,L,R;
while (cnt>m)
{
int x=q.top().x;
q.pop();
if (l[r[x]]!=x||r[l[x]]!=x)continue;
if (!a[x]||(a[x]<0&&(l[x]<1||r[x]>tot)))continue;
ans-=abs(a[x]),--cnt;
a[x]=a[x]+a[l[x]]+a[r[x]];
L=l[l[x]],R=r[r[x]];l[l[x]]=r[l[x]]=l[r[x]]=r[r[x]]=0;
l[x]=L,r[x]=R,r[L]=x,l[R]=x;
q.push((node){x});
}
printf("%lld\n",ans);
}