还没写完 | 2023.6.25做题记录

还没写完 | 2023.6.25做题记录

P3232 [HNOI2013]游走

是不是什么神秘主元法,维护系数

考虑 \(\operatorname{EX}(u)\) 表示点 \(u\) 出发到 \(n\) 的期望步数,\(\operatorname{EX}(n)=0\)

如果假设 \(u\) 的出度为 \(d\)

\[\operatorname{EX}(u)=\frac{1}{d}\bigg(\sum_{v\in to(u)}\operatorname{EX}(v)+(u,v)\bigg) \]

然后我们发现我们不知道 \((u,v)\) 是啥!但是我们可以考虑维护所有 \((u,v)\) 的系数,我们希望知道每条 \((u,v)\) 对答案的贡献是多少

变一下形

\[\operatorname{EX}(u)-\frac{1}{d}\bigg(\sum_{v\in to(u)}\operatorname{EX}(v)\bigg)=\frac{1}{d}\sum_{v\in to(u)}(u,v) \]

那么我们可以对每条边开一个未知数,然后消元的时候整个未知数的向量一起消就行了,时间复杂度 \(\mathcal O(n^3m)\),能过第一个点

考虑把边的次数转到点的次数上来,现在假设 \(\operatorname{EX}(u)\) 表示 \(u\) 要经过的期望次数

\[\operatorname{EX}(u)= \begin{cases} \frac{1}{d_u}\sum_{v\in to(u),v\neq n}\operatorname{EX}(v)+1&u=1\\ \frac{1}{d_u}\sum_{v\in to(u),v\neq n}\operatorname{EX}(v)&1<u<n\\ \end{cases} \]

所以 \((u,v)\) 的期望经过次数即为 \(\frac{\operatorname{EX}(u)}{d_u}+\frac{\operatorname{EX}(v)}{d_v}\)

#include <iostream>
#include <cstring>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cmath>

const int N = 510;
double mat[N][N];
double f[N];
int d[N];
std::vector<int> G[N];
struct edge {
	int u, v;
	edge(){}
	edge(int _u, int _v) {u = _u, v = _v;}
}E[250010];
struct redge {
	int u, v;
	double k;
	bool operator < (const redge& b) {
		return k > b.k;
	}
}E2[250010];

int idx;
int n, m;

inline void add(int u, int v) {
	G[u].push_back(idx), E[idx++] = edge(u, v);
}

int main() {
	scanf("%d%d", &n, &m);
	for(int i = 1; i <= m; i++) {
		int u, v;
		scanf("%d%d", &u, &v);
		add(u, v), add(v, u);
		d[u]++, d[v]++;
		E2[i].u = u, E2[i].v = v;
	}
	for(int i : G[1]) {
		int v = E[i].v;
		if(v != n)
			mat[1][v] += -(double)1 / (double)d[v];
	}
	mat[1][1] = 1, mat[1][n+1] = 1;
	for(int u = 2; u < n; u++) {
		for(int i : G[u]) {
			int v = E[i].v;
			if(v != n)
				mat[u][v] += -(double)1 / (double)d[v];
		}
		mat[u][u] = 1;
	}
	mat[n][n] = 1;
	for(int i = 1; i <= n; i++) {
		double maxl = -1;
		int row = i;
		for(int j = i; j <= n; j++)
			if(fabs(mat[j][i]) > maxl)
				row = j, maxl = fabs(mat[i][j]);
		for(int j = 1; j <= n + 1; j++)
			std::swap(mat[i][j], mat[row][j]);
		for(int j = 1; j <= n; j++) {
			if(j != i) {
				double x = mat[j][i] / mat[i][i];
				for(int k = i; k <= n + 1; k++) {
					mat[j][k] -= mat[i][k] * x;
				}
			}
		}
	}
	for(int i = 1; i <= n; i++)
		f[i] = mat[i][n+1] / mat[i][i];
	for(int i = 1; i <= m; i++) {
		if(E2[i].u != n)
			E2[i].k += f[E2[i].u] / (double)d[E2[i].u];
		if(E2[i].v != n)
			E2[i].k += f[E2[i].v] / (double)d[E2[i].v];
	}
	std::sort(E2 + 1, E2 + 1 + m);
	double ans = 0;
	for(int i = 1; i <= m; i++) {
		ans += E2[i].k * i;
	}
	printf("%.3f\n", ans);
	return 0;
}

