【noip模擬賽 王強的疑惑】 題解
阿新 • • 發佈:2018-11-05
考試題。
是個DP。
50分可以通過子集列舉+線段覆蓋(貪心)完成。
考試沒時間寫了一個子集列舉30分。
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 20; int read() { char ch = getchar(); int u = 0, f = 1; while (!isdigit(ch)) {if (ch == '-')f = -1; ch = getchar();} while (isdigit(ch)) {u = u * 10 + ch - 48; ch = getchar();}return u * f; } //int dp[maxn][maxn]... struct ioi{ int l, r, num; double p; }b[maxn]; bool cmp(ioi a, ioi b) { if(a.l == b.l) return a.r < b.r; return a.l < b.l; } int a[maxn], n, q, seq[maxn], last; double now = 0, ans; void print_subset(int s, int n) { memset(a, 0, sizeof(a)); for(int i = 1; i <= n; i++) if(s & (1 << (i-1))) a[i] = 1; } int main() { freopen("math.in","r",stdin); freopen("math.out","w",stdout); n = read(); q = read(); for(int i = 1; i <= n; i++) { b[i].l = read(); b[i].r = read(); scanf("%lf",&b[i].p); //cin>>b[i].l>>b[i].r>>b[i].p; b[i].r += q; b[i].num = i; } sort(b+1, b+1+n, cmp); for(int i = 1; i < (1 << n); i++) { print_subset(i, n); now = 0; last = 0; for(int j = 1; j <= n; j++) { if(a[j] == 1 && b[last].r <= b[j].l) { now += b[j].p; last = j; } } if(now > ans) { for(int j = 1; j <= n; j++) seq[j] = a[j]; ans = now; } } printf("%0.3lf\n",ans); for(int i = 1; i <= n; i++) { if(seq[i] == 1) printf("%d ", b[i].num); } return 0; }
注意一點:memset初始化是O(n)的。儘管上次morslin跟我說做試驗memset確實比for一遍快...所以我信了他每次列舉memset了一個1e6的陣列..TLE
考慮正解的DP。
說過狀態設的好,轉移就方便。
一開始設的DP[i][j]表示前i個選了j個的最優..
轉移個錘子。
正解:不妨設DP[i]表示在第i時,可以獲得的最大期望(此期望非彼期望)。
於是只有在第j個結束時間為i才轉移,其他的是DP[i] = DP[i-1]
注意a[j].l這個邊界,要>q才能轉移,會被卡。不寫只有50分。
$ if(a[j].r == i && a[j].l >= q) $
$ DP[i] = max(DP[i], DP[a[j].l-q] + a[j].p) $
這時候得到一個O(MN)的DP。M為最大時間
考慮再優化,如果我們先給每個資訊排一遍序,這樣我們隨著時間i的增大,每個a[j].l和a[j].r也在增大。記錄上次列舉到的j是多少,記為pos,下次直接從j = pos開始。
我們再去列舉每個j的時候就不需要從1開始了,因為每次滿足的先行條件是a[j].r == i。
注意一點,不論這次a[j]有沒有更新DP[i]的值,我們的pos都要改變,否則還是TLE。
code:
#include <stack> #include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int maxn = 1e6 + 10; inline int read() { char ch = getchar(); int u = 0, f = 1; while (!isdigit(ch)) {if (ch == '-')f = -1; ch = getchar();} while (isdigit(ch)) {u = u * 10 + ch - 48; ch = getchar();}return u * f; } double dp[maxn]; struct ioi{ int l, r, num; double p; }a[maxn]; int n, m, q, pos = 1, pre[maxn], ans[maxn], Ans[maxn], cnt; bool cmp(ioi a, ioi b) { if(a.r != b.r) return a.r < b.r; else return a.l < b.l; } int main() { //freopen("math.in","r",stdin); //freopen("math.out","w",stdout); scanf("%d%d",&n,&q); for(int i = 1; i <= n; i++) { scanf("%d%d%lf",&a[i].l,&a[i].r,&a[i].p); a[i].num = i; m = max(m, a[i].r); } sort(a+1, a+1+n, cmp); for(int i = 1; i <= m; i++) { dp[i] = dp[i-1]; pre[i] = i - 1; for(int j = pos; j <= n; j++) { if(a[j].r > i) break; if(a[j].r == i) { if(a[j].l >= q && dp[i] < dp[a[j].l-q] + a[j].p) { dp[i] = dp[a[j].l-q] + a[j].p; ans[i] = a[j].num; pre[i] = a[j].l - q; } if(a[j].num == 1 && dp[i] < dp[a[j].l-q] + a[j].p) { dp[i] = dp[a[j].l-q] + a[j].p; ans[i] = a[j].num; pre[i] = 0; } pos = j; } } } int now = m; while(now) { if(ans[now]) Ans[++cnt] = ans[now]; now = pre[now]; } printf("%0.3lf\n",dp[m]); for(int i = cnt; i >= 1; i--) printf("%d ",Ans[i]); return 0; }
輸出路徑的時候是while(now),不是while(pre[now])..
我說怎麼輸出不了最後一個..