正睿 25 年省选联考 Day 17

正睿 25 年省选联考 Day 17

\(\text{Link}\)

得分

T1 T2 T3 得分 排名
\(100\) \(100\) \(50\) \(250\) \(6/21\)

题解

T1 简单题

其实不是什么难题。由于有区间推平操作所以考虑颜色段均摊,我们维护一颗线段树,做 2 操作的时候就正常区间加;做 1 操作的时候我们先找到对应区间,然后开始向下递归,找到同色连续段然后直接操作,最后把这个区间改为同色连续段即可。复杂度是 \(O(n\log n)\) 的。

#include <bits/stdc++.h>
#define il inline
#define int long long

using namespace std;

const int Maxn = 5e5 + 5;
const int Inf = 2e9;
const int Mod = 1e9 + 7;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b) {int res = 1; for(; b; a = 1ll * a * a % Mod, b >>= 1) if(b & 1) res = 1ll * res * a % Mod; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
	x = 0; char ch = getchar(); bool flg = 0;
	for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
	flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
	static short Stk[50], Top = 0;
	x < 0 ? putchar('-'), x = -x : 0;
	do Stk[++Top] = x % 10, x /= 10; while(x);
	while(Top) putchar(Stk[Top--] | 48);
	typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int n, m, q, a[Maxn];
int b[Maxn];
namespace SGT {
	struct node {
		int col, tag, cnt, clr;
	}t[Maxn << 2];
	#define ls(p) (p << 1)
	#define rs(p) (p << 1 | 1)
	void pushup(int p) {
		t[p].cnt = t[ls(p)].cnt | t[rs(p)].cnt;
	}
	void build(int p, int l, int r) {
		t[p].col = -1;
		if(l == r) {t[p].col = a[l]; return;}
		int mid = (l + r) >> 1;
		build(ls(p), l, mid), build(rs(p), mid + 1, r);
		pushup(p);
	}
	void pushclr(int p) {t[p].tag = t[p].cnt = 0, t[p].clr = 1;}
	void pushtag(int p, int v) {t[p].tag += v, t[p].cnt = 1;}
	void pushcol(int p, int col) {t[p].col = col;}
	void pushdown(int p) {
		if(t[p].clr) pushclr(ls(p)), pushclr(rs(p)), t[p].clr = 0;
		if(t[p].tag) pushtag(ls(p), t[p].tag), pushtag(rs(p), t[p].tag), t[p].tag = 0;
		if(t[p].col != -1) pushcol(ls(p), t[p].col), pushcol(rs(p), t[p].col), t[p].col = -1;
	}
	void mdf1(int p, int l, int r, int pl, int pr, int v) {
		if(pl <= l && r <= pr) {
			pushtag(p, v); return ;
		}
		pushdown(p);
		int mid = (l + r) >> 1;
		if(pl <= mid) mdf1(ls(p), l, mid, pl, pr, v);
		if(pr > mid) mdf1(rs(p), mid + 1, r, pl, pr, v);
		pushup(p);
	}
	void add(int p, int l, int r) {
		if(!t[p].cnt) return ;
		if(t[p].col != -1 && t[p].tag != 0) {
			b[t[p].col] += (r - l + 1) * t[p].tag;
			return ;
		}
		pushdown(p);
		int mid = (l + r) >> 1;
		add(ls(p), l, mid), add(rs(p), mid + 1, r);
	}
	void mdf2(int p, int l, int r, int pl, int pr, int x) {
		if(pl <= l && r <= pr) {
			add(p, l, r);
			pushclr(p), pushcol(p, x); return ;
		}
		pushdown(p);
		int mid = (l + r) >> 1;
		if(pl <= mid) mdf2(ls(p), l, mid, pl, pr, x);
		if(pr > mid) mdf2(rs(p), mid + 1, r, pl, pr, x);
		pushup(p);
	}
}

bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
signed main() {
	read(n), read(m), read(q);
	for(int i = 1; i <= n; i++) {
		read(a[i]);
	}
	SGT::build(1, 1, n);
	while(q--) {
		int opt, l, r, x;
		read(opt), read(l), read(r), read(x);
		switch(opt) {
			case 1: {
				SGT::mdf2(1, 1, n, l, r, x);
				break;
			}
			case 2: {
				SGT::mdf1(1, 1, n, l, r, x);
				break;
			}
		}
	}
	SGT::mdf2(1, 1, n, 1, n, 0);
	for(int i = 1; i <= m; i++) write(b[i]);
    Usd();
	return 0;
}

T2 树上游走

巨大 dp 题。以 \(S\) 为根建一个有根树,然后直接树形 dp。观察一下发现我们走到一个子树之后的走法和两个信息有关:一个是它的父亲是否还存活,另一个是它之前有没有被经过过一次(也就是形如 \(fa_u\to u\to fa_u\to u\) 这么走)。所以设 \(f(i,0/1,0/1)\) 表示当前在 \(i\) 子树内,父亲是否存活以及 \(i\) 经过过多少次时,在 \(i\) 子树内结束的期望得分。实际上 \(f(i,1,1)\) 是无用的,因为 \(i\) 经过过一次说明它的父亲一定死了,所以实际上只有三个有效状态。

然后还需要设一个 \(p(i)\) 表示在有父亲且没有经过过的时候,从 \(i\) 开始不经过父亲的概率。转移较为复杂,需要分类讨论一下没有儿子、有一个儿子以及有多个儿子的情况,然后仔细考虑一下不同转移的系数。优化是比较容易的,复杂度 \(O(n)\)

#include <bits/stdc++.h>
#define il inline

using namespace std;

const int Maxn = 2e5 + 5;
const int Inf = 2e9;
const int Mod = 998244353;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
	x = 0; char ch = getchar(); bool flg = 0;
	for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
	flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
	static short Stk[50], Top = 0;
	x < 0 ? putchar('-'), x = -x : 0;
	do Stk[++Top] = x % 10, x /= 10; while(x);
	while(Top) putchar(Stk[Top--] | 48);
	typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int n, rt, w[Maxn];
int inv[Maxn];
#define Inv(a) inv[a]
vector <int> E[Maxn];
namespace S1 {
	void main() {
		int ans = 0, iv = Inv(n - 1);
		for(int i = 1; i <= n; i++) {
			if(i != rt) pls(ans, 1ll * w[i] * iv % Mod);
		}
		write(ans);
	}
}

int f[Maxn][2][2], p[Maxn], deg[Maxn];
int son[Maxn], cnt;
void dfs(int x, int fth) {
	for(auto to : E[x]) if(to != fth) dfs(to, x);
	cnt = 0;
	for(auto to : E[x]) if(to != fth) son[++cnt] = to;
	if(!cnt) {
		f[x][0][0] = f[x][0][1] = w[x]; p[x] = 0;
	}
	else if(cnt == 1) {
		int to = son[1];
		int p1 = Inv(deg[to]), p2 = Del(Del(1, p[to]), p1);
		f[x][0][0] = Add(Add(f[to][1][0], 1ll * p1 * f[to][0][1] % Mod), 1ll * p2 * w[x] % Mod);
		f[x][1][0] = Add(1ll * Inv(2) * f[to][1][0] % Mod, 1ll * Inv(4) * p1 % Mod * f[to][0][1] % Mod);
		p[x] = Del(1, Add(Inv(2), Add(1ll * Inv(4) * p1 % Mod, 1ll * Inv(2) * p2 % Mod)));
		f[x][0][1] = f[to][0][0];
	}
	else {
		int sum = 0;
		for(int i = 1; i <= cnt; i++) {
			int to = son[i];
			pls(sum, f[to][0][0]);
		}
		p[x] = Del(1, Inv(cnt + 1));
		for(int i = 1; i <= cnt; i++) {
			int to = son[i];
			pls(f[x][0][0], 1ll * f[to][1][0] * Inv(cnt) % Mod);
			pls(f[x][1][0], 1ll * f[to][1][0] * Inv(cnt + 1) % Mod);
			int p1 = Inv(deg[to]), p2 = Del(Del(1, p[to]), p1), fs = Del(sum, f[to][0][0]);
			pls(f[x][0][0], 1ll * Inv(cnt) * p2 % Mod * Inv(cnt - 1) % Mod * fs % Mod);
			pls(f[x][0][0], 1ll * Inv(cnt) * p1 % Mod * Inv(cnt) % Mod * fs % Mod);
			pls(f[x][1][0], 1ll * Inv(cnt + 1) * p2 % Mod * Inv(cnt) % Mod * fs % Mod);
			pls(f[x][1][0], 1ll * Inv(cnt + 1) * p1 % Mod * Inv(cnt + 1) % Mod * fs % Mod);	
			pls(f[x][0][0], 1ll * Inv(cnt) * p1 % Mod * Inv(cnt) % Mod * f[to][0][1] % Mod);
			pls(f[x][1][0], 1ll * Inv(cnt + 1) * p1 % Mod * Inv(cnt + 1) % Mod * f[to][0][1] % Mod);
			sub(p[x], 1ll * Inv(cnt + 1) * p1 % Mod * Inv(cnt + 1) % Mod);
			sub(p[x], 1ll * Inv(cnt + 1) * p2 % Mod * Inv(cnt) % Mod);
			pls(f[x][0][1], 1ll * Inv(cnt) * f[to][0][0] % Mod);
		}
	}
}

bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
	read(n), read(rt);
	for(int i = 1; i <= n; i++) read(w[i]);
	bool flg = 1;
    for(int i = 1, u, v; i < n; i++) {
    	read(u), read(v);
    	deg[u]++, deg[v]++;
    	E[u].push_back(v), E[v].push_back(u);
		if(u != rt && v != rt) flg = 0;
	}
	inv[1] = 1;
	for(int i = 2; i <= max(4, n); i++) inv[i] = 1ll * inv[Mod % i] * (Mod - Mod / i) % Mod;
	dfs(rt, 0);
	write(f[rt][0][0]);
	Usd();
	return 0;
}

