*注意:这套题目应版权方要求,不得公示题面。
Problem A 闪电攻击
题目大意
SR操纵一架战机。在时刻$a_{i}$初会出现第$i$号敌机,它始终与SR的战机保持的距离为$d_{i}$,在时刻$b_{i}$末它会发射一枚导弹摧毁SR的战机。SR可以选择在任意时间花费$x$的能量消灭所有距离不超过$x$的敌机。问SR在保证他的安全的情况下最少需要多少能量。
开始一直想的是按顺序做决策。然后发现和gg。
但是如果状态改为一个值域区间内的敌机全部消灭掉,那么转移就轻松了许多。
用$f[l][r]$表示将可以被攻击的时间段是$[l, r]$的子区间的战机全部消灭最少需要的能量。
考虑其中$d$最大的敌机一定是被花费能量为$d$的攻击直接消灭掉的。
另外将攻击的时刻移动到端点不会更劣。(然后就可以离散化了)
枚举在这中间是在哪个端点时攻击。把区间分成$[l, k - 1]$和$[k + 1, r]$。
时间复杂度$O(n^3)$。
Code
1 #include <algorithm> 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstdio> 5 using namespace std; 6 typedef bool boolean; 7 8 const int N = 305; 9 const signed int inf = (signed) (~0u >> 1); 10 11 typedef class Enemy { 12 public: 13 int l, r, d; 14 15 Enemy(int l = 0, int r = 0, int d = 0):l(l), r(r), d(d) {} 16 }Enemy; 17 18 int n; 19 Enemy es[N]; 20 int ps[N << 1]; 21 int f[N << 1][N << 1]; 22 boolean vis[N << 1][N << 1]; 23 24 inline void init() { 25 scanf("%d", &n); 26 for (int i = 1, l, r, d; i <= n; i++) { 27 scanf("%d%d%d", &l, &r, &d); 28 ps[(i << 1) - 1] = l, ps[(i << 1)] = r; 29 es[i] = Enemy(l, r, d); 30 } 31 } 32 33 int dfs(int l, int r) { 34 if (l > r) 35 return 0; 36 if (vis[l][r]) 37 return f[l][r]; 38 vis[l][r] = true; 39 int mxd = -1, id = -1; 40 for (int i = 1; i <= n; i++) 41 if (es[i].l >= l && es[i].r <= r && es[i].d > mxd) 42 mxd = es[i].d, id = i; 43 if (id == -1) 44 return f[l][r] = 0; 45 f[l][r] = inf; 46 for (int i = 1; i <= n; i++) { 47 if (!(es[i].l >= l && es[i].r <= r)) 48 continue; 49 if (es[i].l >= es[id].l && es[i].l <= es[id].r) 50 f[l][r] = min(f[l][r], dfs(l, es[i].l - 1) + dfs(es[i].l + 1, r) + es[id].d); 51 if (es[i].r >= es[id].l && es[i].r <= es[id].r) 52 f[l][r] = min(f[l][r], dfs(l, es[i].r - 1) + dfs(es[i].r + 1, r) + es[id].d); 53 } 54 return f[l][r]; 55 } 56 57 inline void solve() { 58 sort(ps + 1, ps + n * 2 + 1); 59 for (int i = 1; i <= n; i++) { 60 es[i].l = lower_bound(ps + 1, ps + n * 2 + 1, es[i].l) - ps; 61 es[i].r = lower_bound(ps + 1, ps + n * 2 + 1, es[i].r) - ps; 62 } 63 64 for (int i = 1; i <= n; i++) 65 for (int j = i + 1; j <= n; j++) 66 dfs(min(es[i].l, es[j].l), max(es[i].r, es[j].r)); 67 68 int l = inf, r = -inf; 69 for (int i = 1; i <= n; i++) 70 l = min(l, es[i].l), r = max(r, es[i].r); 71 printf("%d\n", dfs(l, r)); 72 } 73 74 int main() { 75 init(); 76 solve(); 77 return 0; 78 }
Problem B 最小生成树
题目大意
给定$n$个点$m$条边的无向带权图,每次给定$l, r$,询问边权在$[l, r]$之间的边在保证连通块个数尽量小的情况下最小生成森林。(强制在线)
考虑Kruskal的过程,发现$r$其实没有任何用。原因是我可以把选择的边存下来,记录边权的前缀和,然后询问的时候二分一下。
于是先将边排序,然后从大到小加入每一条边。记录每次的最小生成树中选择的边。
由于数据范围很小可以直接暴力。出题者考虑到要让代码量和谐,所以没有把数据范围变成$n, m \leqslant 10^5$。
如果改成这个毒瘤数据范围,那么就把dfs找环上最小边改成LCT,复制数组改成可持久化线段树。
由于我比较懒,出题人也比较懒,出随机数据,于是我写了一个期望$O(nm\log m + q\log n)$的做法(最坏$O(m^{2})$,233)。
考场上粘另一道题的读入优化板子,没处理负数,强制在线的一半的点全挂掉了qwq。处理一下读入负数就过了。
Code
1 #include <algorithm> 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstdio> 5 #include <ctime> 6 using namespace std; 7 typedef bool boolean; 8 #define pii pair<int, int> 9 #define fi first 10 #define sc second 11 12 typedef class IO { 13 protected: 14 const static int Limit = 65536; 15 FILE* file; 16 17 int ss, st; 18 char buf[Limit]; 19 public: 20 IO():file(NULL) { }; 21 IO(FILE* file):file(file) { } 22 23 void open(FILE *file) { 24 this->file = file; 25 } 26 27 char pick() { 28 if (ss == st) 29 st = fread(buf, 1, Limit, stdin), ss = 0;//, cerr << "Str: " << buf << "ED " << st << endl; 30 return buf[ss++]; 31 } 32 }IO; 33 34 #define digit(_x) ((_x) >= '0' && (_x) <= '9') 35 36 IO& operator >> (IO& in, int& u) { 37 char x; 38 int aflag = 1; 39 while (~(x = in.pick()) && !digit(x) && x != '-'); 40 if (x == '-') { 41 aflag = -1; 42 x = in.pick(); 43 } 44 for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); 45 u = u * aflag; 46 return in; 47 } 48 49 IO in; 50 51 typedef class Edge { 52 public: 53 int u, v, w; 54 55 boolean operator < (Edge b) const { 56 return w < b.w; 57 } 58 }Edge; 59 60 const int N = 505, M = 20005; 61 62 int n, m, q, alpha; 63 int* wss, *uf; 64 Edge* es; 65 pii res[M][N]; 66 int fins[M]; 67 68 int find(int x) { 69 return (x == uf[x]) ? (x) : (uf[x] = find(uf[x])); 70 } 71 72 inline void init() { 73 in >> n >> m; 74 uf = new int[(n + 1)]; 75 es = new Edge[(m + 1)]; 76 wss = new int[(m + 1)]; 77 for (int i = 1; i <= m; i++) { 78 in >> es[i].u >> es[i].v >> es[i].w; 79 wss[i] = es[i].w; 80 } 81 } 82 83 inline void solve() { 84 sort(es + 1, es + m + 1); 85 sort(wss + 1, wss + m + 1); 86 fins[0] = n - 1; 87 for (int i = 1; i <= m; i++) { 88 int fin = 0; 89 for (int j = 1; j <= n; j++) 90 uf[j] = j; 91 for (int j = i; j <= m && fin < fins[i - 1]; j++) { 92 int u = es[j].u, v = es[j].v; 93 if (find(u) != find(v)) { 94 uf[find(u)] = find(v); 95 fin++; 96 res[i][fin].fi = res[i][fin - 1].fi + es[j].w; 97 res[i][fin].sc = es[j].w; 98 } 99 } 100 fins[i] = fin; 101 } 102 103 in >> q >> alpha; 104 int L, R, lastans = 0, s; 105 while (q--) { 106 in >> L >> R; 107 L = lastans * alpha + L, R = L + R; 108 int l = 1, r = m, mid; 109 while (l <= r) { 110 mid = (l + r) >> 1; 111 if (wss[mid] >= L) 112 r = mid - 1; 113 else 114 l = mid + 1; 115 } 116 s = r + 1; 117 if (s > m) { 118 puts("0"); 119 continue; 120 } 121 122 l = 1, r = fins[s]; 123 while (l <= r) { 124 mid = (l + r) >> 1; 125 if (res[s][mid].sc <= R) 126 l = mid + 1; 127 else 128 r = mid - 1; 129 } 130 printf("%d\n", lastans = res[s][l - 1].fi); 131 } 132 } 133 134 int main() { 135 freopen("mst.in", "r", stdin); 136 freopen("mst.out", "w", stdout); 137 init(); 138 solve(); 139 return 0; 140 }
Problem C 最佳南瓜
题目大意
有$n$个数的数组和$m$个操作,每次操作分两步:
- 区间加上1
- 询问整个数组的中位数
(保证$n$为奇数)
直接考虑分块。
每个块维护快内的数排序后的数组(保留原来的下标)。
修改的时候区间内打tag,两端暴力重构。重构的时候用瑞士轮的方法归并排排序。
询问整个数组的中位数时二分就挂了。由于修改比较特殊,所以答案要么不变要么增加1。
因此每次暴力判断答案是否增加1。判断的话就一个一个块地跳,每个块二分一下多少数小于等于当前答案。
然后调整一下块大小(显然不能是$\sqrt{n}$)就给过了。
Code
1 #include <algorithm> 2 #include <iostream> 3 #include <cstdlib> 4 #include <cstdio> 5 #include <ctime> 6 using namespace std; 7 typedef bool boolean; 8 #define pii pair<int, int> 9 #define fi first 10 #define sc second 11 12 typedef class IO { 13 protected: 14 const static int Limit = 65536; 15 FILE* file; 16 17 int ss, st; 18 char buf[Limit]; 19 public: 20 IO():file(NULL) { }; 21 IO(FILE* file):file(file) { } 22 23 void open(FILE *file) { 24 this->file = file; 25 } 26 27 char pick() { 28 if (ss == st) 29 st = fread(buf, 1, Limit, stdin), ss = 0;//, cerr << "Str: " << buf << "ED " << st << endl; 30 return buf[ss++]; 31 } 32 }IO; 33 34 #define digit(_x) ((_x) >= '0' && (_x) <= '9') 35 36 IO& operator >> (IO& in, int& u) { 37 char x; 38 while (~(x = in.pick()) && !digit(x)); 39 for (u = x - '0'; ~(x = in.pick()) && digit(x); u = u * 10 + x - '0'); 40 return in; 41 } 42 43 IO in; 44 45 const int N = 1e5 + 5, cs = 450; 46 47 pii b1[cs + 1], b2[cs + 1]; 48 49 typedef class Chunk { 50 public: 51 int tag, s; 52 pii cr[cs + 1]; 53 54 Chunk():tag(0), s(0) { } 55 56 void init(int s, int* ar) { 57 this->s = s; 58 for (int i = 0; i < s; i++) 59 in >> cr[i].fi, cr[i].sc = i, ar[i] = cr[i].fi; 60 sort(cr, cr + s); 61 } 62 63 void pushDown() { 64 if (!tag) 65 return; 66 for (int i = 0; i < s; i++) 67 cr[i].fi += tag; 68 tag = 0; 69 } 70 71 void add(int l, int r) { 72 pushDown(); 73 int tp1 = 0, tp2 = 0, p = 0; 74 for (int i = 0; i < s; i++) 75 if (cr[i].sc >= l && cr[i].sc <= r) 76 cr[i].fi++, b1[++tp1] = cr[i]; 77 else 78 b2[++tp2] = cr[i]; 79 int p1 = 1, p2 = 1; 80 while (p1 <= tp1 || p2 <= tp2) { 81 if (p1 <= tp1 && (p2 > tp2 || b1[p1].fi <= b2[p2].fi)) 82 cr[p++] = b1[p1++]; 83 else 84 cr[p++] = b2[p2++]; 85 } 86 } 87 88 int query(int x) { // How many numbers are not greater than x 89 x -= tag; 90 int l = 0, r = s - 1; 91 while (l <= r) { 92 int mid = (l + r) >> 1; 93 if (cr[mid].fi <= x) 94 l = mid + 1; 95 else 96 r = mid - 1; 97 } 98 return l; 99 } 100 }Chunk; 101 102 int n, m, hn, cc; 103 int cmid = 0; 104 int ar[N]; 105 Chunk chs[N / cs + 10]; 106 107 inline void init() { 108 in >> n >> m; 109 hn = (n >> 1) + 1; 110 for (int l = 0, r; l < n; l += cs, cc++) { 111 r = min(l + cs, n) - 1; 112 chs[l / cs].init(r - l + 1, ar + l); 113 } 114 } 115 116 int calc(int x) { 117 int rt = 0; 118 for (int i = 0; i < cc && rt < hn; i++) 119 rt += chs[i].query(x); 120 return rt; 121 } 122 123 inline void solve() { 124 sort(ar, ar + n); 125 cmid = ar[hn - 1]; 126 for (int i = 1, l, r, lid = 0, rid = 0; i <= m; i++) { 127 in >> l >> r; 128 l--, r--; 129 lid = l / cs, rid = r / cs; 130 l %= cs, r %= cs; 131 if (lid == rid) 132 chs[lid].add(l, r); 133 else { 134 for (int j = lid + 1; j < rid; j++) 135 chs[j].tag++; 136 chs[lid].add(l, chs[lid].s - 1); 137 chs[rid].add(0, r); 138 } 139 while (calc(cmid) < hn) 140 cmid++; 141 printf("%d\n", cmid); 142 } 143 } 144 145 int main() { 146 freopen("pumpkin.in", "r", stdin); 147 freopen("pumpkin.out", "w", stdout); 148 init(); 149 solve(); 150 return 0; 151 }