HEOI2016/TJOI2016 做题笔记

HEOI2016/TJOI2016 做题笔记

题目:https://www.luogu.com.cn/problem/list?keyword=&tag=33%7C95&page=1

[HEOI2016/TJOI2016] 树

直接树剖,不断往上查询,通过 set 和记录重链上最上面的标记,不难做到 \(O(n \log n)\)

ケロシの代码
const int N = 1e5 + 5;
int n, m;
int fi[N], ne[N << 1], to[N << 1], ecnt;
int fa[N], sz[N], son[N];
int dfn[N], rnk[N], top[N], cnt;
set<int> S[N]; int f[N];
void add(int u, int v) {
	ne[++ ecnt] = fi[u];
	to[ecnt] = v;
	fi[u] = ecnt;
}
void dfs1(int u) {
	sz[u] = 1;
	son[u] = - 1;
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		if(v == fa[u]) continue;
		fa[v] = u;
		dfs1(v);
		sz[u] += sz[v];
		if(son[u] == - 1 || sz[v] > sz[son[u]]) son[u] = v;
	}
}
void dfs2(int u, int tp) {
	dfn[u] = ++ cnt;
	rnk[cnt] = u;
	top[u] = tp;
	if(son[u] != - 1) dfs2(son[u], tp);
	for(int i = fi[u]; i; i = ne[i]) {
		int v = to[i];
		if(v == fa[u] || v == son[u]) continue;
		dfs2(v, v);
	}
}
void modify(int u) {
	S[top[u]].insert(dfn[u]);
	chmin(f[top[u]], dfn[u]);
}
int query(int u) {
	while(u) {
		if(f[top[u]] <= dfn[u])
			return rnk[* prev(S[top[u]].upper_bound(dfn[u]))];
		u = fa[top[u]];
	}
}
void solve() {
	cin >> n >> m;
	REP(_, n - 1) {
		int u, v;
		cin >> u >> v;
		add(u, v);
		add(v, u);
	}
	dfs1(1); dfs2(1, 1);
	FOR(i, 1, n) f[i] = n + 1;
	modify(1);
	REP(_, m) {
		char opt; cin >> opt;
		if(opt == 'C') {
			int u; cin >> u;
			modify(u);
		}
		else {
			int u; cin >> u;
			cout << query(u) << endl;
		}
	}
}

[HEOI2016/TJOI2016] 排序

由于只有一个查询,不难想到二分答案,把原序列变成 01 序列,然后就好维护了,使用线段树模拟即可,时间复杂度 \(O(n \log^2 n)\)

ケロシの代码
const int N = 1e5 + 5;
int n, m, a[N], b[N], p;
struct Modify {
	int o, l, r;
} q[N];
struct SgT {
	int le[N << 2], ri[N << 2];
	int F[N << 2], T[N << 2];
	void pushup(int u) {
		F[u] = F[u << 1] + F[u << 1 | 1];
	}
	void push(int u, int x) {
		F[u] = x * (ri[u] - le[u] + 1);
		T[u] = x;
	}
	void pushdown(int u) {
		if(T[u] != - 1) {
			push(u << 1, T[u]);
			push(u << 1 | 1, T[u]);
			T[u] = - 1;
		}
	}
	void build(int u, int l, int r) {
		le[u] = l, ri[u] = r;
		T[u] = - 1;
		if(l == r) {
			F[u] = b[l];
			return;
		}
		int mid = l + r >> 1;
		build(u << 1, l, mid);
		build(u << 1 | 1, mid + 1, r);
		pushup(u);
	}
	void modify(int u, int l, int r, int x) {
		if(l <= le[u] && ri[u] <= r) {
			push(u, x);
			return;
		}
		pushdown(u);
		int mid = le[u] + ri[u] >> 1;
		if(l <= mid) modify(u << 1, l, r, x);
		if(mid < r) modify(u << 1 | 1, l, r, x);
		pushup(u);
	}
	int query(int u, int l, int r) {
		if(l <= le[u] && ri[u] <= r) {
			return F[u];
		}
		pushdown(u);
		int mid = le[u] + ri[u] >> 1;
		int res = 0;
		if(l <= mid) res += query(u << 1, l, r);
		if(mid < r) res += query(u << 1 | 1, l, r);
		return res;
	}
} t;
bool check(int mid) {
	FOR(i, 1, n) b[i] = a[i] > mid;
	t.build(1, 1, n);
	FOR(i, 1, m) {
		int c1 = t.query(1, q[i].l, q[i].r);
		int c0 = q[i].r - q[i].l + 1 - c1;
		if(q[i].o) {
			if(c1) t.modify(1, q[i].l, q[i].l + c1 - 1, 1);
			if(c0) t.modify(1, q[i].r - c0 + 1, q[i].r, 0);
		}
		else {
			if(c0) t.modify(1, q[i].l, q[i].l + c0 - 1, 0);
			if(c1) t.modify(1, q[i].r - c1 + 1, q[i].r, 1);
		}
	}
	return t.query(1, p, p) == 0;
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) cin >> a[i];
	FOR(i, 1, m) cin >> q[i].o >> q[i].l >> q[i].r;
	cin >> p;
	int L = 1, R = n, ans = 1;
	while(L <= R) {
		int mid = L + R >> 1;
		if(check(mid)) {
			ans = mid;
			R = mid - 1;
		}
		else {
			L = mid + 1;
		}
	}
	cout << ans << endl;
}