T3 树的同构

发现 \(n\) 很小,所以可以支持一个比较巨大的复杂度。考虑两棵树中深度最浅的点 \(i,j\),令 \(dep_i\ge dep_j\),那么我们可以直接枚举 \(i\),这样第一棵树中的左右节点就在 \(i\) 的子树中了;然后在子树外枚举一下 \(i\) 的对应点,这样我们就知道了两棵树的根,然后就可以树形 dp 了。

\(f(x,y)\) 表示第一棵树中 \(x\) 与第二棵树中 \(y\) 对应时,子树内同构树的最大节点数。转移比较奇怪,我们需要把 \(x,y\) 的儿子一一配对,让儿子之间同构求最大值。不难想到建立二分图模型,对于两个儿子 \(p,q\),我们连边边权 \(f(p,q)\),如此做一次二分图最大匹配即可求出最大值。

由于二分图最大匹配是 \(O(nm)\) 的,所以这个算法的复杂度看上去是 \(O(n^7)\) 的。一个比较显然的优化是,当我们第一棵树的根确定之后,不管第二棵树的根怎样取,只要 \(x,y\) 以及 \(y\) 对应的父亲相同,\(f(x,y)\) 就是一样的。这样的话每一对 \((x,y)\) 将会被计算 \(O(deg_y)\) 次,每一次计算的复杂度可以看作是 \(O(deg_x^2deg_y)\) 的,所以每一对 \((x,y)\) 计算的复杂度是 \(O(deg_x^2 deg_y^2)\),也就大概是 \(O(n^4)\),再加上枚举 \(i\) 的复杂度总复杂度上界应该是 \(O(n^5)\) 的,足够通过。

