『模拟赛』【MX-S7】梦熊NOIP2024模拟赛3
Rank
因为提前知道打不完就没打暴力
A. 【MX-S7-T1】「SMOI-R2」Happy Card
link。
签。比较好想到炸弹等价于三带一,因此本质只有三种出牌类型。并且三带一一次能出掉四张牌,显然优先打三带一是很优的。所以我们处理出三带的个数以及剩下零牌中对子的个数,然后分讨。
-
如果三带的个数少于零牌数,此时策略比较好想:三带全部打出,多余的零牌中尽量打对子。
-
如果二者数量相等,那么一直打三带一就结束了。
-
如果三带的个数多于零牌数,我们就要考虑将零牌带完后如何将三带拆开打出。容易发现四个三带可以通过拆掉一个来在三轮内打出,这样依然是最优的一次出四张牌的出法,因此优先选。那么之后只可能剩 \(0,1,2,3\) 个三带,简单分讨即可。
那么就做完了,整体思路不难,解题瓶颈在于将炸弹拆成三带一的形式。时间复杂度 \(\mathcal{O(n)}\)。
点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int Ratio = 0;
namespace Wisadel
{
short main()
{
int T, n, x; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
ll sum3 = 0, sum2 = 0, sum1 = 0, ans = 0;
for(int i = 1; i <= n; i++)
{
scanf("%d", &x);
sum3 += x / 3;
sum1 += x % 3;
if(x % 3 == 2) sum2++;
}
if(sum3 < sum1)
{ // 三带不完,全带,剩下考虑对
ll zc = sum1 - sum3; // 剩下 zc 张
ll tim = 0; // 出对子数量
tim = min(sum2, zc / 2);
zc -= tim * 2;
ans = sum3 + zc + tim;
}
else if(sum3 == sum1) ans = sum3;
else
{ // 三带多了,拆多的三带
ll zc = sum3 - sum1;
ll tim = zc / 4; // 四个三带三次可出完
if(zc % 4 == 1) ans = 2; // 1 + 2
else if(zc % 4 == 2) ans = 2; // 3.1 + 2
else if(zc % 4 == 3) ans = 3; // (3.1) * 2 + 1
ans += tim * 3 + sum1;
}
printf("%lld\n", ans);
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
B. 【MX-S7-T2】「SMOI-R2」Speaker
签。结论比较一眼,实现比较男绷。
容易想到无论起终点如何每个点最优的中转方案是确定的,因此可以设 \(f_i\) 为点 \(i\) 的最优方案的贡献,比较好想 \(f_i=a_x-2\times d(i,x)\),考虑简单换根 dp 解决。假设存在一父子关系点对 \(x,y\),设存在一点 \(z\in subtree_y\),容易发现若 \(z\) 不是 \(y\) 的最优中转点,则其一定不是 \(x\) 的最优中转点。也就是说,每个点的最优中转点的候选只有相邻点的最优中转点或自身。那么先以 \(1\) 为根正常 dp 跑出 \(f_1\) 的值,然后再倒着转移一遍,即用父亲更新儿子,就可以在 \(\mathcal{O(n)}\) 的时间内求出每个点的 \(f\) 值。
此时答案已经被拆分成了 \(a_{x_i}+a_{y_i}-d(x_i,y_i)+\max_{u\in x_i\rightarrow y_i} f_i\),其中 \(x_i\rightarrow y_i\) 表示 \(x_i\) 到 \(y_i\) 的路径。发现前三项都是固定的,最后一项只需要找到树上路径最大值即可,考虑树剖维护即可。
然后就做完了,时间复杂度 \(\mathcal{O(n\log^2 n)}\)。
注意有个易错小细节,树剖求 lca 和统计答案时用的 \(deep\) 数组不要和树上差分的 \(dis\) 数组搞混!猜猜谁因为这个挂了 12pts。
点击查看代码
#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;
typedef long double ld;
typedef unsigned long long ull;
#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 pll pair<ll, ll>
#define pii pair<int, int>
#define ppp pair<pii, pii>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 2e5 + 5;
const int mod = 998244353;
int n, m;
int a[N];
int dfn[N], dt, fx[N], top[N], son[N], siz[N], dep[N];
int hh[N], to[N << 1], ne[N << 1], cnt;
ll w[N << 1], dis[N], wt[N], f[N], v[N << 2];
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)
{
fx[u] = fa, siz[u] = 1, dep[u] = dep[fa] + 1;
for(int i = hh[u]; i != -1; i = ne[i])
{
int v = to[i];
if(v == fa) continue;
dis[v] = dis[u] + w[i];
Wdfs(v, u);
siz[u] += siz[v];
if(siz[v] > siz[son[u]]) son[u] = v;
}
}
inline void Wdfs2(int u, int tpf)
{
dfn[u] = ++dt, wt[dt] = f[u], 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 == fx[u] || v == son[u]) continue;
Wdfs2(v, v);
}
}
inline void Wdfs3(int u, int fa)
{
f[u] = a[u];
for(int i = hh[u]; i != -1; i = ne[i])
{
int v = to[i];
if(v == fa) continue;
Wdfs3(v, u);
f[u] = max(f[u], f[v] - 2 * w[i]);
}
}
inline void Wdfs4(int u, int fa)
{
for(int i = hh[u]; i != -1; i = ne[i])
{
int v = to[i];
if(v == fa) continue;
f[v] = max(f[v], f[u] - 2 * w[i]);
Wdfs4(v, u);
}
}
#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 Wbuild(int rt, int l, int r)
{
if(l == r)
{
v[rt] = wt[l];
return ;
}
Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
Wpushup(rt);
}
inline ll Wq(int rt, int l, int r, int x, int y)
{
if(x <= l && r <= y) return v[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;
}
#undef ls
#undef rs
#undef mid
inline int Wlca(int x, int y)
{
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x, y);
x = fx[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
return x;
}
inline ll Wque(int x, int y)
{
ll res = -2e18;
while(top[x] != top[y])
{
if(dep[top[x]] < dep[top[y]]) swap(x, y);
res = max(res, Wq(1, 1, n, dfn[top[x]], dfn[x]));
x = fx[top[x]];
}
if(dep[x] > dep[y]) swap(x, y);
res = max(res, Wq(1, 1, n, dfn[x], dfn[y]));
return res;
}
inline ll Wd(int u, int v){return dis[u] + dis[v] - 2 * dis[Wlca(u, v)];}
short main()
{
n = qr, m = qr;
memset(hh, -1, sizeof hh);
fo(i, 1, n) a[i] = qr;
fo(i, 1, n - 1)
{
int a = qr, b = qr; ll c = qr;
Wadd(a, b, c), Wadd(b, a, c);
}
Wdfs(1, 0), Wdfs3(1, 0), Wdfs4(1, 0), Wdfs2(1, 1);
Wbuild(1, 1, n);
fo(i, 1, m)
{
int x = qr, y = qr;
ll ans = a[x] + a[y] - Wd(x, y) + Wque(x, y);
printf("%lld\n", ans);
}
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
C. 【MX-S7-T3】「SMOI-R2」Monotonic Queue
找性质题。找到性质之后就是简单线段树优化 dp。
据 Abnormal123 赛时观察 1h 的结论:我们只选择长度为 1 的区间即可达到最优解。证明主要从能拿到可能的贡献区间和能不选可以避免的负贡献区间两方面考虑。
按小 L 的操作实现,我们先移动右端点,然后再移动左端点。考虑一段什么样的正贡献区间是可拿到的,如果该区间的最大值小于右端点的下一个值,那么显然是可以在右端点移动过程中获得所有贡献的。我们直接选择一个位于该区间右端点的长度为 1 的区间即可。
考虑什么样的负贡献区间可以避免,如果该区间严格单调不下降,那么所有元素都会存在队列里,可以通过移动左端点来 pop_back
掉它们。此时在区间右端点选择一个长度为 1 的区间可以实现。
至于那些不得不吃的负贡献,我们直接忽略掉即可,因为结果是不变的。
那么之后就可以用 dp 求解了。设 \(f_i\) 为钦定必须吃到 \(i\) 的贡献,即选 \([i,i]\) 这一区间的最大值,容易写出转移式:\(f_i=\max_{j=0}^{i-1}f_j+val_{[j,i]}\)。\(val_{[j,i]}\) 表示 \([j,i]\) 这段区间产生的贡献。
考虑这个 \(val\) 怎么求。我们发现一个点 \(i\) 若产生贡献,那么一定存在某一次右端点移动包含了 \(i\) 和它之后第一个大于它的数。我们记这个第一个大于 \(a_i\) 的数的下标为 \(nxt_i\),这个求解的方法很多,树状数组或者单调队列均可。容易发现这个 \(nxt\) 值是单调不降的,所以我们将所有 \(nxt\) 值为 \(nxt_i\) 的下标装入 \(pre_{nxt_i}\) 中,每当遍历到一个点时,将 \(pre_{i}\) 中所有值取出计算贡献即可。
任意包含 \([i,nxt_i]\) 这段区间的区间都会吃到 \(i\) 的贡献,这等价于对一个前缀做了一个区间加的操作。所以我们直接上线段树优化,就可以将转移的复杂度优化至 \(\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;
typedef long double ld;
typedef unsigned long long ull;
#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 pll pair<ll, ll>
#define pii pair<int, int>
#define ppp pair<pii, pii>
#define fi first
#define se second
#define M_P(x, y) make_pair(x, y)
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 5e5 + 5;
const int mod = 998244353;
int n, m;
int a[N], c[N], t[N];
ll v[N << 2], lazy[N << 2], f[N];
vector<int> pre[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 Wbuild(int rt, int l, int r)
{
if(l == r)
{
v[rt] = f[l];
return ;
}
Wbuild(ls, l, mid), Wbuild(rs, mid + 1, r);
Wpushup(rt);
}
inline void Wupd(int rt, int l, int r, int x, ll val)
{
if(l == r) return v[rt] = val, void();
if(lazy[rt]) Wpushdown(rt);
if(x <= mid) Wupd(ls, l, mid, x, val);
else Wupd(rs, mid + 1, r, x, val);
Wpushup(rt);
}
inline void Wupd(int rt, int l, int r, int x, int y, ll val)
{
if(x <= l && r <= y)
{
v[rt] += val, lazy[rt] += val;
return ;
}
if(lazy[rt]) Wpushdown(rt);
if(x <= mid) Wupd(ls, l, mid, x, y, val);
if(y > mid) Wupd(rs, mid + 1, r, x, y, val);
Wpushup(rt);
}
inline void Tm(int x, int v){for(; x <= n; x += (x & -x)) t[x] = min(t[x], v);}
inline int Tq(int x){int res = 2e9; for(; x; x -= (x & -x)) res = min(res, t[x]); return res;}
short main()
{
n = qr;
fo(i, 1, n) c[i] = qr;
fo(i, 1, n) a[i] = qr, t[i] = 2e9, f[i] = -2e18;
fu(i, n, 1)
{
int zc = Tq(n - a[i] + 1);
if(zc <= n) pre[zc].P_B(i);
Tm(n - a[i] + 1, i);
}
Wbuild(1, 0, n);
ll ans = -2e18;
fo(i, 1, n)
{
for(auto j : pre[i])
Wupd(1, 0, n, 0, j, c[j]);
ans = max(ans, v[1]);
Wupd(1, 0, n, i, v[1]);
}
printf("%lld\n", ans);
return Ratio;
}
}
signed main(){return Wisadel::main();}
// All talk and never answer
D. 【MX-S7-T4】「SMOI-R2」XA-Game
还不会。
末
T2 唐错太男绷了,几乎是硬控一整场,每次写树剖都会在一些奇奇怪怪的地方出错(
因为是提前知道打不完,所以算是没好好打?正常调一道题感觉在任何稍微正式的比赛中都是不应该的(除非就剩一道题。
鼻子比想象中的不给力,眼镜戴着戴着就滑下去了,然后就压到肿的地方,好在已经结痂了。
只有 5 天了,加把劲!
- 小剧场
快吃饭时
消费股:有没有这个想中午不睡觉把比赛打完的?
停顿了一会,一些同学举起了手
消费股:有这种想法的赶紧撤回啊,这是不可能的。
🤣🤣🤣