zoukankan      html  css  js  c++  java
  • POJ 1015 Jury Compromise (完全背包)

    题目大意:

    在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的 |D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

    输出:

    选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。

    ——https://blog.csdn.net/lyy289065406/article/details/6671105 (然而这个“标程”是有BUG的)

    前言:

    这个题,网上流传的绝大多数都是错的解法,之所以能流传,因为poj上的数据输出也是错误的。导致真正正确的程序因为WA不止而被埋没。

    随后在discuss里终于出现了CZDleaf等神犇,找出了bug并且出了hack数据并且给出了真正的标程。

    https://blog.csdn.net/glqac/article/details/22687243 http://poj.org/showmessage?message_id=161937

    分析:

    考虑到每个候选人只有选或者不选两种情况,而且之前不是最优解的人可能之后也要被选上。所以做法是0/1背包。

    设f[j][k]表示选了j个人,差值为k的D+P的最大值。

    状态转移方程:f[j][k]=max(f[j][k],f[j-1][k-(p[i]-d[i])]+p[i]+d[i])

    最初f[0][0]=0,其余为-1或者-0x3f3f3f3f 由于可能会使下标变成负数,所以增加一个修正值fix=20m (因为差值最多为20m,令fix映射0,使映射区间向右平移fix个单位长度)

    错误方法:

    外层循环j:1~m;

    中层循环k: 0~2*fix;

    内层循环i:1~n

    每次尝试更新的时候,检查一下之前路径中是否已经有了i,没用过就继续更新,否则conitnue. 这样可以避免路径改变的问题,每一次更新,令pre[j][k]=i即可。最后的时候,从f[m][fix]向两边找到第一个值不为-1的k即为差值。 (此处省略若干字)

    错误的原因: bug之处在于:如果在选择j之前,选择1~j-1的f[j-1]i最优方案不止一个,即使得f[j-1]最大化的路径不止有一条,那么可能的情况是,编号较小的路径组合会覆盖、掩盖之后的路径组合(它只能记录一条),而正确的答案却是从后面的路径转移过来的。换句话说,我们在更新的时候,有可能抛弃了正确答案的转移路径。从而选择j时的正解可能会因为之前选过而被pass掉。

    无法保证最优子结构条件。

    正解:

    外层循环i:1~n

    中层倒序循环j:m~1;

    内层循环k:0~2*fix;

    这样,因为避免了重复选择判断的一项,而且由于i的顺序循环,前面即使出现重复的路径,也不会对之后的答案造成影响,而且避免了最后的sort麻烦。

    至于路径转移:(被卡了)博客上给的是用vector直接复制之前的路径进行转移。这样,即使f[1~m-1][k]的路径变化了,也不会影响到决策的输出。(f[m][k]的路径是孤立的存在,不用递推往前找,从而避免了麻烦。)

    #include <iostream>
    #include <cstdio>
    #include <cstring>
    #include <algorithm>
    #include <vector>
    using namespace std;
    int f[25][805]; // 前i个人,选了j个,辩方总分与控方总分的差为k=-400~400(平移到0~800)
    int d[205][25][805]; // 记路径还是要三维,不然后面的更新可能会覆盖掉之前的记录
    int n, m, a[205], b[205], suma, sumb, T;
    vector<int> c;
    
    void get_path(int i, int j, int k) {
    	if (j == 0) return;
    	int last = d[i][j][k];
    	get_path(last - 1, j - 1, k - (a[last] - b[last]));
    	c.push_back(last);
    	suma += a[last], sumb += b[last];
    }
    
    int main() {
    	while (cin >> n >> m && n) {
    		for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]);
    		memset(f, 0xcf, sizeof(f)); // -INF
    		f[0][400] = 0; // f[0][0]第三维平移400
    		for (int i = 1; i <= n; i++) {
    			for (int j = 0; j <= m; j++) // 不选i
    				for (int k = 0; k <= 800; k++) d[i][j][k] = d[i - 1][j][k];
    			for (int j = m; j; j--) // 选i
    				for (int k = 0; k <= 800; k++) {
    					if (k - (a[i] - b[i]) < 0 || k - (a[i] - b[i]) > 800) continue; // 超出范围
    					if (f[j][k] < f[j - 1][k - (a[i] - b[i])] + a[i] + b[i]) {
    						f[j][k] = f[j - 1][k - (a[i] - b[i])] + a[i] + b[i];
    						d[i][j][k] = i;
    					}
    				}
    		}
    		int ans = 0;
    		for (int k = 0; k <= 400; k++) {
    			if (f[m][400 + k] >= 0 && f[m][400 + k] >= f[m][400 - k]) {
    				ans = k + 400;
    				break;
    			}
    			if (f[m][400 - k] >= 0) {
    				ans = 400 - k;
    				break;
    			}
    		}
    		c.clear();
    		suma = sumb = 0;
    		get_path(n, m, ans);
    		printf("Jury #%d
    ", ++T);
    		printf("Best jury has value %d for prosecution and value %d for defence:
    ", suma, sumb);
    		for (int i = 0; i < c.size(); i++) printf(" %d", c[i]);
    		printf("
    
    ");
    	}
    }
    
  • 相关阅读:
    PHP
    单引号和双引号的区别和效率问题
    SFDC 401认证准备及考试
    SFDC 401 最新考试真题
    3 report formats of SFDC
    HTML输入框点击内容消失
    RDD的转换操作(续)
    RDD的转换操作
    SparkContext和RDD的说明
    集群模式相关概念
  • 原文地址:https://www.cnblogs.com/RioTian/p/13492664.html
Copyright © 2011-2022 走看看