引用晶之王的文字描述:
題意:
從n個人中選出m個人,選法爲控方滿意度之和s1與辯方滿意度之和s2的差的絕對值最小,若有多種方案,則選擇控方滿意度之和s1與辯方滿意度之和s2的和最大的一組,先輸出這是第幾組數據,下一行分別輸出選擇出來的方案中的控方滿意度之和s1與辯方滿意度之和s2。最後一行輸出選擇的人的編號,按從小到大的順序排列。
思路:
用一個二維dp[i][k],第一維i代表已經選擇了的人數,即1——m,第二維k代表枚舉出來的可能出現的控方滿意度之和s1與辯方滿意度之和s2的差,不過這個差可能爲負,即數組的第二維可能爲負,因此我們可以讓它向左偏移一下,具體實現看代碼,這個二維dp的值就代表控方滿意度之和s1與辯方滿意度之和s2的和;則dp[i][k]可由dp[i-1][x]得來的條件是存在某個還沒被選擇的候選人j使得k=x+v[j]。v[j]數組代表第j個人的控方與辯方滿意度之差,s[j]數組代表第j個人的控方與辯方滿意度之和。這個時候我們就可以枚舉1——n中的滿足這個條件的j來動態規劃。輸出要求輸出選擇的人的編號,因此我們用一個path數組來存儲選擇的人的編號,path[i][k]的值爲當前所選人的編號j,i代表這是選擇的第幾個人,與上文的i意義相同,k也與上文的意義相同。這時倒數第二個人的編號就是path[i-1][k-v[path[i][k]]],依次推回去就可得到選擇的所有人的編號。
原文鏈接:https://blog.csdn.net/xinjunwang/article/details/79734103
看了這麼多是不是還不理解,沒關係,我們把樣例走一遍:
4 2
1 2 no1
2 3 no2
4 1 no3
6 2 no4
我們知道: 第一個人的差值是-1(v[1]=-1),和值是3 (s[1]=3) ,第二個人的差值是-1(v[2]=-1),和值是5 (s[2]=5),以此類推
題目要讓我們找出來兩個人的最優解(這兩個人的差絕對值小於其他人,並且和儘量大)
我們一個一個來:
(1)首先找唯一一個人的最優解:第一次循環
no1: 差值-1 , 和是 3
no2: 差值 -1 ,和是5
no3: 差值3 ,和是5
no4:差值4 和是8
好了,爲了我們使用方便, (畢竟我們一會要用這個差值作爲數組的下標), 我們讓所有的差值都加上 400
no1: 差值399, 和是 3
no2: 差值 399 ,和是5
no3: 差值403,和是5
no4:差值404 和是8
我們開一個數組,dp[a][b] , 意思是, 對於已經選出來的 a 個人來說,如果差值(a個人的疊加)是b , dp[a][b]就代表這a個人的 總和值
例如: 我們在第一次循環之後,得到如下結果
dp[1][399] = 5 , 請注意, 如果b一樣,就選擇和最大的那個!
dp[1][403] = 5
dp[1][404]=8
其他的 dp[1][0] , dp[1][1] ,dp[1][2] .....dp[1][799], dp[1][800]都是初始化的-1,
意思是根本不可能合成 0 , 1, 2 ,799, 800
(2)對於第二次循環:
首先知道,dp[2][1,2,3,…800]都是-1
然後:
我們開始 從 1 找到 800 ,看看哪個數是 第一步走過的數: 僅僅有三個:399 ,403 ,404
對於 399 , 這是我們在第一步選擇了 no2 所產生的結果
因此 第二步不能選 no2 , 其他的都可以選的 ,那麼我們得到了
dp[2] [ 399 + -1 ] = 3+3=6 //選擇第一個人
dp[2][399 + 3] = 3+5=8; //選擇第三個人
dp[2][399 + 4] = 3+8 = 10 //選擇第四個人
對於 403,這是第一部選擇了第三個人的後果,因此第二步只能選 1 ,2, 4了
dp[2][403 + -1] = 5+ 3 =8 // 選第一個人
dp[2][403 + -1]=5+5 =10 // 選第二個人
dp[2] [403 + 4] = 5+8 =13 //選第三個人
對於404也是一樣
我們又發現,出現了兩個 dp[2][403-1] , 還是老道理,選擇 和比較大的 ,那麼就是10
(3) 走完了上面兩步,其實dp就結束了, 我們此使得到的 dp數組 我們想要:
dp[2][x] 中的x 最貼近 400 , 想想爲什麼,因爲 400 就是0 (我們上面擔心負數因此加了400), 所以,遍歷一次 dp[2][] ,找出來那個 離 400 最近的 x ,比如 dp[2][399] 就比 dp[2][402] 更近。
實際的值就是 -1比2 更近。
這就是這個題目的思想, 程序中的判斷是否 使用過 和 記錄路徑相信大家一看就知道了
import java.io.BufferedInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Scanner;
public class poj1015 {
static int[][] dp, path;
static int[] v,s;
public static void main(String[] args) {
Scanner sc = new Scanner(new BufferedInputStream(System.in));
dp=new int[25][2000];
path=new int[25][2000];
v=new int[300];
s=new int[300];
List<Integer>list = new ArrayList<Integer>();
int tt=0;
while(sc.hasNext()) {
tt++;
int n =sc.nextInt();
int m=sc.nextInt();
if(n==0 &&m==0) break;
for(int i=0;i<n;i++) {int t1 =sc.nextInt(); int t2= sc.nextInt(); v[i]=t1-t2;s[i]=t1+t2;}
for(int i=0;i<25;i++)for(int j=0;j<2000;j++) {dp[i][j]=-1;path[i][j]=-1;}
dp[0][400]=0;
for(int i=0;i<m;i++) {
for(int k=0;k<=800;k++) {
if(dp[i][k] < 0) continue;
for(int j=0;j<n;j++) {
if(dp[i+1][k+v[j]] < dp[i][ k]+s[j]) {
// 這個j 是否用過,比如 第一次循環對應的這個 k用過1號,
//這裏就能不能再是1號 , 可以是其他人,
int a = k;
int b =i;
while(b>0 && path[b][a]!=j) {
a-=v[path[b][a]];
b--;
}
if(b==0) {
dp[i+1][k+v[j]] = dp[i][k]+s[j];
path[i+1][k+v[j]]=j;
}
}
}
}
}
int a=400;
int b= 0;
//找最貼近400的值
while(dp[m][a-b] <0 && dp[m][a+b]<0) {
b++;
}
int res=0;
//和大的那個
if(dp[m][a-b]>dp[m][a+b])
res=a-b;
else
res=a+b;
System.out.println("Jury #"+tt);
System.out.println("Best jury has value "+(res-400+dp[m][res])/2+" for prosecution and value "+(400-res+dp[m][res])/2+" for defence: ");
int temp;
list.clear();
for(int i=0;i<m;i++) {
temp=path[m-i][res];
list.add(temp);
res-=v[temp];
}
Collections.sort(list);
for(int i:list) {
System.out.print(" "+(i+1));
}
System.out.println();
}
}
}
反思: 這題寫了好久,也參考了別人。這種題爲啥那麼難想,是不是自己做的少?理解題意是一部分, 思維和經驗是一部分,太弱了 ,欲哭無淚