NOIP模拟4
A. 树上排列
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
\(\Huge {读题}\)
读错题浪费 \(1h\) ,思维陷入数据结构直接维护,然后啥也不会
这种无关顺序的考虑 \(hash\), 类似 \(CSPST3\) 随机权即可
或者维护最大最小值平方和立方和啥的
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 255000;
mt19937 rd((ull)(new char) * (ull) (new char));
int sint(){return uniform_int_distribution<>(1, INT_MAX)(rd);}
vector<int>g[maxn];
int n, q, a[maxn], val[maxn];
ll sval[maxn];
int siz[maxn], fa[maxn], dep[maxn], top[maxn], son[maxn];
void dfs1(int x){
siz[x] = 1;
for(int v : g[x]){
if(v == fa[x])continue;
dep[v] = dep[x] + 1; fa[v] = x;
dfs1(v);
siz[x] += siz[v];
son[x] = siz[son[x]] > siz[v] ? son[x] : v;
}
}
int dfn[maxn], id[maxn], tim;
void dfs2(int x, int tp){
top[x] = tp; dfn[x] = ++tim; id[tim] = x;
if(son[x])dfs2(son[x], tp);
for(int v : g[x]){
if(v == fa[x] || v == son[x])continue;
dfs2(v, v);
}
}
struct seg{
ll t[maxn << 2 | 1];
void push_up(int x){t[x] = t[x << 1] + t[x << 1 | 1];}
void built(int x, int l, int r){
if(l == r){
t[x] = a[id[l]];
return;
}
int mid = (l + r) >> 1;
built(x << 1, l, mid);
built(x << 1 | 1, mid + 1, r);
push_up(x);
}
void modify(int x, int l, int r, int pos, int val){
if(l == r){t[x] = val; return;}
int mid = (l + r) >> 1;
if(pos <= mid)modify(x << 1, l, mid, pos, val);
else modify(x << 1 | 1,mid + 1, r, pos, val);
push_up(x);
}
ll query(int x, int l, int r, int L, int R){
if(L > R)return 0;
if(L <= l && r <= R)return t[x];
int mid = (l + r) >> 1; ll ans = 0;
if(L <= mid)ans += query(x << 1, l, mid, L, R);
if(R > mid)ans += query(x << 1 | 1, mid + 1, r, L, R);
return ans;
}
}t;
bool query(int u, int v){
int num = dep[u] + dep[v];
ll sum = 0;
while(top[u] != top[v]){
if(dep[top[u]] < dep[top[v]])swap(u, v);
sum += t.query(1, 1, n, dfn[top[u]], dfn[u]);
u = fa[top[u]];
}
if(dep[u] < dep[v])swap(u, v);
sum += t.query(1, 1, n, dfn[v], dfn[u]);
num -= dep[v]; num -= dep[v] - 1;
return sum == sval[num];
}
void sol(){
n = read(), q = read();
for(int i = 1; i <= n; ++i)a[i] = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
g[u].push_back(v); g[v].push_back(u);
}
for(int i = 1; i <= n; ++i)val[i] = sint();
for(int i = 1; i <= n; ++i)a[i] = val[a[i]];
for(int i = 1; i <= n; ++i)sval[i] = sval[i - 1] + val[i];
dep[1] = 1; dfs1(1); tim = 0;dfs2(1, 1);
t.built(1, 1, n);
for(int i = 1; i <= q; ++i){
int op = read(), l = read(), r = read();
if(op & 1){
if(query(l, r))printf("Yes\n");
else printf("No\n");
}else{ a[l] = val[r]; t.modify(1, 1, n, dfn[l], a[l]);}
}
for(int i = 1; i <= n; ++i)g[i].clear(), son[i] = fa[i] = siz[i] = 0;
}
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
int t = read(); for(int i = 1; i <= t; ++i)sol();
return 0;
}
B. 连任
原题,线段树分治板子吧
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
typedef pair<int, int> pii;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 100005;
const int mod = 1e9 + 7;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1)ans = 1ll * ans * x % mod;
return ans;
}
int inv[maxn];
int n, m, tot;
int l[maxn], r[maxn];
struct edge{int u, v;}e[maxn];
map<pii, int>mp;
int f[maxn], siz[maxn];
int fa(int x){return f[x] == x ? x : fa(f[x]);}
struct seg{
vector<int>t[maxn << 2 | 1];
void modify(int x, int l, int r, int L, int R, int val){
if(L > R)return;
if(L <= l && r <= R){
t[x].push_back(val);
return;
}
int mid = (l + r) >> 1;
if(L <= mid)modify(x << 1, l, mid, L, R, val);
if(R > mid)modify(x << 1 | 1, mid + 1, r, L, R, val);
}
struct node{int tim, y;};
vector<node>del;
void add(int x, int &ans){
for(int id : t[x]){
int u = e[id].u, v = e[id].v;
u = fa(u); v = fa(v);
if(u == v)continue;
if(siz[u] < siz[v])swap(u, v);
ans = 1ll * ans * inv[siz[u]] % mod * inv[siz[v]] % mod;
siz[u] += siz[v];
ans = 1ll * ans * siz[u] % mod;
f[v] = u; del.push_back({x, v});
}
}
void cut(int x){
while(del.size()){
if(del.back().tim != x)return;
int v = del.back().y; del.pop_back();
int u = f[v]; siz[u] -= siz[v];
f[v] = v;
}
}
void solve(int x, int l, int r, int ans){
add(x, ans);
if(l == r){
cut(x);
printf("%d\n",ans);
return;
}
int mid = (l + r) >> 1;
solve(x << 1, l, mid, ans);
solve(x << 1 | 1, mid + 1, r, ans);
cut(x);
}
}t;
int main(){
freopen("b.in","r",stdin);
freopen("b.out","w",stdout);
n = read(), m = read();
for(int i = 1; i <= m; ++i){
int op = read(), u = read(), v = read();
if(u > v)swap(u, v);
if(op & 1)e[++tot] = {u, v}, mp[pii(u, v)] = tot, l[tot] = i;
else r[mp[pii(u, v)]] = i - 1;
}
for(int i = 1; i <= n; ++i)inv[i] = qpow(i, mod - 2);
for(int i = 1; i <= tot; ++i)t.modify(1, 1, m, l[i], r[i] ? r[i] : m, i);
for(int i = 1; i <= n; ++i)f[i] = i, siz[i] = 1;
t.solve(1, 1, m, 1);
return 0;
}
C. 排列
根据期望的线性性?拆成点对的贡献,加起来为答案
(其实是把贡献的概率求和)
然后是个裸的 \(CDQ\)
因为我写的很丑常数巨大,后两个 \(subtask\) 都没过。。。
其实能卡常过
但是也能转二维偏序
首先有关 \(-1\) 的直接转二维
考虑处理全是数字的情况
三位偏序设为 \((a, b, c)\)
那么两个点对要么构成三组偏序,要么构成两组偏序
对 \((a, b) (b, c)(a, c)\) 求出偏序数量,三组偏序统计了 \(3\) 次, 两组统计 \(1\) 次
\((cnt - n*(n - 1)/ 2)/2\)即可得到答案
懒得打正解,所以下面是\(CDQ\)
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; bool f = 0; char c = getchar();
while(!isdigit(c)){f = c == '-'; c = getchar();}
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return f ? -x : x;
}
const int maxn = 1000005;
const int mod = 998244353;
int qpow(int x, int y){
int ans = 1;
for(; y; y >>= 1, x = 1ll * x * x % mod)if(y & 1) ans = 1ll * x * ans % mod;
return ans;
}
struct node{
int a, b;
friend bool operator < (const node &x, const node &y){return x.a < y.a;}
}d[maxn];
int n, a[maxn], b[maxn], tot;
ll ans;
int vis[maxn], inv2, invt;
bool cmp(const node &x, const node &y){return x.id < y.id;}
struct BIT{
int t[maxn];
int lowbit(int x){return x & -x;}
void add(int x, int val){
while(x <= n){
t[x] += val;
x += lowbit(x);
}
}
int query(int x){
int ans = 0;
while(x){
ans += t[x];
x -= lowbit(x);
}
return ans;
}
}t;
void solve(int l, int r){
if(l >= r)return;
int mid = (l + r) >> 1;
solve(l, mid); solve(mid + 1, r);
int p = l - 1;
for(int i = mid + 1; i <= r; ++i){
while(p < mid && d[p + 1].a < d[i].a){++p; if(d[p].b > 0)t.add(d[p].b, 1);}
if(d[i].b > 0)ans = (ans + t.query(d[i].b)) % mod;
}
for(int i = l; i <= p; ++i)if(d[i].b > 0)t.add(d[i].b, -1);
inplace_merge(d+l,d+mid+1,d+r+1);
}
int main(){
freopen("c.in","r",stdin);
freopen("c.out","w",stdout);
n = read(); inv2 = qpow(2, mod - 2);
for(int i = 1; i <= n; ++i)d[i].a = read(), d[i].id = i;
for(int i = 1; i <= n; ++i){d[i].b = read(); if(d[i].b > 0)vis[d[i].b] = 1;}
for(int i = 1; i <= n; ++i)vis[i] += vis[i - 1];
tot = n - vis[n]; invt = qpow(tot, mod - 2);
for(int i = 1; i <= n; ++i)
if(d[i].b == -1){ans = (ans + 1ll * inv2 * t.query(d[i].a) % mod) % mod; t.add(d[i].a, 1);}
else ans = (ans + 1ll * (d[i].b - vis[d[i].b]) * invt % mod * t.query(d[i].a) % mod);
for(int i = 1; i <= n; ++i)t.t[i] = 0;
for(int i = n; i >= 1; --i)
if(d[i].b == -1)t.add(d[i].a, 1);
else ans = (ans + 1ll * (tot - d[i].b + vis[d[i].b]) * invt % mod * (t.query(n) - t.query(d[i].a)) % mod);
for(int i = 1; i <= n; ++i)t.t[i] = 0;
solve(1, n);
printf("%lld\n",ans);
return 0;
}
D. 追逐
先考虑两人的策略,假设 \(s\) 为根
后手一定会找机会进入一棵不含 \(t\) 的子树,以最大化操作次数
此时,先手的最优策略是断掉后手能走的最优子树,令其只能走次优子树
当后手被限制在一个点走不动时,先手断掉当前点到 \(t\) 路径上的其他临接边,然后一条条清障碍即可
先考虑如果知道后手进入哪棵子树需要多少操作数
设 \(f_x\) 表示走到 \(x\) 子树内最少需要多少操作走回来
那么 \(f_x = 次大f_v +1\)
现在考虑后手如何选择子树,他可能从 \(s\) 直接选一棵,还可能走一段再选,而走一段的过程中,先手可以砍掉后面的子树
好像难以处理这个过程
但是我们能求出走向某棵子树后的答案 (到 \(t\) 路径上的 \(\sum deg\) $ + f_v$)
那么我们反过来二分答案
假设已经知道了当前答案 \(mid\)
维护一个 \(res\) 表示先手能在后手行动之前砍掉多少子树
如果一个子树的答案大于二分的 \(mid\) 那么必然需要砍掉他 此时\(--res\)
在这个过程中如果 \(res < 0\) 那么当前答案不可行
如果总的砍掉的子树数量 \(sum > mid\) 答案仍然不可行
否则这是一个合法的答案
code
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
int read(){
int x = 0; char c = getchar();
while(!isdigit(c))c = getchar();
do{x = x * 10 + (c ^ 48); c = getchar();}while(isdigit(c));
return x;
}
const int maxn = 1000005;
int n, t, s, f[maxn], deg[maxn], fa[maxn];
bool in[maxn];
vector<int>g[maxn], vec;
void dfs(int x){
int mx = 0, cmx = 0;
for(int v : g[x]){
if(v == fa[x])continue;
fa[v] = x;
dfs(v);
if(f[v] > mx){cmx = mx; mx = f[v];}
else cmx = max(cmx, f[v]);
}
f[x] = cmx + deg[x] - 1;
}
int sufdeg[maxn], cnt;
bool check(int mid){
int res = 0, now = 0, sum = 0;
for(int x : vec){
++res; ++now;
int las = sum;
for(int v : g[x])if(!in[v]){
if(f[v] + sufdeg[now] > mid - las){
if(res <= 0)return false;
--res; ++sum;
}
}
}
return sum <= mid;
}
int main(){
freopen("d.in","r",stdin);
freopen("d.out","w",stdout);
n = read(), t = read(), s = read();
for(int i = 1; i < n; ++i){
int u = read(), v = read();
g[u].push_back(v); g[v].push_back(u);
++deg[u]; ++deg[v];
}
dfs(s);
int u = t; in[t] = true;
while(fa[u]){
u = fa[u]; in[u] = true;
vec.push_back(u);
}
reverse(vec.begin(), vec.end());
for(int v : vec)sufdeg[++cnt] = deg[v] - 2;
++sufdeg[1];
for(int i = cnt; i >= 1; --i)sufdeg[i] += sufdeg[i + 1];
int l = 0, r = n + n, ans = -1;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid))r = mid - 1, ans = mid;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}