P5643 [PKUWC2018]随机游走

P5643 [PKUWC2018]随机游走

这位更是方程都不会列

点集中的所有点都经过一次的期望步数\(\Longleftrightarrow\)经过点集中最后一个点的期望步数

考虑 \(\min-\max\) 容斥,

\[\operatorname{E}(\max_{x\in S} \{x\})=\sum_{T\subset S}(-1)^{|T|+1}\operatorname{E}(\min_{x\in T}\{x\}) \]

变成经过点集中第一个点的期望步数

不妨假设 \(f_{u,S}\) 表示从 \(u\) 开始,经过 \(S\) 中至少一个点的期望步数,记 \(p\)\(u\) 的父亲

如果 \(u\in S\),则为 \(0\),否则为

\[f_{u,S}=\frac{1}{\deg_u}\bigg(f_{p,S}+\sum_{v\in \operatorname {son}\{u\}}f_{v,S}\bigg)+1 \]

由于是树上的方程,所以可以直接递推,考虑推一下式子,对于这类问题可以拆贡献,把父亲和儿子的贡献分开,即 \(f_{u,S}=A_uf_{p,S}+B_u\)

\[\begin{aligned} f_{u,S} &=\frac{1}{\deg_u}\bigg(f_{p,S}+\sum_{v\in \operatorname {son}\{u\}}f_{v,S}\bigg)+1\\ &=\frac{1}{\deg_u}\bigg(f_{p,S}+\sum_{v\in \operatorname {son}\{u\}}(A_vf_{u,S}+B_v)\bigg)+1\\ &=\frac{1}{\deg_u}\bigg(f_{p,S}+\sum_{v\in \operatorname {son}\{u\}}A_v\cdot f_{u,S}+\sum_{v\in \operatorname {son}}B_v\bigg)+1\\ \end{aligned} \]

那么

\[\deg_u\cdot f_{u,S}=f_{p,S}+\sum_{v\in \operatorname {son}\{u\}}A_v\cdot f_{u,S}+\sum_{v\in \operatorname {son}\{u\}}B_v+\deg_u \]

变换成一开始的形式

\[\bigg(\deg_u-\sum_{v\in \operatorname{son}\{u\}}A_v\bigg)f_{u,S}=f_{p,S}+\sum_{v\in \operatorname{son}\{u\}}B_v+\deg_u \]

\[f_{u,S}=\dfrac{1}{\deg_u-\sum_{v\in \operatorname{son}\{u\}}A_v}f_{p,S}+\dfrac{\sum_{v\in \operatorname{son}\{u\}}B_v+\deg_u}{\deg_u-\sum_{v\in \operatorname{son}\{u\}}A_v} \]

可以知道

\[B_u=\dfrac{\sum_{v\in \operatorname{son}\{u\}}B_v+\deg_u}{\deg_u-\sum_{v\in \operatorname{son}\{u\}}A_v} \]

\[A_u=\dfrac{1}{\deg_u-\sum_{v\in \operatorname{son}\{u\}}A_v} \]

显然,\(f_{root,S}\)\(B_{root}\)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cctype>

typedef long long ll;
const int N = 20;
const ll mod = 998244353;
int h[N], e[N << 1], ne[N << 1], idx;
ll dp[1 << N];
ll A[N], B[N], deg[N];
int n, q, x;

inline void add(int u, int v) {
	e[idx] = v, ne[idx] = h[u], h[u] = idx++;
}

ll f_pow(ll a, ll k = mod - 2) {
	ll base = 1;
	for(; k; k >>= 1, a = a * a % mod)
		if(k & 1)
			base = base * a % mod;
	return base;
}

