有一款游戏,它有 n 个关卡,第 n 个关卡的难度是 pi,已知 n 是 1,…,n 的一个排列。玩家不必按照 1 到 n 的顺序通过这 n 个关卡,他们可以自己选择闯关的顺序。对于每一个关卡,游戏定义了一个重要度 di ,为 p 的前缀最小值,小k每次惠选择 d 最低的关卡闯关,特殊地,如果这样的关卡有很多个,那么他会选择编号最小的那一个。
给出小k 通关的顺序,求出所有可能的难度中字典序最小的
简单构造。先构造 d ,必要的时候加一,也就是说 a 的下标比 b 的下标更靠前,却先打了 b ,那么 a 的重要度一定是更高的
int siz[MAXN], fa[MAXN]; voiddfs1(int u, int dad) { siz[u] = 1; fa[u] = dad; for (int v : e[u]) if (v != dad) { dfs1(v, u); siz[u] += siz[v]; } } voidworka(int u, int dad) { for (int v : e[u]) if (v != dad) { worka(v, u); } num[u] = ++cnta; } voidworkb(int u, int dad) { for (int v : e[u]) if (v != dad) { workb(v, u); } num[u] = --cntb; } intmain() { dfs1(1, 0); for (int i = 1; i <= n; i++) { if (siz[i] == a) { flag = 1; worka(i, fa[i]); workb(fa[i], i); break; } if (siz[i] == b) { flag = 1; workb(i, fa[i]); worka(fa[i], i); break; } } }
T3
有一棵 n 个结点的树,编号为 1 到 n ,每个点有一个颜色,颜色编号的范围是 1 到 m,保证每种颜色至少出现一次。
你需要选择一个结点作为根,同时找一个树上节点的非空子集 T ,满足每种颜色都至少在 T 中出现一次,并且 T 中所有点的 LCA 的深度最大。定义你选的根深度为 1 ,儿子的深度是父亲深度 +1 。
for (int i = 1; i <= d; i++) { ans1[1] += (d - i + 1) * (d - i + 1) * a[i]; if (i > 1) tmp += (d - i + 1) * a[i]; } for (int i = 1; i <= n; i++) { int l = max(1, i), r = min(n, i + d); ans1[i + 1] = (ans1[i] - d * d * a[i] + tmp * 2 + sum[r] - sum[l]); tmp += sum[r] - sum[l]; tmp -= a[i + 1] * d; }
reverse(a + 1, a + 1 + n); for (int i = 1; i <= n; i++) sum[i] = sum[i - 1] + a[i]; tmp = 0;
for (int i = 1; i <= d; i++) { ans2[1] += (d - i + 1) * (d - i + 1) * a[i]; if (i > 1) tmp += (d - i + 1) * a[i]; } for (int i = 1; i <= n; i++) { int l = max(1, i), r = min(n, i + d); ans2[i + 1] = (ans2[i] - d * d * a[i] + tmp * 2 + sum[r] - sum[l]); tmp += sum[r] - sum[l]; tmp -= a[i + 1] * d; } reverse(ans2 + 1, ans2 + 2 + n); for (int i = 1; i <= n + 1; i++) { ans = max(ans, ans1[i] + ans2[i]); }
T2
给一个长度为 n 的序列 A ,你可以删去原序列的一些数,或者一个数都不删除,将剩下的数按照原来的顺序组成新的长度为 m(1≥m≥n) 的序列 。
现在你想知道,对于所有合法的序列 B ,∑i=1m[Bi=i] 的最大值是多少。
先考虑暴力DP
fi 表示 前 i 个数 ,以 i 结尾的答案
f(i)=max{f(j)+1∣j<i,aj<ai,ai−aj<i−j}
那么转移的条件就是 :
j<i
aj<ai
ai−aj<i−j
将序列按照 ai 排序,然后求⼀个最⻓的 i−ai 不下降的⼦序列⻓度即可
10.4
div.1
T1
小 A 和小 B 竞选班长流程如下:
在 [1,n] 中随机选取一个区间 [l,r] ,统计区间内同学的投票情况,如果二人票数之差的绝对值不超过 k ,那么平局,都是班长;否则票数高的同学当选。小 A 担心落选,准备收买一些同学,使得在任何情况下自己都能成为班长。已知收买编号为 i 的同学的花费为 2i,请问小 A 的最少花费是多少
进行一个贪心,每次在不满足的情况下选取下标最小的人收买,所以倒序枚举,不行就收买一下
T2
有 n 个被抽象成矩形的同学在拍照。其中第 i 个同学宽为 wi ,高为 hi 。
需要宽为所有 w 之和,高为所有 h 最大值的相框才能容下所有的同学。
为了缩小相框的面积,你可以让一部分同学躺下。第 i 个同学躺下之后,他的宽变成 hi ,高变成 wi。
同时,太多人躺着不好看,所以最多只能有 ⌊2n⌋ 个同学躺下。
我们枚举最后相框的高度 h,对于第 i 个人,有以下几种情况:
hi≤h,wi≤h ,躺不躺都可
hi≥h,wi≤h ,一定要躺
hi≤h,wi≥h ,一定不能躺
hi≥h,wi≥h ,说明 h 不合法
那么只有第一种情况的人有操作空间,如果躺下的话,宽度会减小 wi−hi ,按这个值排序取最优的即可
intmain() { read(n, m); for (int i = 1; i <= m; i++) { read(l[i], r[i]); a[l[i]].change({r[i], i}); } for (int i = 1; i <= n; i++) { a[i].change(a[i-1].mxz); a[i].change(a[i-1].mxxz); f[i][0] = a[i].mxz.to; g[i][0] = a[i].mxxz.to; } for (int j = 1; j <= MXBT; j++) { for (int i = 1; i <= n; i++) { f[i][j] = f[f[i][j-1]][j-1]; if (a[g[i][j-1]].mxz.id == a[i].mxz.id) g[i][j] = g[g[i][j-1]][j-1]; else g[i][j] = f[g[i][j-1]][j-1]; } }
read(q); while(q--) { int id, s, t, ans = 0; if (t < l[id]) id = 0; if (s < l[id]) { for (int i = MXBT; ~i; i--) { if (f[s][i] < l[id]) ans += (1 << i), s = f[s][i]; } ans++; s = f[s][0]; } if (s >= t) { printf("%d\n", ans); continue; } for(int i = MXBT; i >= 0; i--) { if (a[s].mxz.id == id) { if (g[s][i] < t) ans += (1 << i), s = g[s][i]; } else { if (f[s][t] < t) ans += (1 << i), s = f[s][i]; } } ans++; if (a[s].mxz.id == id) s = g[s][0]; else s = f[s][0]; if (s < t) printf("-1\n"); elseprintf("%d\n", ans); }
return0; }
T4
教练给小Z布置了一个作业,教练给出了 n 个命题,让他证明 n 个命题全部为真。但是小Z非常的懒,一旦需要证明的命题数量大于 k,小Z就会摆烂不完成作业。因此小Z向他的好朋友黑恶卷怪小L求助。小L帮小Z证明了 m 个等价关系,即对于 1≤i≤m ,小L证明了 ui 和 vi 是等价的。小L把他的成果发给了小Z,但是网络质量不佳,小Z只收到了一个区间的等价关系证明。 现在有 q 条时间线,你需要对于每条时间线,求出假如小Z收到了第 l∼r 个等价关系的证明,小Z是否能够完成作业
namespace union_set { structnode { int x, y, add; };
int fa[MAXN], siz[MAXN], cnt; node sta[MAXN << 2]; int top;
voidinit() { for (int i = 1; i <= n; i++) { fa[i] = i; siz[i] = 1; cnt = n; } }
intfind(int x) { return x == fa[x] ? x : find(fa[x]); }
voidmerge(int x, int y) { int fx = find(x), fy = find(y); if (fx == fy) return; if (siz[fx] > siz[fy]) swap(fx, fy); siz[fy] += siz[fx]; fa[fx] = fy; cnt--; sta[++top] = {fx, fy, 1}; }
voideko(int x, int y, int z) { fa[x] = x; siz[y] -= siz[x]; cnt += z; }
}usingnamespace union_set;
#define ls(i) (i << 1) #define rs(i) (i << 1 | 1)
structnood { int x, y; }e[MAXN];
vector <int> v[MAXN << 2];
int f[MAXN], now;
voidadd(int i, int l, int r, int L, int R, int u) { if (l >= L && r <= R) { v[i].push_back(u); return; } int mid = (l + r) >> 1; if (mid >= L) add(ls(i), l, mid, L, R, u); if (mid+1 <= R) add(rs(i), mid+1, r, L, R, u); }
voidsolve(int u, int l, int r) { int last = top; for (auto k : v[u]) merge(e[k].x, e[k].y); if (l == r) { while (now && cnt > k) { if (now < l) add(1, 1, n, now, l-1, now); merge(e[now].x, e[now].y); now--; } if (cnt <= k) f[l] = now + 1; } else { int mid = (l + r) >> 1; solve(rs(u), mid+1, r); solve(ls(u), l, mid); } while (top > last) eko(sta[top].x, sta[top].y, sta[top].add), top--; }
intmain() { read(n, m, k, Type); for (int i = 1; i <= m; i++) { int u, v; read(u, v); e[i] = {u, v}; } now = m; init(); solve(1, 1, m); read(q); unsignedint lastans = 0; while (q--) { int l, r; read(l, r); if (Type) { l = ((unsignedint)l + lastans) % m + 1; r = ((unsignedint)r + lastans) % m + 1; if (l > r) swap(l ,r); } lastans <<= 1; if (f[r] >= l) lastans++; puts(f[r] >= l ? "Yes" : "No"); } return0; }
10.6
T1
我们定义一个序列是单峰的,当且仅当它满足以下一个条件:
存在一个下标 k(1≤k≤n) 满足 a1<a2<a3<⋯<ak>ak+1>⋯>an
存在一个下标 k(1≤k≤n) 满足 a1>a2>a3>⋯>ak<ak+1<⋯<an
一个非单峰序列可以进行若干次Swap操作使得序列变成单峰的。小C每次会在序列的末尾添加一个新的元素 x ,小C想知道每次添加新的元素后,序列最少进行几次Swap操作可以变成单峰的
#include<bits/stdc++.h> #define ll long long #define MAXN 300005
usingnamespace std; int n;
namespace doubleForkIndexTree { #define lowbit(i) (i & -i)
int t[MAXN];
voidadd(int pos, int val) { while (pos <= n) { t[pos] += val; pos += lowbit(pos); } } intquery(int pos, int res = 0) { while (pos) { res += t[pos]; pos -= lowbit(pos); } return res; }
}usingnamespace doubleForkIndexTree;
int a[MAXN], h[MAXN];
vector <int> q[MAXN];
int iOrderPair[MAXN];
ll ans[MAXN];
voidsolve() { memset(t, 0, sizeof(t)); for (int i = 1; i <= n; i++) q[i].clear(); for (int i = 1; i <= n; i++) { h[a[i]] = i; iOrderPair[i] = query(a[i]); add(a[i], 1); } memset(t, 0, sizeof(t)); for (int i = 1; i <= n; i++) { int l = h[i], r = n, mid; while (l < r) { mid = (l + r) >> 1; if (query(mid) >= 2 * iOrderPair[h[i]]) r = mid; else l = mid + 1; } q[l].push_back(i); add(h[i], 1); } memset(t, 0, sizeof(t));
ll v = 0; for (int i = 1; i <= n; i++) { v += query(n) - query(a[i]); for (int j : q[i]) add(j, -1); add(a[i], 1); ans[i] = min(ans[i], v); } }
intmain() { read(n); memset(ans, 0x3f, sizeof(ans)); for (int i = 1; i <= n; i++) read(a[i]), h[i] = a[i]; sort(h+1, h+1+n); for (int i = 1; i <= n; i++) a[i] = lower_bound(h+1, h+1+n, a[i]) - h;
solve(); for (int i = 1; i <= n; i++) a[i] = n - a[i] + 1; solve();
for (int i = 1; i <= n; i++) printf("%lld\n", ans[i]);
return0; }
T2
小 Q 要进行 n 组体能训练。需要 m 种配重圆盘。在第 i 组训练中,需要 wi,j 个配重为 j 的圆盘。小 Q 的训练器械是一个类似栈的结构,每次只能取出最上方的圆盘或把一个圆盘放在最上方,每次操作都要消耗一点体力。淸帮助小 Q 安排放圓盤的順序,使得消耗的體力最少
与处理出每次最多可以保留的圆盘,也就是 min(wi−1,j,wi,j) ,然后区间Dp
Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
memset(f, 0x3f, sizeof(f)); for (int i = 1; i <= n;i++) { for (int j = 1; j <= m; j++) read(w[i][j]), g[j] = w[i][j]; for (int j = i; j >= 1; j--) for (int k = 1; k <= m; k++) { g[k] = min(g[k], w[j][k]); cost[j][i] += g[k] * 2; } f[i][i] = cost[i][i]; } for (int len = 2; len <= n; len++) for (int l = 1; l <= n - len + 1; l++) { int r = l + len - 1; for (int k = l; k < r; k++) f[l][r] = min(f[l][r], f[l][k] + f[k+1][r] - cost[l][r]); }
T3
有 n 个小朋友,第 i 个小朋友站在 xi,yi ,小 Q 在地上放置了 n 个普通 🍬 ,小 Q 每次回选择一个小朋友,小朋友回每次选择距离他最近的糖果拿下,但是小 Q 还不小心把它喜欢的艾尔登法环掉到地上了,请问是否存在使唤小朋友的顺序,使得最后选剩下的是艾尔登法环
对于剩下的情况主要是这样的,可以以一个点为根向其余的点全连上边,所以最少的边数是 n−1。
巫师之间必须要连边或者连向同一个人,那么也就是说可以枚举上面说的 n 。
还有就是只有两个巫师的情况,也可以直接他们之间连边,然后剩下的点哪个代价少连那个。特判取 min 即可
T3
给定两个序列,安排顺序,使得对应位置异或出来的新序列字典序最少
建两颗 Trie 树贪心,真是没什么好说的,怎么考试就没想出来呢麻麻地
Code
1 2 3 4 5 6 7 8 9 10
voidquery(int u, int v, int d, int now) { a.siz[u]--; b.siz[v]--; if (cnt >= n) return; if (d < 0) { ans[++cnt] = now; return; } while (a.siz[lsa(u)] && b.siz[lsb(v)]) query(lsa(u), lsb(v), d-1, now); while (a.siz[rsa(u)] && b.siz[rsb(v)]) query(rsa(u), rsb(v), d-1, now); while (a.siz[lsa(u)] && b.siz[rsb(v)]) query(lsa(u), rsb(v), d-1, now + (1 << d)); while (a.siz[rsa(u)] && b.siz[lsb(v)]) query(rsa(u), lsb(v), d-1, now + (1 << d)); }
10.10
T1
有n座山,m只猫和p个工作人员。山从左往右编号为 1∼n,山 i 和 i−1 之间的距离是 d 米。有一天,猫都到山上去玩了∶第 i 只猫会到山 hi 去,并一直玩到时间 t,之后就在那座山等待工作人员来接它。每个工作人员的线路都是从1走到n,并带走沿途任意只在等待的猫。工作人员速度为每单位时间1米,不能在山上停留。安排每个工作人员出发的时间,使所有猫的等待时间总和最小
斜率优化DP
首先可以发现的是每个人一定是选择恰好可以接到一只猫的时候出发
timi 表示想要恰好接到第 i 只猫要从什么时刻出发。先对 timi 排个序,那么在 i 前面的都能被 i 接到
那么 fi,j 表示第 i 个人选第 j 只猫的最小花费。
转移的话,比如说第 i−1 个人选择了第 k 只猫,第 i 个人选择了第 j 只猫,那么这个人可以把 [k+1,j] 的猫全拿下,这些猫一共等待 i=k+1∑jtj−ti 的时间,记录 s 为 t 的前缀和,转移就是
一张图,每个点有点权,小 k 和边之间有颜色,不能通过和自己一样颜色的边,但是小 k 可以中途在任意一个点改变颜色,每次询问从给定的点出发,最多的点权和。
疯狂 bfs 去处理出每个颜色的连通块和相交部分的连通块.
10.17
T1
开始有 n 个物品排成一行,每个物品有一个代价和一个价值 。你可以选择两个相邻的代价之和不超过 k 的物品,将它们从序列中删去,并获得两个物品价值之和的收益。求最大收益
区间 DP , fl,r 表示 l,r 个物品的贡献
1 2 3 4 5 6 7 8 9
for (int len = 2; len <= n; len++) for (int l = 1, r = l + len - 1; r <= n; l++, r++) { for (int _k = l+1; _k < r; _k++) f[l][r] = max(f[l][r], f[l][_k] + f[_k+1][r]);