1. 程式人生 > 實用技巧 >最長M欄位和(1052 1115 1053)

最長M欄位和(1052 1115 1053)

這三個版本最大的區別就是資料範圍的區別:N<=5000時,用n^2的dp可以過;當n達到50000時,用nlogn的dp可以過。

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的最大欄位和。

#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方便配合連結串列進行刪除。

#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

這題與上題的唯一區別就是變成了環形操作。

將雙向連結串列變為環形雙向連結串列,初始的首末連通塊如果正負性相同則需合併。其餘與上題相同。

#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