Vijos 1404 遭遇战 - 动态规划 - 线段树 - 最短路 - 堆
背景
你知道吗,SQ Class的人都很喜欢打CS。(不知道CS是什么的人不用参加这次比赛)。
描述
今天,他们在打一张叫DUSTII的地图,万恶的恐怖分子要炸掉藏在A区的SQC论坛服务器!我们SQC的人誓死不屈,即将于恐怖分子展开激战,准备让一个人守着A区,这样恐怖分子就不能炸掉服务器了。(一个人就能守住??这人是机械战警还是霹雳游侠?)
但是问题随之出现了,由于DustII中风景秀丽,而且不收门票,所以n名反恐精英们很喜欢在这里散步,喝茶。他们不愿意去单独守在荒无人烟的A区,在指挥官的一再命令下,他们终于妥协了,但是他们每个人都要求能继续旅游,于是给出了自己的空闲时间,而且你强大的情报系统告诉了你恐怖份子计划的进攻时间(从s时刻到e时刻)。
当然,精明的SQC成员不会为你免费服务,他们还要收取一定的佣金(注意,只要你聘用这个队员,不论他的执勤时间多少,都要付所有被要求的佣金)。身为指挥官的你,看看口袋里不多的资金(上头真抠!),需要安排一个计划,雇佣一些队员,让他们在保证在进攻时间里每时每刻都有人员执勤,花费的最少资金。
格式
输入格式
第一行是三个整数n(1≤n≤10000),s和e(1≤s≤e≤90000)。
接下来n行,描述每个反恐队员的信息:空闲的时间si, ei(1≤si≤ei≤90000)和佣金ci(1≤ci≤300000)。
输出格式
一个整数,最少需支付的佣金,如果无解,输出“-1”。
样例1
样例输入1
3 1 5
1 3 3
4 5 2
1 1 1
样例输出1
5
限制
提示
敌人从1时刻到4时刻要来进攻,一共有3名反恐队员。第1名从1时刻到3时刻有空,要3元钱(买糖都不够??)。以此类推。
一共要付5元钱,选用第1名和第2名。
来源
SQ CLASS公开编程竞赛2008——Problem D
Source: WindTalker, liuichou, royZhang
题目大意
数轴上有一些区间,每个区间有一个费用。要求选择一些区间将$[s, t]$覆盖,问最小的总费用。
Solution#1 Dynamic Programming & Heap Optimization
dp是显然的。 用$f[i]$表示将$[s, i]$覆盖的最小费用。
考虑转移。显然通过能够覆盖点$i$的线段进行转移,因此有
$f[i] = \max_{线段j能覆盖点i}\left\{ f[l_{j} - 1] + w_{j}\right\}$
显然可以用一个堆维护右边的那一坨。
Code
1 /** 2 * Vijos 3 * Problem#1404 4 * Accepted 5 * Time: 98ms 6 * Memory: 3.48m 7 */ 8 #include <bits/stdc++.h> 9 #ifndef WIN32 10 #define Auto "%lld" 11 #else 12 #define Auto "%I64d" 13 #endif 14 using namespace std; 15 typedef bool boolean; 16 17 #define ll long long 18 19 typedef class Segment { 20 public: 21 int l, r; 22 int w; 23 24 Segment(int l = 0, int r = 0, int w = 0):l(l), r(r), w(w) { } 25 26 boolean operator < (Segment b) const { 27 return l < b.l; 28 } 29 }Segment; 30 31 typedef class Heap { 32 public: 33 priority_queue<ll, vector<int>, greater<int> > que; 34 priority_queue<ll, vector<int>, greater<int> > del; 35 36 Heap() { } 37 38 ll top() { 39 while (!del.empty() && que.top() == del.top()) 40 que.pop(), del.pop(); 41 return que.top(); 42 } 43 44 void push(ll x) { 45 que.push(x); 46 } 47 48 void remove(ll x) { 49 del.push(x); 50 } 51 52 boolean empty() { 53 return (que.size() == del.size()); 54 } 55 }Heap; 56 57 int n, s, t; 58 Segment* ss; 59 vector<Segment*> *g; 60 Heap h; 61 ll *f; 62 63 inline void init() { 64 scanf("%d%d%d", &n, &s, &t); 65 ss = new Segment[(n + 1)]; 66 g = new vector<Segment*>[(t - s + 5)]; 67 f = new ll[(t - s + 5)]; 68 t = t - s + 1; 69 for (int i = 1; i <= n; i++) { 70 scanf("%d%d%d", &ss[i].l, &ss[i].r, &ss[i].w); 71 ss[i].l = max(ss[i].l - s, 0) + 1; 72 ss[i].r = max(ss[i].r - s, 0) + 1; 73 if (ss[i].r > t) ss[i].r = t; 74 } 75 } 76 77 inline void solve() { 78 int p = 1; 79 f[0] = 0; 80 sort (ss + 1, ss + n + 1); 81 for (int i = 1; i <= t; i++) { 82 while (p <= n && ss[p].l == i) { 83 h.push(f[ss[p].l - 1] + ss[p].w); 84 g[ss[p].r].push_back(ss + p); 85 p++; 86 } 87 if (h.empty()) { 88 puts("-1"); 89 return; 90 } 91 f[i] = h.top(); 92 for (int j = 0; j < (signed)g[i].size(); j++) 93 h.remove(f[g[i][j]->l - 1] + g[i][j]->w); 94 g[i].clear(); 95 } 96 printf(Auto"\n", f[t]); 97 } 98 99 int main() { 100 init(); 101 solve(); 102 return 0; 103 }
Solution#2 Shortest Path & Segment Tree Optimization
其实把dp的转移变成边,然后发现巧的是每条区间的前一个点,向区间上的所有整点连边。
于是写个线段树建图优化(其实没什么高端的,就是建虚点,防止重复的连边,然后再加点连边的技巧就好了)。
Code
1 /** 2 * Vijos 3 * Problem#1404 4 * Accepted 5 * Time: 606ms 6 * Memory: 16.0m 7 */ 8 #include <bits/stdc++.h> 9 #ifndef WIN32 10 #define Auto "%lld" 11 #else 12 #define Auto "%I64d" 13 #endif 14 using namespace std; 15 typedef bool boolean; 16 17 #define ll long long 18 #define pii pair<int, int> 19 #define fi first 20 #define sc second 21 22 const signed ll llf = (signed ll) (~0ull >> 1); 23 24 int n, s, t; 25 vector<pii> *g; 26 ll *f; 27 boolean *vis; 28 29 inline void init() { 30 scanf("%d%d%d", &n, &s, &t); 31 t = t - s + 1; 32 int siz = ((t + 1) << 2) + 5; 33 g = new vector<pii>[siz]; 34 f = new ll[siz]; 35 vis = new boolean[siz]; 36 fill(vis, vis + siz, false); 37 fill(f, f + siz, llf >> 1); 38 } 39 40 void build(int p, int l, int r) { 41 if (l == r) return; 42 int mid = (l + r) >> 1; 43 g[p].push_back(pii(p << 1, 0)); 44 g[p].push_back(pii((p << 1) | 1, 0)); 45 build(p << 1, l, mid); 46 build((p << 1) | 1, mid + 1, r); 47 } 48 49 int query(int p, int l, int r, int idx) { 50 if (l == r) 51 return p; 52 int mid = (l + r) >> 1; 53 if (idx <= mid) 54 return query(p << 1, l, mid, idx); 55 return query((p << 1) | 1, mid + 1, r, idx); 56 } 57 58 void query(int p, int l, int r, int ql, int qr, int st, int w) { 59 if (l == ql && r == qr) { 60 g[st].push_back(pii(p, w)); 61 return; 62 } 63 int mid = (l + r) >> 1; 64 if (qr <= mid) 65 query(p << 1, l, mid, ql, qr, st, w); 66 else if (ql > mid) 67 query((p << 1) | 1, mid + 1, r, ql, qr, st, w); 68 else { 69 query(p << 1, l, mid, ql, mid, st, w); 70 query((p << 1) | 1, mid + 1, r, mid + 1, qr, st, w); 71 } 72 } 73 74 inline void build() { 75 build(1, 0, t); 76 for (int i = 1, l, r, w; i <= n; i++) { 77 scanf("%d%d%d", &l, &r, &w); 78 l = max(l - s, 0) + 1; 79 r = max(r - s, 0) + 1; 80 if (r > t) r = t; 81 int id = query(1, 0, t, l - 1); 82 query(1, 0, t, l, r, id, w); 83 } 84 } 85 86 queue<int> que; 87 inline void spfa(int s) { 88 que.push(s); 89 f[s] = 0; 90 while (!que.empty()) { 91 int e = que.front(); 92 que.pop(); 93 vis[e] = false; 94 for (int i = 0; i < (signed)g[e].size(); i++) { 95 int eu = g[e][i].fi, w = g[e][i].sc; 96 if (f[e] + w < f[eu]) { 97 f[eu] = f[e] + w; 98 if (!vis[eu]) { 99 vis[eu] = true; 100 que.push(eu); 101 } 102 } 103 } 104 } 105 } 106 107 inline void solve() { 108 int is = query(1, 0, t, 0); 109 int it = query(1, 0, t, t); 110 spfa(is); 111 if (f[it] == (llf >> 1)) 112 puts("-1"); 113 else 114 printf(Auto"\n", f[it]); 115 } 116 117 int main() { 118 init(); 119 build(); 120 solve(); 121 return 0; 122 }
Solution#3 Shortest Path
wtf?纯最短路?没逗我?
每个区间的前一个点向区间结束点连边。然后每个点向它的前一个点连边,权值为0。
(神奇的建图方法。。)
看起来非常朴素,竟然没有dp快
Code
1 /** 2 * Vijos 3 * Problem#1404 4 * Accepted 5 * Time: 285ms 6 * Memory: 5.75m 7 */ 8 #include <bits/stdc++.h> 9 #ifndef WIN32 10 #define Auto "%lld" 11 #else 12 #define Auto "%I64d" 13 #endif 14 using namespace std; 15 typedef bool boolean; 16 17 #define ll long long 18 #define pii pair<int, int> 19 #define fi first 20 #define sc second 21 22 const signed ll llf = (signed ll) (~0ull >> 1); 23 24 int n, s, t; 25 vector<pii> *g; 26 ll *f; 27 boolean *vis; 28 29 inline void init() { 30 scanf("%d%d%d", &n, &s, &t); 31 t = t - s + 1; 32 g = new vector<pii>[t + 1]; 33 f = new ll[t + 1]; 34 vis = new boolean[t + 1]; 35 fill(vis, vis + t + 1, false); 36 fill(f, f + t + 1, llf >> 1); 37 } 38 39 inline void build() { 40 for (int i = 1, l, r, w; i <= n; i++) { 41 scanf("%d%d%d", &l, &r, &w); 42 l = max(l - s, 0) + 1; 43 r = max(r - s, 0) + 1; 44 if (r > t) r = t; 45 g[l - 1].push_back(pii(r, w)); 46 } 47 for (int i = 1; i <= t; i++) 48 g[i].push_back(pii(i - 1, 0)); 49 } 50 51 queue<int> que; 52 inline void spfa(int s) { 53 que.push(s); 54 f[s] = 0; 55 while (!que.empty()) { 56 int e = que.front(); 57 que.pop(); 58 vis[e] = false; 59 for (int i = 0; i < (signed)g[e].size(); i++) { 60 int eu = g[e][i].fi, w = g[e][i].sc; 61 if (f[e] + w < f[eu]) { 62 f[eu] = f[e] + w; 63 if (!vis[eu]) { 64 vis[eu] = true; 65 que.push(eu); 66 } 67 } 68 } 69 } 70 } 71 72 inline void solve() { 73 spfa(0); 74 if (f[t] == (llf >> 1)) 75 puts("-1"); 76 else 77 printf(Auto"\n", f[t]); 78 } 79 80 int main() { 81 init(); 82 build(); 83 solve(); 84 return 0; 85 }