[HEOI2016/TJOI2016] 序列

首先对于最长不降子序列,肯定想到 dp。但是这题的转移条件比较复杂,是 \(\max a'_j \le a_i\)\(a_j \le \min a'_i\)

但是不难发现这是一个二维偏序,直接分治做,算左半边对右半边的贡献即可,使用树状数组维护,时间复杂度 \(O(n \log^2 n)\)

ケロシの代码
const int N = 1e5 + 5;
int n, m;
int a[N], f[N], g[N];
int dp[N];
vector<PII> eq, em;
struct fenwick {
	int c[N];
	int lowbit(int x) {
		return - x & x;
	}
	void modify(int u, int x) {
		for(int i = u; i <= n; i += lowbit(i))
			chmax(c[i], x);
	}
	void clear(int u) {
		for(int i = u; i <= n; i += lowbit(i))
			c[i] = 0;
	}
	int query(int u) {
		int res = 0;
		for(int i = u; i; i -= lowbit(i))
			chmax(res, c[i]);
		return res;
	}
} t;
void slv(int l, int r) {
	if(l == r) return;
	int mid = l + r >> 1;
	slv(l, mid);
	em.clear(); eq.clear();
	FOR(i, l, mid) em.push_back({f[i], i});
	FOR(i, mid + 1, r) eq.push_back({a[i], i});
	sort(ALL(em)); sort(ALL(eq));
	int pl = 0;
	for(auto h : eq) {
		while(pl < SZ(em) && FI(em[pl]) <= FI(h)) {
			int u = SE(em[pl]);
			t.modify(a[u], dp[u] + 1);
			pl ++;
		}
		int u = SE(h);
		chmax(dp[u], t.query(g[u]));
	}
	FOR(i, l, mid) t.clear(a[i]);
	slv(mid + 1, r);
}
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) cin >> a[i];
	FOR(i, 1, n) f[i] = g[i] = a[i];
	REP(_, m) {
		int u, x;
		cin >> u >> x;
		chmax(f[u], x);
		chmin(g[u], x);
	}
	FOR(i, 1, n) dp[i] = 1;
	slv(1, n);
	int ans = 0;
	FOR(i, 1, n) chmax(ans, dp[i]);
	cout << ans << endl;
}

[HEOI2016/TJOI2016] 游戏

发现限制是行和列不能有重复的,自然想到通过行和列建二分图,然后跑最大匹配。

当时发现题目中有硬石头,所以直接对每行每列分没有硬石头的连续段,作为一个点即可。

直接用 Dinic 跑二分图最大匹配即可,时间复杂度 \(O(nm\sqrt{nm})\)