由于我不会 \(O(nm)\) 的 KM 算法,所以直接信仰跑网络流。

#include <bits/stdc++.h>
#define il inline

using namespace std;

const int Maxn = 51 + 5, Maxm = 5e3 + 5;
const int Inf = 2e9;
const int Mod = 1e9 + 7;
il int Add(int x, int y) {return x + y >= Mod ? x + y - Mod: x + y;} il void pls(int &x, int y) {x = Add(x, y);}
il int Del(int x, int y) {return x - y < 0 ? x - y + Mod : x - y;} il void sub(int &x, int y) {x = Del(x, y);}
il int qpow(int a, int b) {int res = 1; for(; b; a = 1ll * a * a % Mod, b >>= 1) if(b & 1) res = 1ll * res * a % Mod; return res;}
il int Inv(int a) {return qpow(a, Mod - 2);}
template <typename T> il void chkmax(T &x, T y) {x = (x >= y ? x : y);}
template <typename T> il void chkmin(T &x, T y) {x = (x <= y ? x : y);}
template <typename T>
il void read(T &x) {
	x = 0; char ch = getchar(); bool flg = 0;
	for(; ch < '0' || ch > '9'; ch = getchar()) flg = (ch == '-');
	for(; ch >= '0' && ch <= '9'; ch = getchar()) x = (x << 1) + (x << 3) + (ch ^ 48);
	flg ? x = -x : 0;
}
template <typename T>
il void write(T x, bool typ = 1) {
	static short Stk[50], Top = 0;
	x < 0 ? putchar('-'), x = -x : 0;
	do Stk[++Top] = x % 10, x /= 10; while(x);
	while(Top) putchar(Stk[Top--] | 48);
	typ ? putchar('\n') : putchar(' ');
}
il void IOS() {ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);}
il void File() {freopen("in.txt", "r", stdin); freopen("out.txt", "w", stdout);}
bool Beg;

