『模拟赛』多校A层冲刺NOIP2024模拟赛09
Rank
还行
A. 排列最小生成树 (pmst)
签,有点可惜。
考虑连 \(i\) 与 \(i+1\) 时,所有边边权都是小于 \(n\) 的,因此我们只考虑边权小于 \(n\) 的边即可。因为边权为 \(|p_i-p_j|\times|i-j|\),所以只考虑 \(|p_i-p_j|\lt \sqrt{n}\) 和 \(|i-j|\lt \sqrt{n}\) 的情况,每个点只用连 \(\sqrt{n}\) 条边,复杂度为 \(\mathcal{O(n\sqrt{n}\log n)}\)。桶记录每一条边可以优化掉排序,并查集按秩合并优化,然后就过了。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 pii pair<int, int>
#define P_B(x) push_back(x)
#define M_P(a, b) make_pair(a, b)
#define fi first
#define se second
const int Ratio = 0;
const int N = 5e4 + 5;
int n, cnt;
int a[N], fx[N], p[N], siz[N];
ll ans;
vector<pii> e[N];
namespace Wisadel
{
inline int Wfind(int x)
{
if(x == fx[x]) return x;
return fx[x] = Wfind(fx[x]);
}
short main()
{
freopen("pmst.in", "r", stdin) , freopen("pmst.out", "w", stdout);
n = qr;
fo(i, 1, n) a[i] = qr, fx[i] = i, p[a[i]] = i, siz[i] = 1;
for(int i = 1; i * i <= n; i++) fo(j, 1, n - i)
{
int zc = 1ll * abs(a[j + i] - a[j]) * i;
if(zc < n) e[zc].P_B(M_P(j, j + i));
}
for(int i = 1; i * i <= n; i++) fo(j, 1, n - i)
{
int zc = 1ll * abs(p[j] - p[j + i]) * i, zd = 1ll * abs(p[j] - p[j + i]);
if(1ll * zd * zd > 1ll * n && zc < n) e[zc].P_B(M_P(p[j], p[j + i]));
}
fo(i, 1, n) if(e[i].size())
{
for(pii j : e[i])
{
int _ = Wfind(j.fi), __ = Wfind(j.se);
if(_ != __)
{
if(siz[_] >= siz[__])
{
siz[_] += siz[__];
fx[__] = _;
}
else
{
siz[__] += siz[_];
fx[_] = __;
}
ans += i;
}
}
}
printf("%lld\n", ans);
return Ratio;
}
}
signed main(){return Wisadel::main();}
B. 卡牌游戏 (cardgame)
真正的签,不知道跟 T2 换位置有什么深意。
发现卡牌数量互质情况下每一对牌都会 pk 一次,当不互质时,会重复 \(gcd(n,m)\) 次完全一样的对局,此时对于一张牌显然不会与其余所有牌 pk,手模发现它只会和从它开始与它间隔 \(gcd(n,m) - 1\) 个的牌 pk,然后 \(\mathcal{O(n)}\) 跑一遍就做完了。
为了方便,我分成了 \(n\le m\) 和 \(n\gt m\) 两种情况做。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 pii pair<int, int>
#define M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 1e5 + 5, M = 5e5;
int n, m;
int a[N], b[N];
ll win, lose, ping;
vector<int> sum[N];
namespace Wisadel
{
short main()
{
freopen("cardgame.in", "r", stdin) , freopen("cardgame.out", "w", stdout);
n = qr, m = qr;
fo(i, 1, n) a[i] = qr;
fo(i, 1, m) b[i] = qr;
int gcd = __gcd(n, m);
if(n <= m)
{
fo(len, 1, gcd)
{
for(int i = len; i <= m; i += gcd)
sum[len].P_B(b[i]);
sort(sum[len].begin(), sum[len].end());
}
fo(i, 1, n)
{
int zu = i % gcd; if(zu == 0) zu = gcd;
int zc = lower_bound(sum[zu].begin(), sum[zu].end(), a[i]) - sum[zu].begin();
zc++;
win += 1ll * (zc - 1);
int zd = upper_bound(sum[zu].begin(), sum[zu].end(), a[i]) - sum[zu].begin();
zd++;
ping += 1ll * (zd - zc);
lose += 1ll * (m / gcd - zd + 1);
}
win *= gcd, ping *= gcd, lose *= gcd;
}
else
{
fo(len, 1, gcd)
{
for(int i = len; i <= n; i += gcd)
sum[len].P_B(a[i]);
sort(sum[len].begin(), sum[len].end());
}
fo(i, 1, m)
{
int zu = i % gcd; if(zu == 0) zu = gcd;
int zc = lower_bound(sum[zu].begin(), sum[zu].end(), b[i]) - sum[zu].begin();
zc++;
lose += 1ll * (zc - 1);
int zd = upper_bound(sum[zu].begin(), sum[zu].end(), b[i]) - sum[zu].begin();
zd++;
ping += 1ll * (zd - zc);
win += 1ll * (n / gcd - zd + 1);
}
win *= gcd, ping *= gcd, lose *= gcd;
}
printf("%lld\n%lld\n%lld\n", win, lose, ping);
return Ratio;
}
}
signed main(){return Wisadel::main();}
C. 比特跳跃 (jump)
又一道魔改最短路。
-
\(s=1\) 时,发现除了最大点为 \(11\cdots 1_{(2)}\) 之外的情况,都能找到一个点与一个数的位与值为 0,所以特判 \(n\) 即可。
-
\(s=2\) 时,发现对三点一定有 \(a\oplus b+b\oplus c=a\oplus c\),因此我们可以省掉 \(a\rightarrow c\) 这条边,即每个点只与与其一位不一样的点连边即可,每个点只有 \(\log n\) 条。
-
\(s=3\) 时,发现 \((a|b) + (b|c) \ge (a|c)\)。对于点 \(i\) 边权最少是 \(i\)。我们考虑给每个点多映射一个虚点,点到虚点的边权为 \(k\times i\),虚点到实点边权为 \(0\),然后在虚点上连边,同样只与与其一位不一样的点连边,若该点该位为 1 那么没有额外边权,否则边权为 \(k\times 2^i\) (\(i\) 为位数)。这样连只多了 \(n\times(2+\log n)\) 条边,大概是最优的连法。
然后对 \(s\neq 1\) 的情况跑一遍 dijkstra 就做完了,是目前最优解。
对于 \(s=2\) 中的 \(a\oplus b+b\oplus c=a\oplus c\) 指任意三点存在这样的关系,是一种组合而不是排列。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 pii pair<int, int>
#define M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 2e5 + 5, M = 5e5;
int n, m, s, k;
int hh[N], to[N << 4], ne[N << 4], cnt;
ll dis[N], w[N << 4];
bool yz[N];
struct rmm
{
ll dis; int u;
bool operator < (const rmm &a) const {return a.dis < dis;}
};
namespace W
{
void Wadd(int u, int v, ll val)
{
to[++cnt] = v;
w[cnt] = val;
ne[cnt] = hh[u];
hh[u] = cnt;
}
void Wdij(int x)
{
priority_queue<rmm> q;
memset(dis, 0x3f, sizeof dis);
dis[x] = 0;
q.push({0, x});
while(q.size())
{
int u = q.top().u; q.pop();
if(yz[u]) continue;
yz[u] = 1;
for(int i = hh[u]; i != -1; i = ne[i])
{
int v = to[i];
if(dis[v] > dis[u] + w[i])
{
dis[v] = dis[u] + w[i];
q.push({dis[v], v});
}
}
}
}
}
namespace W1
{
short main()
{
fill(dis + 1, dis + 1 + n, 0);
int zc = 1;
while(zc <= n) zc *= 2;
if(n == zc - 1)
{
dis[n] = k;
for(int i = hh[n]; i != -1; i = ne[i])
dis[n] = min(dis[n], w[i]);
}
else dis[n] = 0;
fo(i, 2, n) printf("%lld ", dis[i]); puts("");
return Ratio;
}
}
namespace W2
{
short main()
{
fo(i, 0, n) fo(j, 0, 16)
{
int v = i ^ (1 << j);
ll val = 1ll * k * (1 << j);
if(v <= n) W::Wadd(i, v, val);
}
W::Wdij(1);
fo(i, 2, n) printf("%lld ", dis[i]); puts("");
return Ratio;
}
}
namespace W3
{
short main()
{
fo(i, 0, n)
{
fo(j, 0, 16)
{
int v = i ^ (1 << j);
if(v <= n)
{
ll val = ((i >> j) & 1) ? 0 : 1ll * k * (1 << j);
W::Wadd(i + n + 1, v + n + 1, val);
}
}
W::Wadd(i, i + n + 1, 1ll * k * i);
W::Wadd(i + n + 1, i, 0);
}
W::Wdij(1);
fo(i, 2, n) printf("%lld ", dis[i]); puts("");
return Ratio;
}
}
namespace Wisadel
{
short main()
{
freopen("jump.in", "r", stdin) , freopen("jump.out", "w", stdout);
n = qr, m = qr, s = qr, k = qr;
memset(hh, -1, sizeof hh);
fo(i, 1, m)
{
int a = qr, b = qr, c = qr;
W::Wadd(a, b, c), W::Wadd(b, a, c);
}
if(s == 1) return W1::main();
if(s == 2) return W2::main();
if(s == 3) return W3::main();
return Ratio;
}
}
signed main(){return Wisadel::main();}
D. 区间 (interval)
据说是线段树区间历史和板子。
Upd:更像是单调栈板子。
将询问按右端点升序离线,发现第一个条件相当于是 \(a_l\) 是 \(a_i(i\in[l,r-1])\) 的最大值,维护一个单调递减的单调栈,然后二分出满足条件二的最大区间,区间中的点贡献加 1,然后删除栈内元素记得加 tag 表示该点不再有贡献,线段树维护即可。
点击查看代码
#include<bits/stdc++.h>
#define fo(x, y, z) for(register int (x) = (y); (x) <= (z); (x)++)
#define fu(x, y, z) for(register 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 pii pair<int, int>
#define M_P(a, b) make_pair(a, b)
#define fi first
#define se second
#define P_B(x) push_back(x)
const int Ratio = 0;
const int N = 1e6 + 5, M = 5e5;
int n, m, top;
int a[N], b[N];
struct rmm{int id, l, r;} q[N];
pii st[N];
int tag[N << 2];
ll sum[N << 2], lazy[N << 2], ans[N];
namespace Wisadel
{
#define ls (rt << 1)
#define rs (rt << 1 | 1)
#define mid ((l + r) >> 1)
void Wpushdown(int rt, int l, int r)
{
sum[ls] += lazy[rt] * (mid - l + 1 - tag[ls]);
sum[rs] += lazy[rt] * (r - mid - tag[rs]);
lazy[ls] += lazy[rt], lazy[rs] += lazy[rt];
lazy[rt] = 0;
}
void Wadd(int rt, int l, int r, int x, int y)
{
if(x <= l && r <= y)
{
sum[rt] += r - l + 1 - tag[rt], lazy[rt]++;
return ;
}
if(lazy[rt]) Wpushdown(rt, l, r);
if(x <= mid) Wadd(ls, l, mid, x, y);
if(y > mid) Wadd(rs, mid + 1, r, x, y);
sum[rt] = sum[ls] + sum[rs];
}
void Wdel(int rt, int l, int r, int x)
{
if(l == r)
{
tag[rt]++;
return ;
}
if(lazy[rt]) Wpushdown(rt, l, r);
if(x <= mid) Wdel(ls, l, mid, x);
else Wdel(rs, mid + 1, r, x);
tag[rt] = tag[ls] + tag[rs];
}
ll Wq(int rt, int l, int r, int x, int y)
{
if(x <= l && r <= y) return sum[rt];
if(lazy[rt]) Wpushdown(rt, l, r);
ll res = 0;
if(x <= mid) res += Wq(ls, l, mid, x, y);
if(y > mid) res += Wq(rs, mid + 1, r, x, y);
return res;
}
short main()
{
freopen("interval.in", "r", stdin) , freopen("interval.out", "w", stdout);
n = qr;
fo(i, 1, n) a[i] = qr;
fo(i, 2, n) b[i] = qr;
m = qr;
fo(i, 1, m) q[i].l = qr, q[i].r = qr, q[i].id = i;
sort(q + 1, q + 1 + m, [](rmm &A, rmm &B){return A.r < B.r;});
int j = 1;
fo(i, 1, n)
{
int l = 1, r = top, zc = 0;
while(l <= r)
if(st[mid].se < b[i]) zc = mid, r = mid - 1;
else l = mid + 1;
if(zc) Wadd(1, 1, n, st[zc].fi, i - 1);
while(j <= m && q[j].r == i) ans[q[j].id] = Wq(1, 1, n, q[j].l, i), j++;
while(top && st[top].se <= a[i]) Wdel(1, 1, n, st[top].fi), top--;
st[++top] = M_P(i, a[i]);
}
fo(i, 1, m) printf("%lld\n", ans[i]);
return Ratio;
}
}
signed main(){return Wisadel::main();}
末
又双叒叕回去整改了,打的不烂。只是 T3 好多性质没切,有点恼。
赛后一分钟得知 T1 建 110 条边就过了,大样例需要建 99 条,本地跑 0.95s 多,念在 OJ 速度不如本地,只建了 100 条。
下午信友队晚上 STAOI,所以现在才发。
Upd:这两场打的都还行。
尝试着跳过今天,使得 CSP 赶在我状态好的时候。
下午改 T4,改信友队题。
完结撒花~
现找的。