最长M字段和(1052 1115 1053)

这三个版本最大的区别就是数据范围的区别:N<=5000时,用n^2的dp可以过;当n达到50000时,用nlogn的dp可以过。最长M字段和(1052  1115 1053)

 

 最长M字段和(1052  1115 1053)

 

51nod 1052:

设dp[i][j]表示前j个数分成i段的最大字段和,转移方程由:dp[i][j]=max(max(dp[i-1][k](k=1...j-1)),dp[i][j])+a[j]);

由于空间会超限,可采用滚动数组;用f[j]数组记录第i-1的前j的最大字段和。

最长M字段和(1052  1115 1053)
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=6e3+100;
ll dp[N][2];
ll f1[N],f2[N];
int a[N];
int main()
{
    //freopen("1.txt","r",stdin);
    int n,m;
    cin>>n>>m;
    int res=0;
    ll ans=0,sum=0;
    for(int i=1;i<=n;i++)
    {
        scanf("%d",&a[i]);
        if(a[i]>0)
        {
            res++;
            sum+=a[i];
        }
    }
    if(m>=res)
    {
        printf("%lld\n",sum);
        return 0;
    }
    for(int i=1;i<=m;i++)
    {
       // memset(f1,0,sizeof f1);
        for(int j=1;j<=n;j++)
        {
            dp[i][j&1]=max(f2[j-1],dp[i][(j-1)&1])+a[j];
            f1[j]=max(f1[j-1],dp[i][j&1]);
            if(i==m)
                ans=max(ans,dp[i][j&1]);
        }
        memcpy(f2,f1,sizeof f1);
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

51nod 1053:

我们考虑先把原数组的相同正负性的数合并,原数组变成正负相间的数,要求只包含m个字段的最大数值和,等价于将数组中的正数连通块变为m个的最大收益;

有两个操作可以让整数连通块减少:1.将两个正数联通块与中间的负数连通块合并,答案减去负数的绝对值。

2.减去一个正数连通块,贡献减去正数连通块的绝对值。

因此,无论哪个操作都是减去联通块的绝对值,我们考虑将每个连通块以绝对值为贡献排序,尽量用绝对值小的减去正数连通块的数量,这样一定是最优的。

为了方便合并,我们采用双向链表进行操作。注意:当负数连通块在链表两端时,操作无效。

为了方便操作,采用set方便配合链表进行删除。

最长M字段和(1052  1115 1053)
#define ll long long
#define x first
#define y second
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+100;
ll sum[N], a[N];
int l[N],r[N];
void modify(int u)//删去链表某个点
{
    int l1=l[u],rr=r[u];
    if(l1)
        r[l1]=rr;
    if(rr)
        l[rr]=l1;
}
int main()
{
    //freopen("1.txt","r",stdin);
    int n,m;
    int cnt=0;
    cin>>n>>m;
    ll ans=0;
    int res=0;
    for(int i=1;i<=n;i++)
    {
        int x;
        scanf("%d",&x);
        if(x)
         a[++cnt]=x;
         if(x>0)ans+=x,res++;
    }
    if(m>=res)
    {
        cout<<ans<<endl;
        return 0;
    }
    res=0;
    ll u=0;
    n=cnt;
    cnt=0;
    for(int i=1,j;i<=n;i++)
    {
        j=i;
        u=a[i];
        while(j+1<=n&&a[j]*a[j+1]>0)
        {
            u+=a[j+1];
            j++;
        }
        if(!cnt&&u<0)continue;
        if(u<0&&i==n)continue;
        if(u>0)res++;
        sum[++cnt]=u;
        i=j;
    }
    for(int i=2;i<=cnt;i++)
    {
        l[i]=i-1;
        r[i-1]=i;
    }
    set<pair<ll,int> >s;
    for(int i=1;i<=cnt;i++)
    {
        s.insert({abs(sum[i]),i});
        //cout<<sum[i]<<endl;
    }
    //cout<<ans<<endl;
    for(int i=res;i>m;)
    {
        int id=s.begin()->second;
        s.erase(s.begin());
        if(sum[id]<0&&(!l[id]||!r[id]))continue;//当负数连通块在两端时无效
        //cout<<sum[id]<<endl;
        ans-=abs(sum[id]);//减去代价
        sum[id]+=sum[l[id]]+sum[r[id]];
        if(l[id])
        {
            s.erase({abs(sum[l[id]]),l[id]});
            modify(l[id]);
        }
        if(r[id])
        {
            s.erase({abs(sum[r[id]]),r[id]});
            modify(r[id]);
        }
        if(sum[id])
        s.insert({abs(sum[id]),id});
        i--;
    }
    cout<<ans<<endl;
    return 0;
}
View Code

 

51nod 1115

这题与上题的唯一区别就是变成了环形操作。

将双向链表变为环形双向链表,初始的首末连通块如果正负性相同则需合并。其余与上题相同。

最长M字段和(1052  1115 1053)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 100010;
int n,m,now;
ll sum[N];
ll a[N];
int l[N],r[N];
int main()
{
    //freopen("2.txt","r",stdin);
    cin>>n>>m;
    int p=0;
    int res=0;
    ll ans=0;
    for(int i=1;i<=n;i++)
    {
        ll x;
        scanf("%lld",&x);
        if(x!=0)
            a[++p]=x;
            if(x>0)
            {
                res++;
                ans+=x;
            }
    }
    if(m>=res)
    {
        cout<<ans<<endl;
        return 0;
    }
    n=p;
    int cnt=0;
    p=0;
    res=0;
    ll u=0;
    for(int i=1,j;i<=n;i++)
    {
        j=i;
        u=a[i];
        while(j+1<=n&&a[j+1]*a[j]>0)
        {
            u+=a[j+1];
            j++;
        }
        i=j;
        sum[++cnt]=u;
    }
    set<pair<ll,int> > s;
    if(sum[1]*sum[cnt]>0)//首末连通块正负性相同则合并
     {
         sum[1]+=sum[cnt];
         cnt--;
     }
    for(int i=1;i<=cnt;i++)
       {
           //cout<<sum[i]<<endl;
            if(sum[i]>0)res++;
           s.insert({abs(sum[i]),i});
       }
       for(int i=2;i<=cnt;i++)
       {
           l[i]=i-1;
           r[i-1]=i;
       }
       p=cnt;
       r[cnt]=1;
       l[1]=cnt;
    for(int i=res;i>m;)
    {
        int id=s.begin()->second;
        s.erase(s.begin());
        ans-=abs(sum[id]);
        sum[id]+=sum[l[id]]+sum[r[id]];
        if(l[id])
        {
            s.erase({abs(sum[l[id]]),l[id]});
            int cur=l[id];
            int ll=l[cur],rr=r[cur];
            if(ll)
            {
                r[ll]=rr;
            }
            if(rr)
            {
                l[rr]=ll;
            }
        }
       if(r[id])
        {
            s.erase({abs(sum[r[id]]),r[id]});
            int cur=r[id];
            int ll=l[cur],rr=r[cur];
            if(ll)
            {
                r[ll]=rr;
            }
            if(rr)
            {
                l[rr]=ll;
            }
        }
        if(sum[id])
        s.insert({abs(sum[id]),id});
        i--;
    }
    printf("%lld\n",ans);
    return 0;
}
View Code

 

上一篇:PTA 乙级 1052 卖个萌 (20分) C++


下一篇:JS:标签类名的操作方法(代码)