[2018.7.4集训]divide-动态规划

Posted zltttt

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[2018.7.4集训]divide-动态规划相关的知识,希望对你有一定的参考价值。

题目大意

一个舰队有$n$艘飞船,每艘飞船有一个武力值$w_i$,现需要把飞船分成两队。
对于任意两艘不在同一队的飞船,且它们的武力值之和不小于$m$,那么这两艘飞船配合默契。
求出最多能有多少对飞船配合默契,并求出有多少种分队方案可以达到此效果。

$n leq 2000,w_i leq 10^6,m leq 2*10^6$

题解

看完题第一步显然是给 $w_i$ 排个序了~显然将来会有用的~

观察题目可知,这个数据范围仿佛在呼唤一个 $O(n^2)$ 做法,比如dp。
但是由于飞船之间的是否默契情况,貌似并没有什么好的dp方法。

观察排好序的 $w_i$ ,可以发现,与每个飞船配合默契的飞船都是一段后缀。
那么考虑据此搞出一个方便dp的东西来。

于是考虑建出一个遍历节点的顺序。
考虑区间 $[1,n]$ 的情况:
取出 $w_1$ 和 $w_n$ ,计算它们的和 $sum$ 与 $m$ 的大小关系。
如果 $s < m$,那么显然 $w_1$ 和目前区间内剩下的位置都无法默契了,因此咱们将 $w_1$ 加入序列,递归做 $[2,n]$。
如果 $s geq m$,那么 $w_n$ 和目前区间内剩下的位置都配合默契,因此咱们将 $w_n$ 加入序列,递归做 $[1,n-1]$。

然后得到了一个序列!
考虑它的含义,对于每个位置,要么和后面的位置均不默契,要么均默契。

于是得到了一个可以用来dp的条件很强的顺序。
那么翻转整个新数组,按顺序dp。
设$f[i][j]$代表,考虑完前$i$艘飞船,其中有$j$艘属于$A$队的最大默契数。
设当前位置与前面的位置均默契时,$c[i]=1$,否则$c[i]=0$,那么有转移方程:

$$f[i][j]=max(f[i-1][j-1]+c[i]*(i-j), f[i-1][j]+c[i]*j)$$

方案可以同理统计。
于是做完了!

代码:

#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;

inline int read()
{
    int x=0;char ch=getchar();
    while(ch<'0' || '9'<ch)ch=getchar();
    while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
    return x;
}

typedef long long ll;
const int N=2009;
const int md=1e9+7;

int n,m,ans;
int w[N],c[N];
ll f[N][N],g[N][N];

inline bool chkmin(ll &a,ll b){if(a>b)return a=b,1;return 0;}
inline bool chkmax(ll &a,ll b){if(a<b)return a=b,1;return 0;}


inline ll qpow(ll a,ll b)
{
    ll ret=1;
    while(b)
    {
        if(b&1)ret=ret*a%md;
        a=a*a%md;b>>=1;
    }
    return ret;
}

inline void update(ll &a,ll &b,ll c,ll d)
{
    if(chkmax(a,c))b=d;
    else if(a==c)(b+=d)%=md;
}

int main()
{
    n=read();m=read();
    for(int i=1;i<=n;i++)
        w[i]=read();
    sort(w+1,w+n+1);

    int l=1,r=n;
    for(int i=n;i>=1;i--)
    {
        if(w[l]+w[r]<m)
            c[i]=0,l++;
        else
            c[i]=1,r--;
    }

    memset(f,128,sizeof(f));
    f[0][0]=0;g[0][0]=1;
    for(int i=1;i<=n;i++)
        for(int j=0;j<=i;j++)
        {
            f[i][j]=f[i-1][j]+c[i]*j;g[i][j]=g[i-1][j];
            if(j)
                update(f[i][j],g[i][j],f[i-1][j-1]+c[i]*(i-j),g[i-1][j-1]);
        }

    ll ans=-1e18,cnt=0;
    for(int i=0;i<=n;i++)
        update(ans,cnt,f[n][i],g[n][i]);
    printf("%lld %lld
",ans,cnt);
    return 0;
}

以上是关于[2018.7.4集训]divide-动态规划的主要内容,如果未能解决你的问题,请参考以下文章

五月集训(第28天) —— 动态规划

暑假集训 || 动态规划

UOJ#39. 清华集训2014简单回路 动态规划 插头DP

2017清北学堂集训笔记——动态规划Part2

UOJ#346. 清华集训2017某位歌姬的故事 动态规划

动态规划的泛式解题思路