[DP] POJ 1015

题意

必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的 |D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

思路

dp(j, k)表示,取j 个候选人,使其辩控差为k 的所有方案中,辩控和最大的那个方案的辩控和。
综上:dp[j][k]=dp[j-1][k-V[i]]+S[i]
开始傻了,写成了dp[i][x] = dp[k][x-1] + w[i]
存储了每次最小的差,又开了一个数组存最大,但单纯的差只需要贪心就能求最小了,所以dp应该存的是每个差所对应的最大和

正向计算,输出的时候就用正向的输出了,注意要按照编号排序
这里对绝对值的处理也很巧妙,通过fix的值来左右两边计算

dp的方程写不对真的很困扰

代码

#include <algorithm>
#include <cmath>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 206;
const int INF = 0x7f7f7f7f;

int n, m;
int dp[ 21 ][ 805 ]; // dp[j][k]:取j个候选人,使其辩控差为k的所有方案中,辩控和最大的方案
int path[ 21 ][ 805 ]; //记录所选定的候选人的编号

//前j个,差,下一个,差的数组
//确认dp[j][k]方案是否曾选择过候选人i
bool select ( int j, int k, int i, int v[] ) {
    while ( j > 0 && path[ j ][ k ] != i )
        k -= v[ path[ j-- ][ k ] ];

    // j > 0代表i被选过,返回false
    return j ? false : true;
}

int main () {
    int kse = 1;
    while ( cin >> n >> m && n ) {
        int *p = new int[ n + 1 ];
        int *d = new int[ n + 1 ];
        int *s = new int[ n + 1 ]; //和
        int *v = new int[ n + 1 ]; //差
        memset ( dp, -1, sizeof ( dp ) );
        memset ( path, 0, sizeof ( path ) );

        // INPt
        for ( int i = 1; i <= n; ++i ) {
            cin >> p[ i ] >> d[ i ];
            s[ i ] = p[ i ] + d[ i ];
            v[ i ] = p[ i ] - d[ i ]; //取绝对值,怎么减都行啦
        }
        int fix = m * 20; //总修正值,修正极限为从[-400,400]映射到[0,800]

        // DP
        dp[ 0 ][ fix ] = 0;
        for ( int j = 1; j <= m; ++j )           // j个候选人
            for ( int k = 0; k <= 2 * fix; ++k ) //差为k
                if ( dp[ j - 1 ][ k ] >= 0 ) //初始化成了-1,所以这里代表上一个值存在
                    for ( int i = 1; i <= n; ++i ) //找下一个
                        if ( dp[ j ][ k + v[ i ] ] < dp[ j - 1 ][ k ] + s[ i ] )
                            if ( select ( j - 1, k, i, v ) ) {
                                //正向推导,方便输出,不方便查找所以需要每次都会回去判断所选的是否被选过
                                dp[ j ][ k + v[ i ] ] = dp[ j - 1 ][ k ] + s[ i ];
                                path[ j ][ k + v[ i ] ] = i;
                            }
        // 取两边的差(绝对值)最小的那个(最接近fix)
        int k;
        for ( k = 0; k <= fix; ++k )
            if ( dp[ m ][ fix - k ] >= 0 || dp[ m ][ fix + k ] >= 0 )
                break;

        int div = dp[ m ][ fix - k ] > dp[ m ][ fix + k ] ? ( fix - k ) : ( fix + k );

        cout << "Jury #" << kse++ << endl;
        cout << "Best jury has value ";
        cout << ( dp[ m ][ div ] + div - fix ) / 2 << " for prosecution and value ";
        cout << ( dp[ m ][ div ] - div + fix ) / 2 << " for defence:" << endl;

        int *id = new int[ m ];
        for ( int i = 0, j = m, k = div; i < m; ++i ) {
            id[ i ] = path[ j-- ][ k ];
            k -= v[ id[ i ] ];
        }
        sort ( id, id + m ); //可能忘记排序了
        for ( int i = 0; i < m; ++i )
            cout << ' ' << id[ i ];
        cout << endl << endl;
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值