inline ll Plus(ll a, ll b) {
	a += b;
	return a >= mod ? a -= mod : a;
}

inline ll Minu(ll a, ll b) {
	a -= b;
	return a < 0 ? a + mod : a;
}

void dfs(int u, int fa, int s) {
	if(s & (1 << u)) {
		A[u] = B[u] = 0;
		return ;
	}
	ll suma = 0, sumb = 0;
	for(int i = h[u]; ~i; i = ne[i]) {
		int v = e[i];
		if(v == fa)
			continue;
		dfs(v, u, s);
		suma = Plus(suma, A[v]);
		sumb = Plus(sumb, B[v]);
	}
	ll t = Minu(deg[u], suma);;
	ll invt = f_pow(t);
	A[u] = invt;
	B[u] = Plus(sumb, deg[u]) * invt % mod;
}

int main() {
	std::ios::sync_with_stdio(false);
	std::cin.tie(0);
	memset(h, -1, sizeof(h));
	std::cin >> n >> q >> x;
	x--;
	for(int i = 1; i < n; i++) {
		int u, v;
		std::cin >> u >> v;
		u--, v--;
		add(u, v), add(v, u);
		deg[u]++, deg[v]++;
	}
	for(int st = 0; st < 1 << n; st++) {
		dfs(x, -1, st);
		dp[st] = B[x];
	}
	for(int i = 1; i <= q; i++) {
		int st = 0;
		int sz;
		std::cin >> sz;
		for(int i = 1; i <= sz; i++) {
			int t;
			std::cin >> t;
			st |= 1 << (t-1);
		}
		ll ans = 0;
		for(int s = st; s; s = (s-1) & st) {
			int t = __builtin_popcount(s);
			if(t & 1) {
				ans = Plus(ans, dp[s]);
			} else {
				ans = Minu(ans, dp[s]);
			}
		}
		std::cout << ans << '\n';
	}
	return 0;
}

P4117 [Ynoi2018] 五彩斑斓的世界

P4117 [Ynoi2018] 五彩斑斓的世界

终于做掉了

先分块!考虑对块内怎么处理

这个 \(>x\) 的数减去 \(x\) 的修改性质不太好,考虑一些暴力。

我们注意到对于每一块,其最大值一定是不升的,

如果有 \(\sqrt n\) 块,那么每块的最大值都是 \(\mathcal O(n)\) 级别的,所有块的最值之和就是 \(\mathcal O(n\sqrt n)\),我们考虑把复杂度摊到这个东西上面

设定一个阈值 \(p\),考虑如果 \(x<p\) 时,我们把操作变为把 \(\le x\) 的数加 \(x\),再给整个区间减去 \(x\),就能花 \(\mathcal O(x)\cdot \mathcal O(DS)\) 的代价令最大值减去 \(\mathcal O(x)\)

如果 \(x\ge p\),对 \(x+1\sim v\) 暴力即可,花 \(\mathcal O(v-x)\cdot \mathcal O(DS)\) 的代价令最大值减小到 \(\mathcal O(x)\),即减小了 \(\mathcal O(v-x)\)

所以那么令 \(p=\frac{1}{2}v\),其中 \(v\) 是最大值,单块的总代价就是

\[\mathcal O(n)\cdot \mathcal O(DS) \]

用一个并查集实际上就够了。所以单块就是 \(\mathcal O(n)\)

均摊下来就是 \(\mathcal O((n+m)\sqrt n)\),空间复杂度 \(\mathcal O(n\sqrt n)\),把询问离线下来对每个块分别处理可以空间复杂度降到 \(\mathcal O(n)\)

猜我写了多久。五个钟。

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <vector>
#include <cmath>
#include <queue>

