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;
}

posted @ 2022-11-16 17:18  Chen_jr  阅读(23)  评论(0编辑  收藏  举报