Rank
有点唐
A. 随机游走
签。
重要的就后两句话。题意由此转化成:到每一个节点时,先后遍历其所有子节点的子树,使得 \(\sum t_i\times w_i\) 最小。
提前 dfs 一遍处理出便利完某棵子树所需要的总时间和子树总价值,容易发现对于两个子节点的子树来说,全部遍历完所需总时间是一样的,所以只需考虑二者的先后关系。设子树 1/2 花费总时间和总价值分别为 \(t_1,t_2\) 和 \(w_1,w_2\),容易发现若先遍历子树 1,则需要“多”消耗的代价为 \(t_1w_2\),反之先遍历子树 2 则代价为 \(t_2w_1\),显然选择代价小的更优,于是我们在求解每个点的子节点子树遍历顺序时按这个排序即可。时间复杂度最坏 \(\mathcal{O(n\log n)}\)。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pil pair<int, ll>
#define P_B(x) push_back(x)
#define pii pair<int, int>
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 5e5 + 5;
int n;
ll sumw[N], sumt[N], w[N << 1], a[N], ans;
int hh[N], to[N << 1], ne[N << 1], cnt;
vector<pil> son[N];
namespace Wisadel
{inline void Wadd(int u, int v, ll val){to[++cnt] = v;w[cnt] = val;ne[cnt] = hh[u];hh[u] = cnt;}inline void Wdfs(int u, int fa){sumw[u] = a[u];for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == fa) continue;son[u].P_B(M_P(v, w[i]));sumt[v] = w[i];Wdfs(v, u);sumw[u] += sumw[v];sumt[u] += sumt[v];}}inline void Wsol(int u, int fa, ll tim){ans += tim * a[u];if(!son[u].size()) return ;sort(son[u].begin(), son[u].end(), [](pil a, pil b){return sumw[a.fi] * sumt[b.fi] > sumw[b.fi] * sumt[a.fi];});for(pil v : son[u]){Wsol(v.fi, u, tim + v.se);tim += sumt[v.fi];}}short main(){freopen("walk.in", "r", stdin), freopen("walk.out", "w", stdout);n = qr;memset(hh, -1, sizeof hh);fo(i, 1, n - 1){int a = qr, b = qr;Wadd(i + 1, a, b), Wadd(a, i + 1, b);}fo(i, 1, n) a[i] = qr;Wdfs(1, 0);Wsol(1, 0, 0);printf("%lld\n", ans);return Ratio;}
}
signed main(){return Wisadel::main();}
B. 分发奖励
也比较签。其实九点左右就打完了,赛时由于调不出来树剖里一个唐错就先放了。
考虑一对存在祖先关系的点,容易发现如果祖先与某些点存在有一样的关系,那么子节点和这些点同样也存在有一样的关系。因此想到 dfs 同时统计答案。对于每个点搜索时统计这个点的贡献点,回溯时撤销这个点的贡献点即可。又因为本题中点和子树是捆绑关系,如果一个点被统计过有贡献,则其子树整体也一定有贡献。所以加入时判断是否被统计过,撤销时只用撤新加入的贡献点即可。
赛时写法大概率是可以被卡的,构造一棵一半是菊花一半是链的树就会爆炸。所以考虑优化,也比较简单,上一棵线段树即可。求出所有点的 dfs 序及其回溯边界,将树拍成序列在线段树上操作,复杂度就是稳定的 \(\mathcal{O(n\log n)}\)。不过不是很想写了所以放原代码。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pil pair<int, ll>
#define P_B(x) push_back(x)
#define pii pair<int, int>
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 5e5 + 5;
int n, m;
int siz[N], son[N], dep[N], fx[N], top[N];
int hh[N], to[N << 1], ne[N << 1], cnt;
int ans[N];
bool vis[N], yz[N];
set<int> st[N];
namespace Wisadel
{inline void Wadd(int u, int v){to[++cnt] = v;ne[cnt] = hh[u];hh[u] = cnt;}inline void Wdfs(int u){siz[u] = 1, dep[u] = dep[fx[u]] + 1;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];fx[v] = u;Wdfs(v);siz[u] += siz[v];if(siz[v] > siz[son[u]]) son[u] = v;}}inline void Wdfs2(int u, int tpf){top[u] = tpf;if(son[u]) Wdfs2(son[u], tpf);for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];if(v == son[u]) continue;Wdfs2(v, v);}}inline int Wq(int u, int v){if(top[u] == top[v]) return dep[u] > dep[v] ? v : u;if(dep[u] < dep[v]) swap(u, v);while(top[u] != top[v]){if(dep[u] <= dep[v]) return -1;if(u == top[u]) u = fx[u];else u = top[u];}return v;}int stk[N << 1], tp;inline int Wshua(int u){if(yz[u]) return 0;yz[u] = 1;stk[++tp] = u;int res = 1;for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];res += Wshua(v);}return res;}inline void Wsol(int u, int now){stk[++tp] = -u;if(st[u].size()) now += Wshua(u);for(int vv : st[u]){if(vis[vv] || yz[vv]) continue;now += Wshua(vv);}ans[u] = now - yz[u];for(int i = hh[u]; i != -1; i = ne[i]){int v = to[i];Wsol(v, now);}while(stk[tp] != -u) yz[stk[tp]] = 0, tp--, now--;tp--;}short main(){freopen("reward.in", "r", stdin), freopen("reward.out", "w", stdout);n = qr, m = qr;memset(hh, -1, sizeof hh);fo(i, 2, n){int a = qr;Wadd(a, i);}Wdfs(1), Wdfs2(1, 1);fo(i, 1, m){int a = qr, b = qr;if(a == b) st[a].insert(a);else{int op = Wq(a, b);if(op == -1) st[a].insert(b), st[b].insert(a);else st[op].insert(op);}}if(st[1].size()){fo(i, 1, n) printf("%d ", n);return Ratio;}Wsol(1, 0);fo(i, 1, n) printf("%d ", ans[i]);return Ratio;}
}
signed main(){return Wisadel::main();}
C. 卡路里
题面自相矛盾懒得说了,线上和本机测评差半秒是什么鬼。
题面就得倒着看,前面说的和要求的是两码事。转换后的题意可以理解为选择一段区间 \([l,r],1\le l\le r\le m\) 内的点,点之间有负边权,取选中的点中 \(n\) 个元素的最大值之和,最大化最大值之和与边权之和。\(\mathcal{O(nm^2)}\) 很好想啊,枚举两点作为左右边界扫一遍就做完了,然后本机跑 \(n=200,m=5000\) 只用 0.6s?怎么算怎么复杂度不对,但是出于信任加了个循环展开直接交了,结果 35pts。
考虑正解。考虑到选定一个区间有哪些项会有贡献,比较显然是其中 \(n\) 个元素分别的最大值所在位置。考虑枚举右端点,首先要在所有左端点上加上到当前点的边权的负贡献,那么对每个元素来说,当前点会产生贡献的左端点一定是从上一个不大于自身的点到当前点,容易想到单调栈维护,此时这一段该元素的取值一定取当前点优,给这些位置删去原贡献加上当前点贡献即可。依次操作这 \(n\) 种元素,最终以当前点为右端点的最大值就是以前面所有点为左端点时贡献的最大值。发现本质上是区间修改和区间最大值的操作,直接上线段树即可。时间复杂度 \(\mathcal{O(nm\log m)}\)。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(int (x) = (y); (x) >= (z); (x)--)
using namespace std;
typedef long long ll;
#define lx ll
inline lx qr()
{char ch = getchar(); lx x = 0, f = 1;for(; ch < '0' || ch > '9'; ch = getchar()) if(ch == '-') f = -1;for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 3) + (x << 1) + (ch ^ 48);return x * f;
}
#undef lx
#define qr qr()
#define fi first
#define se second
#define pil pair<int, ll>
#define P_B(x) push_back(x)
#define pii pair<int, int>
#define M_P(x, y) make_pair(x, y)
const int Ratio = 0;
const int N = 200 + 5, M = 5000 + 5;
int n, m;
ll d[M];
ll a[M][N], v[M << 2], lazy[M << 2], st[N][M], top[N];
namespace Wisadel
{#define ls (rt << 1)#define rs (rt << 1 | 1)#define mid ((l + r) >> 1)inline void Wpushup(int rt){v[rt] = max(v[ls], v[rs]);}inline void Wpushdown(int rt){v[ls] += lazy[rt], v[rs] += lazy[rt];lazy[ls] += lazy[rt], lazy[rs] += lazy[rt];lazy[rt] = 0;}inline void Wupd(int rt, int l, int r, int x, int y, ll k){if(x <= l && r <= y){v[rt] += k, lazy[rt] += k;return ;}if(lazy[rt]) Wpushdown(rt);if(x <= mid) Wupd(ls, l, mid, x, y, k);if(y > mid) Wupd(rs, mid + 1, r, x, y, k);Wpushup(rt);}inline ll Wq(int rt, int l, int r, int x, int y){if(x <= l && r <= y) return v[rt];if(lazy[rt]) Wpushdown(rt);ll res = -2e18;if(x <= mid) res = max(res, Wq(ls, l, mid, x, y));if(y > mid) res = max(res, Wq(rs, mid + 1, r, x, y));return res;}short main(){freopen("calorie.in", "r", stdin), freopen("calorie.out", "w", stdout);n = qr, m = qr;fo(i, 1, m - 1) d[i] = qr;fo(i, 1, m) fo(j, 1, n) a[i][j] = qr;ll ans = -2e18;fo(i, 1, m){if(i != 1) Wupd(1, 1, m, 1, i - 1, -d[i - 1]);fo(j, 1, n){while(top[j] && a[st[j][top[j]]][j] < a[i][j]){Wupd(1, 1, m, st[j][top[j] - 1] + 1, st[j][top[j]], -a[st[j][top[j]]][j]);top[j]--;}Wupd(1, 1, m, st[j][top[j]] + 1, i, a[i][j]);st[j][++top[j]] = i;}ans = max(ans, Wq(1, 1, m, 1, i));}printf("%lld\n", ans);return Ratio;}
}
signed main(){return Wisadel::main();}
D. 传话游戏
不会。12pts 暴力挂了。
正解用到拉插。
末
倒数第三场模拟赛,又是全真模拟,换了座。
开场半小时切签,然后半小时会 T2,出现唐错果断开 T3 换脑子,然后发现暴力可过,然后回来再看 T2 一眼找出错。此时只过了 3h,当时以为顺飞了,然后 all in T4 上,1.5h 拿到 0pts 的高分。
感觉策略是出了一点问题的,尤其是 T4,不应该在看一眼不会暴力的情况下不留检查时间的。哪怕留出半个小时给 T3 优化也能打出来,就不至于挂到 35pts 了。然后就是树剖又又写挂,这次挂的是判祖先关系,不能直接跳 fa[top[u]]
,可能会出现 v=top[u]
然后直接跳过了的情况导致判错。复杂度也不对,得亏是数据没有狠卡,想想写个优化上去也耽误不了多长时间,只是没有心塌下去将程序不断优化。
这两天要复习的,字符串(哈希和 kmp 感觉要不是最近光做到就忘完了),线段树合并(学的时候几乎全是硬焯),平衡树(真考吗,不过起码看看 fhq),tarjan 相关(不熟,但怕真考到),最短路(忘),差分约束,矩阵,树状数组扩展,exgcd。。。byd 怎么这么多
但是说到底,考前心态放平最重要,考场上尽力就好,不要因为没有快切签到而焦急,也不能因为切得太快而放飞自我,塌下心来最重要,平和,坦然。
下午冒险又打了会篮球,怕高强度所以打了会半场,后来人越来越多就扩成全场了,比前几次少了单带和快攻,多了 n 次投不中的篮(
天气越来越冷了,不知道燕大机房有没有空调。
完结撒花~
?