const int N = 1e6 + 50;
int a[N];
//{{{ FAST IO
inline int read() {
	int s = 0;
	char c = getchar();
	while (!isdigit(c))
		c = getchar();
	while (isdigit(c))
		s = (s << 3) + (s << 1) + (c ^ 48), c = getchar();
	return s;
}
inline void write(int x) {
	static char stk[22];	
	static int tt = 0;
	stk[tt++] = x % 10 + '0';
	x /= 10;
	while (x) {
		stk[tt++] = x % 10 + '0';
		x /= 10;
	}
	while (tt) {
		putchar(stk[--tt]);
	}
	putchar('\n');
}
//}}}

struct query {
	int tp, l, r, x;
}Q[N >> 1];
std::vector<int> ans;
int n, m;
int L[1010], R[1010];
int block, bnum;
void init() {
	block = sqrt(n);
	bnum = (n-1) / block + 1;
	for (int i = 1; i <= bnum; i++) {
		L[i] = (i-1) * block + 1, R[i] = std::min(i * block, n);
	}
}

int fa[N / 10 + 10], sz[N / 10 + 10];

inline int find(int x) {
	return x == fa[x] ? x : fa[x] = find(fa[x]);
}

inline void merge(int u, int v) {
	int fu = find(u), fv = find(v);
	if(fu == fv)
		return ;
	else
		fa[fu] = fv, sz[fv] += sz[fu];
	sz[fu] = 0;
}

void rebuild(int l, int r, int tag, int& v) {
	for (int i = l; i <= r; i++) {
		sz[find(a[i])] = 0;
		if(a[i] != 0)
			a[i] = find(a[i]) - tag;
	}
	v -= tag;
	for (int i = 1; i <= tag; i++)
		fa[i] = i;
	for (int i = l; i <= r; i++)
		sz[a[i]]++;
}

void solve(int b) {
	int tag = 0, v = 0;
	int l, r;
	for (int i = L[b]; i <= R[b]; i++)
		v = std::max(v, a[i]);
	for (int i = 1; i <= v; i++)
		sz[i] = 0, fa[i] = i;
	for (int i = L[b]; i <= R[b]; i++)
		sz[a[i]]++;
	int qnum = 0;
	for (int i = 1; i <= m; i++) {
		if(Q[i].tp == 2)
			qnum++;
		l = std::max(Q[i].l, L[b]);
		r = std::min(Q[i].r, R[b]);
		if (l > r)
			continue;
		int x = Q[i].x;
		if(Q[i].tp == 1) {
			if(x == 0)
				continue;
			if (l == L[b] && r == R[b]) {
				if (2 * x < v - tag) {
					for (int j = 1 + tag; j <= x + tag; j++)
						merge(j, j + x);
					tag += x;
				} else {
					for (int j = x + 1 + tag; j <= v; j++)
						merge(j, j - x);
					while(!sz[v])	v--;
				}
			} else {
				rebuild(L[b], R[b], tag, v);
				tag = 0;
				for (int j = l; j <= r; j++)
					if (a[j] > x) {
						sz[a[j]]--;
						a[j] -= x;
						sz[a[j]]++;
					}
			}
		} else {
			if(x + tag > v)
				continue;
			if (l == L[b] && r == R[b]) {
				if(x != 0)
					ans[qnum-1] += sz[find(x + tag)];
				else
					ans[qnum-1] += sz[0];
			} else {
				int cnt = 0;
				rebuild(L[b], R[b], tag, v);
				tag = 0;
				for (int j = l; j <= r; j++)
					if(a[j] == x)
						cnt++;
				ans[qnum-1] += cnt;
			}
		}
	}
}

int main() {
	n = read(), m = read();
	for (int i = 1; i <= n; i++)
		a[i] = read();
	int cnt = 0;
	for (int i = 1; i <= m; i++) {
		Q[i].tp = read(), Q[i].l = read(), Q[i].r = read(), Q[i].x = read();
		if(Q[i].tp == 2)
			cnt++;
	}
	ans.resize(cnt);
	init();
	for (int k = 1; k <= bnum; k++) {
		solve(k);
	}
	for (int i : ans)
		write(i);
	return 0;
}
posted @ 2023-06-26 20:31  Nelofus  阅读(12)  评论(0编辑  收藏  举报