int p[Maxn << 1], tmp;
int n;
vector <int> E[Maxn];
int fa[Maxn], vis[Maxn];
void dfs1(int x, int fth) {fa[x] = fth; for(auto to : E[x]) if(to != fth) dfs1(to, x);}
void dfs2(int x) {vis[x] = 1; for(auto to : E[x]) if(to != fa[x]) dfs2(to);}

namespace F {
	int head[Maxn], edgenum;
	struct node {
		int nxt, to, w, c;
	}edge[Maxm];
	int n, s, t;
	void init(int _n, int _s, int _t) {
		edgenum = 1;
		for(int i = 1; i <= n; i++) head[i] = 0;
		n = _n, s = _s, t = _t;
	}
	void add(int u, int v, int w, int c) {
		edge[++edgenum] = {head[u], v, w, c}; head[u] = edgenum;
		edge[++edgenum] = {head[v], u, 0, -c}; head[v] = edgenum;
	}
	int dis[Maxn], pre[Maxn], minw[Maxn], vis[Maxn];
	int SPFA() {
		for(int i = 1; i <= n; i++) dis[i] = -Inf, vis[i] = 0;
		queue <int> q; q.push(s);
		dis[s] = 0, minw[s] = Inf, vis[s] = 1;
		while(!q.empty()) {
			int x = q.front(); q.pop();
			vis[x] = 0;
			for(int i = head[x]; i; i = edge[i].nxt) {
				int to = edge[i].to, w = edge[i].w;
				if(dis[x] + edge[i].c > dis[to] && w > 0) {
					dis[to] = dis[x] + edge[i].c;
					pre[to] = i, minw[to] = min(minw[x], w);
					if(!vis[to]) q.push(to), vis[to] = 1;
				}
			}
		}
		return dis[t] != -Inf;
	}
	int EK() {
		int mc = 0;
		while(SPFA()) {
			mc += minw[t] * dis[t];
			for(int x = t, i = pre[t]; x != s; x = edge[i ^ 1].to, i = pre[x]) {
				edge[i].w -= minw[t]; edge[i ^ 1].w += minw[t];
			}
		}
		return mc;
	}
}

int f[Maxn][Maxn][Maxn], rt;
int dfs3(int x, int y, int fth) {
	if(f[x][y][fth] != 0) return f[x][y][fth];
	vector <int> p1, p2; int p = 0, q = 0;
	for(auto to : E[x]) if(to != fa[x]) p1.push_back(to), p++;
	for(auto to : E[y]) if(to != fth && to != rt) p2.push_back(to), q++;
	for(int i = 0; i < p; i++) for(int j = 0; j < q; j++) dfs3(p1[i], p2[j], y);
	F::init(p + q + 2, p + q + 1, p + q + 2);
	for(int i = 0; i < p; i++) F::add(p + q + 1, i + 1, 1, 0), F::add(i + 1, p + q + 2, 1, 0);
	for(int i = 0; i < q; i++) F::add(p + i + 1, p + q + 2, 1, 0);
	for(int i = 0; i < p; i++) for(int j = 0; j < q; j++) F::add(i + 1, p + j + 1, 1, f[p1[i]][p2[j]][y]);
	return f[x][y][fth] = 1 + F::EK();
}

bool End;
il void Usd() {cerr << (&Beg - &End) / 1024.0 / 1024.0 << "MB " << (double)clock() * 1000.0 / CLOCKS_PER_SEC << "ms\n"; }
int main() {
	while(cin >> p[++tmp]) p[tmp]++;
	n = (tmp >> 1) + 1;
	for(int i = 1; i < n; i++) {
		int u = p[i], v = p[i + n - 1];
		E[u].push_back(v), E[v].push_back(u);
	}
	dfs1(1, 0);
	int ans = 0;
	for(int i = 2; i <= n; i++) {
		for(int j = 1; j <= n; j++) vis[j] = 0;
		dfs2(i); rt = i;
		memset(f, 0, sizeof f);
		for(int j = 1; j <= n; j++) {
			if(!vis[j]) chkmax(ans, dfs3(i, j, 0));
		}
	}
	write(ans);
    Usd();
	return 0;
}
posted @ 2025-04-18 14:47  UKE_Automation  阅读(40)  评论(0)    收藏  举报