【問題描述】每年的北京資訊學冬令營上,同學們都會在課餘開展豐富多彩的電子競技活動。經過一年又一年的實戰,同學們對彼此的實力已經非常熟悉了,以至於產生出了一套實力對戰評分系統。這套系統把每個人的實力量化為一個整數。並且對於兩個實力分別為RA, RB的人A 和B,這套系統把他們對戰的通常的結果也量化為一個關於他們實力值的實數分數:
其中EA , EB 分別為A 和B 在對戰中通常得到的對戰分數。例如,若RA = 2432, RB = 2611 ,則在A 和B 的對戰中A 取得的對戰分數應該為
不過,今年冬令營的情況與以往略有不同,同學們分成了人數相等的兩隊展開了團體比賽。每隊的老大給自己的隊員排定一個順序,然後兩隊按順序第一個人互相對戰,第二個人互相對戰,以此類推。對於團體比賽中對戰的每一對選手,我們都可以根據他們的實力值計算出他們分別獲得的對戰分數。而兩個團隊在這樣一種比賽順序下的團隊對戰分數就是團隊每一位選手在這個順序下獲得的對戰分數和。為了公平競賽,同學們約定了一個整數L,並規定一個團隊中實力為x的選手必須排在所有實力嚴格小於x – L 的選手之前。例如若L = 100,實力為2437 的一名選手必須排在本團隊實力為2336 的選手之前,但是卻不一定排在本團隊實力為2337 的選手之前。你作為一支隊伍的老大,運用了種種不正當手段搞來了另外一直隊伍排好的隊員順序。並且你也瞭解自己隊伍和對方隊伍每一個人的實力值。你現在的任務就是想方設法安排本隊人員的順序,使得你的團隊在對戰中獲得的分數最大。【輸入檔案】輸入檔案的第一行包含一個整數 N,代表每隊的人數。接下來的N 行,每行一個整數,表示你的每個隊員的能力值。接下來的N 行,每行一個整數,依次表示對方團隊的每個隊員的能力值。最後一行包含一個整數,代表L。【輸出檔案】輸出檔案僅包含一個實數,表示你最大可能獲得的分數。答案四捨五入保留到小數點後六位【範例輸入】422392412239922672534242923402389100【範例輸出】1.483574【範例說明】根據題目中的規定,可能的安排順序只可能有四種,它們能獲得的分數如下 排列 分數2399 2412 2239 2267 1.480409862399 2412 2267 2239 1.483573612412 2399 2239 2267 1.478153482412 2399 2267 2239 1.48131723注意,如果沒有L 的限制,排列2239 2267 2399 2412 可以得到更多的分數。但是,由於2239 和2267 不允許2399 和2412 之前出現,這是不可能的。【資料規模】對於100% 的資料,有1 ≤ N ≤ 20 成立。對於100% 的資料,每個人的能力值都在1500 到3000 之間。對於100% 的資料,有0 ≤ L ≤ 1500 成立。對於100% 的資料,你的對手隊伍排列好的順序中的第i 名隊員的能力值與L 的和大於等於對手隊伍中排在他之後的任意一名隊員的能力值。
狀態壓縮型動態規劃。
狀態:已經參加過比賽的選手集合。
轉移時,枚舉當前參賽的選手,若他還沒有參加過比賽並且滿足題目中給定的條件,那麼就更新該選手加入的集合所對應的總得分。
一個最佳化:將選手的實力值排序(排序方向無所謂),然後每次只需要在已參加比賽的所有選手中選出一個實力值最小的選手進行判定即可(因為若該選手都能滿足條件,那麼比他實力值大的其他選手都一定能滿足條件)。
Accode:
#include <cstdio>#include <cstdlib>#include <algorithm>#include <cmath>const int maxN = 30;const int maxSTATUS = 1 << 20;double f[maxSTATUS], cost[maxN][maxN];//由於隨著狀態轉移的推進,集合中含有人數在不斷增加,//所以之前的狀態並不影響後面的狀態,從而可以就地滾動。int a[maxN], b[maxN], n, L, pst, ths = 1;int cnt[2], status[2][maxSTATUS];//cnt數組用於記錄當前需要枚舉的所有狀態的總數,//分別儲存在status裡面,減少枚舉量,防止無效狀態的枚舉。inline bool check(int S, int i){ for (int j = n - 1, tmp = 1 << j; tmp; tmp >>= 1, --j) if (tmp & S) return a[i] - L <= a[j]; return 1;}int main(){ freopen("compete.in", "r", stdin); freopen("compete.out", "w", stdout); scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", a + i); for (int i = 0; i < n; ++i) scanf("%d", b + i); scanf("%d", &L); for (int i = 0; i < n; ++i) for (int j = i + 1; j < n; ++j) if (a[i] < a[j]) std::swap(a[i], a[j]); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) cost[i][j] = (1.L / (1. + pow(10.L, (b[j] - a[i]) / 400.L)));//對戰的結果分數可以預先處理出來以免多次計算。//注意要先排序再預先處理這個分數。 status[ths][cnt[ths]++] = 0; for (int i = 0; i < n; ++i) { std::swap(pst, ths); cnt[ths] = 0; for (int k = 0; k < cnt[pst]; ++k) { int Last = status[pst][k]; double val = f[Last]; for (int j = 0; j < n; ++j) if (!(Last & (1 << j))) if (check(Last, j)) { int Now = Last | (1 << j); if (f[Now] < 1e-12) status[ths][cnt[ths]++] = Now; if (val + cost[j][i] > f[Now]) f[Now] = val + cost[j][i]; } else break; } } printf("%.6lf", f[(1 << n) - 1]); return 0;}
再貼一個樸素的搜尋程式:
#include <cstdio>#include <cstdlib>#include <algorithm>#include <cmath>const char fi[] = "compete.in";const char fo[] = "compete.out";const int maxN = 30;int a[maxN], b[maxN], c[maxN];bool tag[maxN];int n, L;double ans = 0, cost[maxN][maxN];void init_file(){ freopen(fi, "r", stdin); freopen(fo, "w", stdout); return;}inline bool cmp(const int &a, const int &b){return a > b;}void readdata(){ scanf("%d", &n); for (int i = 0; i < n; ++i) scanf("%d", a + i); for (int i = 0; i < n; ++i) scanf("%d", b + i); scanf("%d", &L); std::sort(a, a + n, cmp); for (int i = 0; i < n; ++i) for (int j = 0; j < n; ++j) cost[i][j] = (1.L / (1. + pow(10.L, (b[j] - a[i]) / 400.L))); return;}void print(){ double tmp = 0; for (int i = 0; i < n; ++i) tmp += cost[c[i]][i]; if (tmp > ans) ans = tmp; return;}void Dfs(int i){ if (i >= n) {print(); return;} for (int j = 0; j < n; ++j) { if (tag[j]) continue; if (a[j] - L > a[c[i - 1]]) break; c[i] = j; tag[j] = 1; Dfs(i + 1); tag[j] = 0; } return;}void work(){ for (int i = 0; i < n; ++i) if (a[0] - a[i] <= L) { c[0] = i; tag[i] = 1; Dfs(1); tag[i] = 0; } printf("%.6lf\n", ans); return;}int main(){ init_file(); readdata(); work(); return 0;}