• 洛谷 P2389 电脑班的裁员 解题报告


    题意:

    给定一段长为N的序列,选取其中的至多M段使这些子段和最大。


    当N=1000时,我们可以采用动态规划解法

    (dp[i][j][k])代表当前选至位置(i)处于第(j)段当前是否选取(1选0不选)

    则转移为
    (dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]))
    (dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j][1])+score[i])

    其中(i)的一维可以滚动掉

    Code:

    #include <cstdio>
    #include <cstring>
    int max(int x,int y){return x>y?x:y;}
    const int N=503;
    const int inf=0x3f3f3f3f;
    int dp[N][N][2],n,k,score[N];
    void init()
    {
        scanf("%d%d",&n,&k);
        for(int i=1;i<=n;i++)
            scanf("%d",score+i);
        memset(dp,-0x3f,sizeof(dp));
        dp[1][0][0]=0,dp[1][1][1]=score[1];
    }
    void work()
    {
        for(int i=2;i<=n;i++)
            for(int j=0;j<=k;j++)
            {
                dp[i][j][0]=max(dp[i-1][j][1],dp[i-1][j][0]);
                dp[i][j][1]=max(dp[i-1][j-1][0],dp[i-1][j][1])+score[i];
            }
        int ans=-inf;
        for(int i=0;i<=k;i++)
            ans=max(ans,max(dp[n][i][0],dp[n][i][1]));
        printf("%d
    ",ans);
    }
    int main()
    {
        init();
        work();
        return 0;
    }
    
    

    当N=100,000时,我们可以采用贪心解法
    注意到每次选取时,一段连续的正数或者连续的负数,要么我们不取选它,要么全部选走。

    我们先缩个点,使序列变成正负数交错的,其中,第一项和最后一项的负数我们一定不去选择,故舍去。

    设当前有(k)个正数。

    则当(M>=k)时,直接选取(k)个正数即可。

    (M<=k)时,对每个正数,我们有两种选择,放弃它,或者跨过负数选择它。

    则当选中一个负数(a)时,我们损失了(-a),当放弃一个正数(b)时,我们损失了(b)

    则不管哪种情况,我们只需要找到绝对值最小的一个数,选择它或者放弃它

    采用二叉堆维护这个最小值

    当这个最小值被操作时,其实等价于新产生了一个权值为 它和它旁边的两个元素的权值和 的新元素,这时候产生的新数列一定会少一个正数

    值得一提的是,当第一项和最后一项是负数的时候,不一定一定减少一个正数,所以我们根据贪心不去选择这些点即可

    合并元素可以采用链表进行维护

    Code:(实在是不好看QAQ)

    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <queue>
    #define ll long long
    #define P pair <ll,ll>
    using namespace std;
    const int N=1000010;
    ll a[N],b[N],n0,k,n,pre[N],suc[N],cnt,used[N];
    ll labs(ll x){return x>0?x:-x;}
    void init()
    {
        scanf("%lld%lld",&n0,&k);
        for(int i=1;i<=n0;i++)
            scanf("%lld",a+i);
        int k=0;a[0]=-1;
        while(a[k]<0) k++;
        for(int i=k;i<=n0;i++)
        {
            if(a[i]*a[i-1]>0)
                b[n]+=a[i];
            else
                b[++n]=a[i];
        }
        while(n&&b[n]<0) n--;
        for(int i=1;i<=n;i++)
        {
            pre[i]=i-1;
            suc[i]=i+1;
        }
        suc[0]=1;
        suc[n]=0;
    }
    priority_queue <P,vector <P >,greater <P > > q;
    P p;
    void work()
    {
        for(int i=1;i<=n;i++)
        {
            if(b[i]>0) cnt++;
            p.first=labs(b[i]);
            p.second=i;
            q.push(p);
        }
        while(cnt>k)
        {
            int pos=q.top().second;
            q.pop();
            if(used[pos]) continue;
            if(!suc[pos]&&b[pos]<0)
            {
                suc[pre[pos]]=suc[pos];
                pre[suc[pos]]=pre[pos];
                continue;
            }
            if(!pre[pos]&&b[pos]<0)
            {
                pre[suc[pos]]=pre[pos];
                suc[pre[pos]]=suc[pos];
                continue;
            }
            used[pre[pos]]=used[suc[pos]]=1;
            b[pos]+=b[pre[pos]]+b[suc[pos]];
            cnt--;
            if(pre[pos])
            {
                pre[pos]=pre[pre[pos]];
                suc[pre[pos]]=pos;
            }
            if(suc[pos])
            {
                suc[pos]=suc[suc[pos]];
                pre[suc[pos]]=pos;
            }
            p.first=labs(b[pos]),p.second=pos;
            q.push(p);
        }
        int now=suc[0];
        ll ans=0;
        while(now)
        {
            if(b[now]>0) ans+=b[now];
            now=suc[now];
        }
        printf("%lld
    ",ans);
    }
    int main()
    {
        init();
        work();
        return 0;
    }
    

    当N=1,000,000时,我们可以将算法近似优化到(O(N))

    具体大家可以参考出题人的题解

    我在这里解释一下这个近似,原题解没有说道找到第(k)值的问题

    主要就是利用基于快速排序思想的STL函数(nth\_element),可以在近似(O(n))的复杂度找到第(k)

    关于第k值

    代码我没写,感觉不太好写

  • 相关阅读:
    c# 扩展方法奇思妙用基础篇五:Dictionary<TKey, TValue> 扩展
    c# 扩展方法奇思妙用基础篇九:Expression 扩展
    c# 扩展方法奇思妙用高级篇一:改进 Scottgu 的 "In" 扩展
    c# 扩展方法奇思妙用高级篇三:Enumerable.Cast<T> 应用
    c# 扩展方法奇思妙用高级篇二:Aggregate 扩展及其改进
    c# 扩展方法奇思妙用基础篇四:Random 扩展
    c# 扩展方法奇思妙用基础篇七:IsBetween 通用扩展
    c# 扩展方法奇思妙用基础篇三:byte 常用扩展
    c# 扩展方法奇思妙用基础篇八:Distinct 扩展
    c# 扩展方法奇思妙用基础篇 六:WhereIf 扩展
  • 原文地址:https://www.cnblogs.com/butterflydew/p/9280322.html
Copyright © 2020-2023  润新知