【JZOJ4007】【GDKOI2015】星球杯(DP)

本文介绍了一种通过动态规划解决特定竞赛晋级问题的方法。在有限的参赛者数量下,通过枚举分界线并利用DP状态转移方程,确定两轮预选赛后来自指定国家的选手最高得分总和。

Problem

  有0和1两个王国共派出N个人参加两轮预选赛,每名选手必须且只能参加一轮预选赛,最后每轮预选赛中得分前K高的晋级决赛。已知第i个人参加第一轮预赛的得分Xi、参加第二轮预赛的得分Yi和国籍Zi,求所有晋级决赛的1王国的选手最高的得分总和。

Hint

  Xi 之间的值互不相同, Yi 之间的值互不相同。
  对于 30%数据, N≤20;
  对于 100%数据, N≤200, Xi, Yi 的总和小于 2^31。

Solution

  这题乍一眼看上去很不可做,所以比赛时我只打了暴力(虽然爆0了,且原因至今未知)。
  然而正解却是一个神奇的DP。
  我们观察到它的N非常小,于是百思不得其解便可以为所欲为——我们可以枚举一个分界线,表示第一轮预选赛中排名第K的人。我们设他在第一轮预选赛中的得分为x。
  那么我们可以发现,对于所有0国的人i,若Xi<x,则显然把他丢进第一轮里。我们把除过这些人以外的人的信息丢进一个a数组中,按照Yi从大到小排序(原因待会再告诉你)。
  然后我们可以设f[i][j]表示在前i个选手中,有j个选手参加了第一轮预选赛并且晋级时1王国的选手最高的得分总和。
  对于每个0国的人,若j<k,则我们可以让他参加第一轮,即得转移方程:

f[i+1][j+1]=max(f[i+1][j+1],f[i][j]);

  而无论如何我们都可以让他参加第二轮,即:

f[i+1][j]=max(f[i+1][j],f[i][j])

  对于每个1国的人,若Xi≥x且j<k,我们也可以让他参加第一轮,即:

f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+Xi);

  而若i-j<k,则说明参加第二轮且晋级(因为我们上面说了按Yi从大到小排序,所以这i-j个参加第二轮的人肯定晋级了)的人还不足k,还可以再添点,于是让这个人去参加第二轮可以晋级,即:

f[i+1][j]=max(f[i+1][j],f[i][j]+Yi);

  若i-j≥k,则说明已有i-j个人去参加了第一轮。因为那参加第一轮的j个人肯定都晋级了:对于j个人中的0国人l,由于我们之前已经把Xl<x(不能晋级第一轮)的人全都筛掉了,所以剩下的0国人参加了第一轮就肯定能晋级;而对于1国人l,我们只有当他能晋级第一轮的时候才会让他去参加第一轮,否则让他去参加第二轮就会使答案有增无减。
  综上,i+1这个人只能去参加第二轮,而他去参加第二轮不可以晋级,即:

f[i+1][j]=max(f[i+1][j],f[i][j])

  于是我们做N轮DP即可解决问题。
  时间复杂度:O(n3)O(n3)

Code

#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
#define N 201
#define fo(i,a,b) for(i=a;i<=b;i++)
int i,j,n,k,X[N],Y[N],Z[N],boun,x,m,f[N][N],ans;
struct comp
{
    int num,x,y,z;
}a[N];
inline bool compare(comp a,comp b)
{
    return a.y>b.y;
}
int main()
{
    scanf("%d%d",&n,&k);
    fo(i,1,n)scanf("%d%d%d",&X[i],&Y[i],&Z[i]);
    fo(boun,1,n)
    {
        x=X[boun];
        m=0;
        fo(i,1,n)
            if(Z[i]||X[i]>=x)
            {
                a[++m].num=i;
                a[m].x=X[i];
                a[m].y=Y[i];
                a[m].z=Z[i];
            }
        sort(a+1,a+m+1,compare);
        memset(f,254,sizeof f);
        f[0][0]=0;
        fo(i,0,m-1)
            fo(j,0,min(i,k))
                if(f[i][j]>=0)
                {
                    if(!a[i+1].z)
                    {
                        if(j<k)f[i+1][j+1]=max(f[i+1][j+1],f[i][j]);
                        f[i+1][j]=max(f[i+1][j],f[i][j]);
                        continue;
                    }
                    if(a[i+1].x>=x&&j<k)f[i+1][j+1]=max(f[i+1][j+1],f[i][j]+a[i+1].x);
                    f[i+1][j]=max(f[i+1][j],f[i][j]+(i-j<k?a[i+1].y:0));
                }
        fo(i,0,min(m,k))ans=max(ans,f[m][i]);
    }
    printf("%d",ans);
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值