Table of Contents
Problem B. Bars
n 个人排成一排开酒吧,每个人可以选择开或不开,且每个人会去自己以外的左右最近 2 个开业酒吧各 1 次(没有则不去),第 i 个人的酒吧每招待一个顾客得 p_i 元,最大化总利润。
先写个暴力压压惊。
#include <lastweapon/io> using namespace lastweapon; const int N = int(1e6) + 9; LL f[N]; int p[N]; int n; int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif Rush { RD(n); REP(i, n) RD(p[i]); fill(f, f+n, 0); FOR(i, 1, n) { REP(j, i) checkMax(f[i], f[j] + (LL)(p[i]+p[j]) * (i-j)); } cout << f[n-1] << endl; } }
第一反应是凸壳 dp… 但是经过一番化简貌似我们需要求一个三维向量的点积的最值。。
这个就给我整不会了。。。
看了一下 maspy 的题解,原来这个东西可以类比梯形求和。。。(这堆东西叠起来的形状大概像是《魔神英雄传》里的创界山。。)
然后只要保留外面的凸壳就可以了。。做法也和求凸包类似。。。
#include <lastweapon/io> using namespace lastweapon; const int N = int(1e6) + 9; int p[N], s[N], sn; int n; LL w(int j, int i) { return (LL)(p[i]+p[j])*(i-j); } bool bad(int a, int b, int c) { return w(a, b) + w(b, c) <= w(a, c); } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif Rush { RD(n); REP(i, n) RD(p[i]); sn = 0; REP(i, n) { while (sn > 1 && bad(s[sn-1], s[sn], i)) --sn;; s[++sn] = i; } LL z = 0; REP_1(i, sn) z += w(s[i-1], s[i]); cout << z << endl; } }
Problem C. Ctrl+C Ctrl+V
给定一个字符串,问至少修改几个字符,使得字符串中不存在 “ania” 模式串。
签到题。可以 kmp 不过没必要(因为模式串太短且固定)。
每次匹配到 ania 时,直接把末尾的 a 修改成一个不存在的字符即可。
也可以记录连续匹配的段数 c,然后每次增加 (c+1) / 2。
#include <lastweapon/io> using namespace lastweapon; const int N = int(1e6) + 9; char p[5] = "ania"; char s[N]; int n, z, c; int main() { #ifndef ONLINE_JUDGE // freopen("in.txt", "r", stdin); #endif Rush { n = strlen(RS(s)); z = 0; c = 0; int j = 0; for (int i=0;i<n;++i) { if (s[i] == p[j]) { ++j; if (j == 4) { ++c; j = 1; } } else { z += (c+1)/2; c = 0; if (s[i] == 'a') j = 1; else j = 0; } } z += (c+1)/2; cout << z << endl; } }
Problem D. Dazzling Mountain
给定一颗有根数,问有哪些满足条件的 x,使得所有子树 size 等于 x 的节点向下覆盖了所有的叶子。
显然根节点满足,最后所有的叶子节点也满足,
我们不妨枪坦推进,开个 map<int, VI> 记录当前每种不同覆盖面积的根节点。
每次取出面积最大的 VI 依次取出并往孩子移动,当每层扇形的面积都相等时,既此时 map 的 size 恰好为 1 时,则得到一组符合条件的答案,如果此时扇形面积为 1, 说明已经到了叶子,算法结束。
#include <lastweapon/io> using namespace lastweapon; const int N = int(1e6) + 9; VI adj[N]; int sz[N], fa[N]; int n; void dfs(int u = 1, int p = 0) { sz[u] = 1; for (auto v: adj[u]) if (v != p) { fa[v] = u; dfs(v, u); sz[u] += sz[v]; } } int main() { #ifndef ONLINE_JUDGE //freopen("in.txt", "r", stdin); #endif Rush { RD(n); REP_1(i, n) adj[i].clear(); DO(n-1) { int a, b; RD(a, b); adj[a].PB(b); adj[b].PB(a); } dfs(); VI z; z.clear(); map<int, VI> H; H[sz[1]].PB(1); while (true) { int s = H.begin()->fi; z.PB(s); if (s == 1) break; do { auto it = H.end(); --it; VI U = it->se; H.erase(it); for (auto u: U) { for (auto v: adj[u]) if (v != fa[u]) { H[sz[v]].PB(v); } } } while (SZ(H) != 1); } SRT(z); cout << SZ(z) << endl; for (auto zi : z) printf("%d ", zi); puts(""); } }
Problem E. Euclidean Algorithm
如果 a, b 在,且 2a-b>0,则 2a-b 也在,重复这个操作得到的最小的数是 gcd 的 1-n 内的整数 pair有多少个。
不会。
Problem G. Great Chase
警察抓小偷。小偷出生在数轴原点,开始向右以速度 v0 移动,它左右分别有一些警察 (pi, vi),pi 表示警察的位置,vi 表示警察的速度,左边的警察向右追,右边的警察向左追,保证小偷的速度大于所有警察,且数轴两边都存在警察。当小偷遇上警察时,小偷会向反向移动,直到被两个警察包夹。问被包夹时,小偷移动的总距离。
小偷的运动相对比较复杂,我们考虑不管,直接二分时间,看两边的警察是否能相遇即可。
Problem I. Investors
给定 n 个数列,你可以从中筛选出 m 段连续的子序列,将它们整体 offset 一段数,最小化最后的逆序对。
显然最优解一定是每次都选一些后缀进行操作,让原序列分割成 m+1 段互不干扰的区间,分别统计这些段的逆序对和,代价函数即为每一段的逆序对。
那么显然逆序对函数符合经典四边形不等式,由此推出决策单调性。
利用队列维护当前可行决策,并用二分计算出相邻决策的分割点即可(决策不劣的最早时刻)。
(参考诗人小 G)
似乎还存在很多神奇的做法,例如 直接二分每一段代价的上界?
另外瞄了一眼 maspy 的代码,什么,这居然也有 板子?
#include <lastweapon/io> #include <lastweapon/fenwicktree> using namespace lastweapon; const int N = int(6e3) + 9; int a[N], f[2][N], w[N][N]; VI A; int p = 0, q = 1; int Q[N], P[N]; int cz, op; int n, m; int calc(int l, int r) { return f[q][l] + w[l+1][r]; } int left(int a, int b) { int l = a, r = n; while (l < r) { int m = (l + r) / 2; if (calc(b, m) <= calc(a, m)) { r = m; } else { l = m + 1; } } return l; } int main() { #ifndef ONLINE_JUDGE freopen("in.txt", "r", stdin); #endif Rush { RD(n, m); A.clear(); REP(i, n) A.PB(RD(a[i])); if (m >= n-1) { puts("0"); continue; } UNQ(A); REP(i, n) a[i] = LBD(A, a[i]); REP(i, n) { fenwick_tree<int> T(SZ(A)); FOR(j, i+1, n) { T.add(a[j-1], 1); w[i][j] = w[i][j-1] + (j-i) - T.sum(a[j]+1); } } FOR(i, 1, n) f[p][i] = w[0][i]; REP_1(s, m) { swap(p, q); cz = 0, op = 0; Q[0] = s-1; FOR(i, s, n) { //f[p][i] = INF; FOR(j, s-1, i) checkMin(f[p][i], calc(j, i)); while (cz < op && P[cz] <= i) ++cz; f[p][i] = calc(Q[cz], i); int j = left(Q[op], i); while (cz < op && j <= P[op-1]) j = left(Q[--op], i); P[op] = j; Q[++op] = i; } } cout << f[p][n-1] << endl; } }
Problem M. Minor Evil
给定 1 到 n,n 个数和 m 组操作(ai, bi),每组操作表示,当序列中存在 ai 时,可以删除 bi,你必须按照顺序决定每个操作是否执行,问是否能将给定集合中的数都删除。
每个数要尽可能久的做跳板,我们应该要找到它能被删除时的最晚时刻。
第一反应可以类比 Dijkstra(),做法也确实如此。
(上次遇到类似的套路应该是在 Codeforces Round #800 (Div. 1+2) 的 C 题。)
d[i] 现在定义为:i 位置可被删除的最晚时刻。
那么对于所有不需要被删除的位置,初始 d[i] = k+1 即可。
最后只要没有 d[i] = 0 说明所有的点都可以在某个时刻被删除。
所以和原版 Dijkstra 相比,只是变成了时光倒流,以及边多了时间的限制而已。。。
#include <lastweapon/io> using namespace lastweapon; const int N = int(1e6) + 9; set<int> Q[N]; VII adj[N]; char s[N]; int d[N]; int n, k; bool dijkstra() { REP_1(i, k+1) Q[i].clear(); REP_1(i, n) if (d[i] == k+1) Q[k+1].insert(i); DWN_1(i, k+1, 2) { for (auto u: Q[i]) { for (auto e: adj[u]) { int v = e.fi, w = e.se; if (d[v] < w && w < i) { if (d[v]) Q[d[v]].erase(Q[d[v]] .find(v)); Q[d[v] = w].insert(v); } } } } REP_1(i, n) if (!d[i]) return 0; return 1; } int main() { #ifndef ONLINE_JUDGE //freopen("in.txt", "r", stdin); #endif Rush { RD(n, k); REP_1(i, n) adj[i].clear(); REP_1(i, k) { int a, b; RD(a, b); adj[a].PB({b, i}); } REP_1(i, n) d[i] = k+1; Rush d[RD()] = 0; if (!dijkstra()) { puts("NIE"); } else { puts("TAK"); REP(i, k) s[i] = 'N'; REP_1(i, n) s[d[i]-1] = 'T'; s[k] = 0; puts(s); } } }