ケロシの代码
namespace Dinic {
	const int N = 2e3 + 5;
	const int M = 1e4 + 5;
	const int INF = 0x3f3f3f3f;
	struct Edge {
		int ne, to, ew;
	} e[M];
	int fi[N], c[N], ecnt;
	int S, T;
	int d[N];
	void init() {
		memset(fi, 0, sizeof fi);
		ecnt = 1;
	}
	void add(int u, int v, int w) {
		e[++ ecnt] = {fi[u], v, w};
		fi[u] = ecnt;
		e[++ ecnt] = {fi[v], u, 0};
		fi[v] = ecnt;
	}
	bool bfs() {
		memset(d, 0x3f, sizeof d);
		queue<int> q;
		d[S] = 0; q.push(S);
		while(! q.empty()) {
			int u = q.front();
			q.pop();
			for(int i = fi[u]; i; i = e[i].ne) if(e[i].ew) {
				int v = e[i].to;
				if(d[v] == INF) {
					d[v] = d[u] + 1;
					q.push(v);
				}
			}
		}
		return d[T] != INF;
	}
	int dfs(int u, int w) {
		if(u == T || ! w) return w;
		int res = 0;
		for(int & i = c[u]; i; i = e[i].ne) {
			int v = e[i].to;
			if(d[v] != d[u] + 1) continue;
			int val = dfs(v, min(w, e[i].ew));
			if(! val) continue;
			e[i].ew -= val;
			e[i ^ 1].ew += val;
			res += val;
			w -= val;
			if(! w) return res;
		}
		return res;
	}
	int dinic(int _S, int _T) {
		S = _S, T = _T;
		int res = 0;
		while(bfs()) {
			memcpy(c, fi, sizeof c);
			res += dfs(S, INF);
		}
		return res;
	}
}
const int N = 55;
int n, m;
int f[N][N], t1;
int g[N][N], t2;
string s[N];
void solve() {
	cin >> n >> m;
	FOR(i, 1, n) {
		cin >> s[i];
		s[i] = ' ' + s[i];
	}
	FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] != '#') {
		if(j == 1 || s[i][j - 1] == '#') f[i][j] = ++ t1;
		else f[i][j] = f[i][j - 1];
	}
	FOR(j, 1, m) FOR(i, 1, n) if(s[i][j] != '#') {
		if(i == 1 || s[i - 1][j] == '#') g[i][j] = ++ t2;
		else g[i][j] = g[i - 1][j];
	}
	Dinic :: init();
	int S = 0, T = t1 + t2 + 1;
	FOR(i, 1, t1) Dinic :: add(S, i, 1);
	FOR(i, 1, t2) Dinic :: add(t1 + i, T, 1);
	FOR(i, 1, n) FOR(j, 1, m) if(s[i][j] == '*') 
		Dinic :: add(f[i][j], t1 + g[i][j], 1);
	cout << Dinic :: dinic(S, T) << endl;
}

[HEOI2016/TJOI2016] 字符串

对于 LCP 问题,考虑后缀数组。

但是这道题有右端点的限制,所以每次询问都二分答案,那么合法后缀的左端点区间即为 \([a,b-mid+1]\)

接下来需要找这个区间内找到 LCP 大于等于 \(mid\) 的后缀左端点。

不难发现 \(LCP(i,j)=\min_{k=rk_i+1}^{rk_j} height_k\),所以 LCP 在 \(rk\) 上是单峰的,所以只需找到与 \(rk_c\) 两边最近的两个端点即可。

直接使用主席树维护即可。

时间复杂度 \(O(n \log^2 n)\)

ケロシの代码
const int N = 1e5 + 5;
const int M = 2e7 + 5;
int n, m;
string s;
int len, sa[N], rk[N], a[N], tp[N], h[N];
int lg[N], f[N][19];
int rt[N], tot, ls[M], rs[M], F[M];
void Sort() {
	FOR(i, 1, len) a[i] = 0;
	FOR(i, 1, n) a[rk[i]] ++;
	FOR(i, 1, len) a[i] += a[i - 1];
	ROF(i, n, 1) sa[a[rk[tp[i]]] --] = tp[i];
}
void SA() {
	len = 130;
	FOR(i, 1, n) rk[i] = s[i], tp[i] = i;
	Sort();
	for(int w = 1, p = 0; w <= n; len = p, p = 0, w <<= 1) {
		FOR(i, n - w + 1, n) tp[++ p] = i;
		FOR(i, 1, n) if(sa[i] > w) tp[++ p] = sa[i] - w;
		Sort(); swap(rk, tp);
		p = rk[sa[1]] = 1;
		FOR(i, 2, n) rk[sa[i]] = (tp[sa[i]] == tp[sa[i - 1]] && tp[sa[i] + w] == tp[sa[i - 1] + w]) ? p : ++ p;
		if(p == n) return;
	}
}
void build() {
	FOR(i, 2, n) lg[i] = lg[i >> 1] + 1;
	FOR(i, 1, n) f[i][0] = h[i];
	FOR(j, 1, lg[n]) FOR(i, 1, n - (1 << j) + 1)
		f[i][j] = min(f[i][j - 1], f[i + (1 << j - 1)][j - 1]);
}
int get(int l, int r) {
	int len = lg[r - l + 1];
	return min(f[l][len], f[r - (1 << len) + 1][len]);
}
int lcp(int l, int r) {
	if(l == r) return n - sa[l] + 1;
	if(l > r) swap(l, r);
	int len = lg[r - l ++];
	return min(f[l][len], f[r - (1 << len) + 1][len]);
}
int insert(int u, int l, int r, int p) {
	int v = ++ tot;
	ls[v] = ls[u];
	rs[v] = rs[u];
	F[v] = F[u] + 1;
	if(l == r) {
		return v;
	}
	int mid = l + r >> 1;
	if(p <= mid) ls[v] = insert(ls[u], l, mid, p);
	else rs[v] = insert(rs[u], mid + 1, r, p);
	return v;
}
int query_l(int u, int v, int l, int r, int p) {
	if(p < l || F[v] - F[u] == 0) {
		return - 1;
	}
	if(l == r) {
		return l;
	}
	int mid = l + r >> 1;
	int val = query_l(rs[u], rs[v], mid + 1, r, p);
	if(val == - 1) val = query_l(ls[u], ls[v], l, mid, p);
	return val;
}
int query_r(int u, int v, int l, int r, int p) {
	if(r < p || F[v] - F[u] == 0) {
		return - 1;
	}
	if(l == r) {
		return l;
	}
	int mid = l + r >> 1;
	int val = query_r(ls[u], ls[v], l, mid, p);
	if(val == - 1) val = query_r(rs[u], rs[v], mid + 1, r, p);
	return val;
}
bool check(int l1, int r1, int l, int mid) {
	int pos = rk[l];
	int pl = query_l(rt[l1 - 1], rt[r1], 1, n, pos);
	int pr = query_r(rt[l1 - 1], rt[r1], 1, n, pos);
	if(pl != 1 && lcp(pl, pos) >= mid) return 1;
	if(pr != 1 && lcp(pr, pos) >= mid) return 1;
	return 0;
}
void solve() {
	cin >> n >> m;
	cin >> s; s = ' ' + s;
	SA();
	int p = 0;
	FOR(i, 1, n) {
		if(p) p --;
		int j = sa[rk[i] - 1];
		while(i + p <= n && j + p <= n && s[i + p] == s[j + p]) p ++;
		h[rk[i]] = p;
	}
	build();
	FOR(i, 1, n) rt[i] = insert(rt[i - 1], 1, n, rk[i]);
	REP(_, m) {
		int l1, r1, l2, r2;
		cin >> l1 >> r1 >> l2 >> r2;
		int L = 1, R = min(r1 - l1 + 1, r2 - l2 + 1), ans = 0;
		while(L <= R) {
			int mid = L + R >> 1;
			if(check(l1, r1 - mid + 1, l2, mid)) {
				ans = mid;
				L = mid + 1;
			}
			else {
				R = mid - 1;
			}
		}
		cout << ans << endl;
	}
}

[HEOI2016/TJOI2016] 求和

对于 \(i<j\),有 \(\begin{Bmatrix} i \\j \end{Bmatrix} = 0\),所以直接推式子:

\[\begin{aligned} & \sum_{i=0}^{n} \sum_{j=0}^{i} \begin{Bmatrix} i \\j \end{Bmatrix} 2^jj! \\ =& \sum_{i=0}^{n} \sum_{j=0}^{n} \begin{Bmatrix} i \\j \end{Bmatrix} 2^jj! \\ =& \sum_{j=0}^{n} 2^jj! \sum_{i=0}^{n} \begin{Bmatrix} i \\j \end{Bmatrix} \\ =& \sum_{j=0}^{n} 2^jj! \sum_{i=0}^{n} \sum_{k=0}^{j} \frac {(-1)^{j-k}k^i} {k!(j-k)!} \\ =& \sum_{j=0}^{n} 2^jj! \sum_{k=0}^{j} \frac {(-1)^{j-k}} {k!(j-k)!} \sum_{i=0}^{n} k^i \\ =& \sum_{j=0}^{n} 2^jj! \sum_{k=0}^{j} \frac {(-1)^{j-k}} {k!(j-k)!} \frac{k^{n+1}-1}{k-1} \\ =& \sum_{j=0}^{n} 2^jj! \sum_{k=0}^{j} \frac {(-1)^{j-k}} {(j-k)!} \frac{k^{n+1}-1}{k!(k-1)} \\ \end{aligned} \]

推出来的式子一边只和 \(k\) 有关,一边只和 \(j-k\) 有关,直接卷积即可。

时间复杂度 \(O(n \log n)\)

ケロシの代码
const int N = 4e5 + 5;
const int P = 998244353;
int add(int x, int y) { return (x + y < P ? x + y : x + y - P); }
void Add(int & x, int y) { x = (x + y < P ? x + y : x + y - P); }
int sub(int x, int y) { return (x < y ? x - y + P : x - y); }
void Sub(int & x, int y) { x = (x < y ? x - y + P : x - y); }
int mul(int x, int y) { return (1ll * x * y) % P; }
void Mul(int & x, int y) { x = (1ll * x * y) % P; }
int fp(int x, int y) {
	int res = 1;
	for(; y; y >>= 1) {
		if(y & 1) Mul(res, x);
		Mul(x, x);
	}
	return res;
 }
int n, fac[N], finv[N];
int r[N], F[N], G[N];
void NTT(int * a, int lim, int o) {
	REP(i, lim) if(i < r[i]) swap(a[i], a[r[i]]);
	for(int i = 1; i < lim; i <<= 1) {
		int wn = fp(o == 1 ? 3 : (P + 1) / 3, (P - 1) / (i << 1));
		for(int j = 0; j < lim; j += (i << 1)) {
			for(int k = 0, w = 1; k < i; k ++, Mul(w, wn)) {
				int x = a[j + k];
				int y = mul(w, a[j + k + i]);
				a[j + k] = add(x, y);
				a[j + k + i] = sub(x, y);
			}
		}
	}
	if(o == 1) return;
	int inv = fp(lim, P - 2);
	REP(i, lim) Mul(a[i], inv);
}
void solve() {
	cin >> n;
	int lim = 1, l = 0;
	while(lim < (n << 1)) lim <<= 1, l ++;
	REP(i, lim) r[i] = (r[i >> 1] >> 1) | ((i & 1) << (l - 1));
	fac[0] = 1;
	FOR(i, 1, n) fac[i] = mul(fac[i - 1], i);
	finv[n] = fp(fac[n], P - 2);
	ROF(i, n - 1, 0) finv[i] = mul(finv[i + 1], i + 1);
	FOR(i, 0, n) F[i] = finv[i];
	FOR(i, 0, n) if(i % 2) F[i] = sub(0, F[i]);
	FOR(i, 0, n) G[i] = mul(sub(fp(i, n + 1), 1), mul(finv[i], fp(sub(i, 1), P - 2)));
	G[1] = n + 1;
	NTT(F, lim, 1);
	NTT(G, lim, 1);
	REP(i, lim) Mul(F[i], G[i]);
	NTT(F, lim, - 1);
	int ans = 0;
	FOR(i, 0, n) Add(ans, mul(F[i], mul(fac[i], fp(2, i))));
	cout << ans << endl;
}
posted @ 2024-12-12 20:47  KevinLikesCoding  阅读(76)  评论(0编辑  收藏  举报