闲话 22.11.6

闲话

圆环之理!

所以我大概需要缓几天。

仍然是杂题

CF1305G

有n个人,并且给出他们的年龄。两个人是朋友,当且仅当两个人年龄的按位与结果为0。现在,有一个传销组织,每个人有两种操作:

  1. 主动加入传销组织,这样的话,传销组织不会给你钱;

  2. 邀请自己的一个朋友加入传销组织,这样的话,传销组织会奖励你数值等于你的年龄的钱。(当然,执行该操作的人必须已经进入传销组织了)

每个人只可以进入传销组织一次。
给定 \(n\) 个人和他们的年龄,请输出:如果 \(n\) 个人合作,传销组织支付给这 \(n\) 个人的钱数之和最大是多少。

\(1\le n,a[i]\le 2\times 10^5\)

这题正解是 \(O(18\times 2^{18} \log n)\) 的,但是你直接冲一个 \(O(3^{18} \log n)\) 的暴力也能过(但是最大的点 2.8s 卡界)

考虑抽象成图,两个人是朋友则两个人之间有边。\(a\)\(b\) 拉进传销组织就等于选择了一条 \((a,b)\) 的边。那自己加入组织怎么办呢?
我们考虑一个年龄为 \(0\) 的节点,容易发现这个节点和任一节点有边,并且一定不会出现在原图上,也不会对答案产生贡献。

由于 \((i,j)\) 有边等价于 \(a_i\ \text{and}\ a_j = 0\),因此 \(a_i + a_j\) 等于 \(a_i\ \text{or} \ a_j\)。考虑使 \((i,j)\) 的边权为 \(a_i + a_j\),选择图上最大生成树后减去所有节点权值加和就是答案。
\(O(3^{18} \log n)\) 考虑枚举边权,枚举边权的子集得到可能拼成这条边的两个节点。如果存在两个节点或为这条边且与为 \(0\),那就可以选择这两个节点。
这里可以考虑让节点的权值成为节点的编号,记录这样节点的个数。若 \(i\) 权值出现了 \(c_i\) 次,则 \((i,j)\) 边的权值即为 \((c_i + c_j - 1) \times (i\ \text{or}\ j)\)。连完一条边后把 \(c_i,c_j\)\(1\) 即可,容易通过最小生成树的贪心策略证明该做法的正确性。

\(O(18\times 2^{18} \log n)\) 考虑快速求得值 \(S\) 子集中的最大值,以及与这个最大值不在同一联通块内的次大值。容易发现决策只能从这两个值中选择。

如果这题扔进模拟赛:
缩小时限,卡掉 \(O(3^{18} \log n)\) 的暴力。

code (3^{18} log n)
#include<bits/stdc++.h>
using namespace std;
template <typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 3) + (x << 1) + (ch ^ 48), ch = getchar(); f && (x = -x);
} template <typename T, typename ...Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,a,b) for (register auto i = (a), i##_ = (b) + 1; i < i##_; ++ i)
#define pre(i,a,b) for (register auto i = (a), i##_ = (b) - 1; i > i##_; -- i)
const int N = 4e5 + 10;
int M = (1 << 18) - 1;
int n, a[N], cnt[N], fa[N], k, mx;
long long ans;

int find(int u) { return u == fa[u] ? u : fa[u] = find(fa[u]); }
int merge(int u, int v) {
	u = find(u), v = find(v);
	if (u == v) return 0;
	fa[u] = v; 
	return 1;
}

signed main(){
	get(n); rep(i,1,n) get(a[i]), mx = max(mx, a[i]), cnt[a[i]] ++, ans -= a[i];
	cnt[0] ++; M = (1 << __lg(mx) + 1) - 1;
	rep(i,0,M) fa[i] = i;
	pre(i,M,0) {
		for (int s = i; 1; s = (s - 1) & i) {
			if (cnt[s] and cnt[i ^ s] and merge(s, i ^ s)) {
				ans += 1ll * (cnt[s] + cnt[i ^ s] - 1) * i;
				cnt[s] = cnt[i ^ s] = 1;
				++ k; if (k == n) cout << ans << '\n', exit(0);
			} if (s == 0) break;
		}
	} cout << ans << '\n';
}



CF512D

给定一张 \(n\) 个点 \(m\) 条边的无向图。 一个点只有当与它直接相连的点中最多只有一个点未被选择过时才可被选择。询问对于每个 \(k \in [0,n]\),有序选择 \(k\) 个点的方案数。

\(n \le 100\)\(m \le \frac{n(n-1)}2\),答案对 \(10^9+9\) 取模。

如果这题扔进模拟赛:
造数据一定要注意点,\(m \ge 2n\) 时基本上输出 \(\text{1\n0\n0\n}\cdots\text{0\n}\) 就能对,而且 \(n=m\) 时很大程度上答案是一段 \(1\) + 一段 \(0\)
数据更好是森林或者 \(n > m-1\) 的情况。

如果一个点在环上那它永远也无法被选择。因此拓扑排序找到所有环,环上的点是无法被选择的点,反之是可选点。容易发现可选点和最多一个不可选点组成了森林。
每棵树对答案的贡献相同,只需要分别跑出可能性后背包乘进组合数就行了。

现在讨论单棵树。
第一种是都可选。枚举每个点作为最后一个选的点,以它为根搜一遍整棵树,\(i,j\to i+j\) 合并背包时合并排列情况乘入 \(\binom{i+j}{i}\) 即可。
第二种是只有一个位置可选。这时都不用枚举,直接以这个不可选的位置为根跑背包合并就行了。

上下界优化因此总复杂度为 \(O(n^3)\)
\(O(n^2)\) 的 EGF 做法,看不懂。晚上可能会看看。

code
#include <bits/stdc++.h>
using namespace std; 
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 2e2 + 10, mod = 1e9 + 9;
int n, m, t1, t2, C[N][N], deg[N], bel[N], siz[N], ans[N], inv[N], f[N][N];
vector <int> g[N];

bool ck[N];
void topo() {
	queue<int> q; assert(q.empty());
	rep(i,1,n) if (deg[i] <= 1) q.push(i), ck[i] = 1;
	while (q.size()) {
		int u = q.front(); q.pop();
		for (int v : g[u]) {
			deg[v] --;
			if (deg[v] <= 1 and !ck[v]) q.push(v), ck[v] = 1;
		}
	}
}

void find(int u, int bl, int & sum) {
	++ sum, bel[u] = bl;
	for (auto v : g[u]) if (deg[v] == 0 and !bel[v]) 
		find(v, bl, sum);
}

void dfs(int u, int fa) {
	siz[u] = 1; f[u][0] = 1;
	for (int v : g[u]) if (v != fa and bel[u] == bel[v]) {
		dfs(v, u);
		rep(i,0,siz[v]-1) f[u][i + siz[u]] = 0;
		pre(j,siz[u],0) rep(k,1,siz[v]) 
			f[u][j+k] = (f[u][j+k] + 1ll * f[u][j] * f[v][k] % mod * C[j+k][j]) % mod;
		siz[u] += siz[v];
	} f[u][siz[u]] = f[u][siz[u]-1];
}

signed main() {
	get(n, m); rep(i,1,m) get(t1, t2), g[t1].emplace_back(t2), g[t2].emplace_back(t1), deg[t1]++, deg[t2]++;
	rep(i,0,n << 1) C[i][0] = 1; inv[0] = inv[1] = 1;
	rep(i,2,n << 1) inv[i] = 1ll * (mod - mod / i) * inv[mod % i] % mod; 
	rep(i,1,n << 1) rep(j,1,i) C[i][j] = (C[i-1][j] + C[i-1][j-1]) % mod;

	topo();
	rep(i,1,n) if (deg[i] == 1) find(i, i, siz[i]); // 找到每棵树上唯一(?)的在环上的点,以他为根搜出一系列树点
	rep(i,1,n) if (deg[i] == 0 and !bel[i]) find(i, i, siz[i]); // 这树上不存在在环上的点

	ans[0] = 1; int tot = 0;
	rep(i,1,n) if (i == bel[i]) {
		int sz = siz[i];
		if (deg[i] == 1) {
			dfs(i, 0); rep(j,0,siz[i]) f[0][j] = (f[0][j] + f[i][j]) % mod;
		} 
		else {
			rep(j,1,n) if (bel[j] == i) {
				dfs(j, 0); rep(k,0,siz[j]) f[0][k] = (f[0][k] + f[j][k]) % mod;
			}
			rep(j,0,sz) f[0][j] = 1ll * f[0][j] * inv[sz - j] % mod;
		}
		pre(j,tot,0) rep(k,1,sz) 
			ans[j + k] = (ans[j + k] + 1ll * ans[j] * f[0][k] % mod * C[j + k][k]) % mod;
		rep(j,0,sz) f[0][j] = 0;
		tot += sz;
	}

	rep(i,0,n) cout << ans[i] << '\n';
}



[SCOI2010] 序列操作

给定一个 \(01\) 序列,你需要支持五种操作:

  • 0 l r\([l, r]\) 区间内的所有数全变成 \(0\)
  • 1 l r\([l, r]\) 区间内的所有数全变成 \(1\)
  • 2 l r\([l,r]\) 区间内的所有数全部取反,也就是说把所有的 \(0\) 变成 \(1\),把所有的 \(1\) 变成 \(0\)
  • 3 l r 询问 \([l, r]\) 区间内总共有多少个 \(1\)
  • 4 l r 询问 \([l, r]\) 区间内最多有多少个连续的 \(1\)

对于\(100\%\) 的数据,\(1\le n,m \le 10^5\)

练码力写的题。

挺平凡的。线段树上分别维护 \(01\) 出现次数和,左右最长连续段,区间最长连续段。

维护两个 lazy 标记,一个 cov 表示覆盖该区间的数字是什么,初始化 \(-1\);另一个 rev 表示该区间是否翻转,\(0\) 则翻了,\(1\) 则没有。
如果该区间只有 rev 则 cov 为-1,rev 为 0/1。如果该区间最后一次操作为 cov 则 rev 为 0,如果该区间最后一次操作不为 cov 但这段中没有破坏整段相同的性质则还是 cov,因此相同时候只有一个操作成立。

朴素维护即可。

code
#include <bits/stdc++.h>
using namespace std; 
template<typename T> void get(T & x) {
	x = 0; char ch = getchar(); bool f = false; while (ch < '0' or ch > '9') f = f or ch == '-', ch = getchar();
	while ('0' <= ch and ch <= '9') x = (x << 1) + (x << 3) + ch - '0', ch = getchar(); f && (x = -x); 
} template <typename T, typename ... Args> void get(T & a, Args & ... b) { get(a); get(b...); }
#define rep(i,s,t) for (register int i = (s), i##_ = (t) + 1; i < i##_; ++ i)
#define pre(i,s,t) for (register int i = (s), i##_ = (t) - 1; i > i##_; -- i)
const int N = 1e5 + 10;
int n, m, typ, l, r;

struct SegmentCitrus {
	#define ls (p << 1)
	#define rs (p << 1 | 1)
	#define siz(p) seg[p].siz
	#define sum(p) seg[p].sum
	#define lmax(p) seg[p].lmax
	#define rmax(p) seg[p].rmax
	#define tmax(p) seg[p].tmax
	#define lzycov(p) seg[p].lzycov
	#define lzyswp(p) seg[p].lzyswp

	struct node {
		int siz, sum[2], lzycov, lzyswp;
		int lmax[2], rmax[2], tmax[2];
		node() = default;
		void clear() { memset(this, 0, sizeof(node)); }
	} seg[N << 2];

	node ps_p(node lp, node rp) {
		node ret;
		ret.siz = lp.siz + rp.siz; 
		rep(i,0,1) ret.sum[i] = lp.sum[i] + rp.sum[i];
		rep(i,0,1) {
			ret.lmax[i] = lp.lmax[i];
			if (lp.lmax[i] == lp.siz) ret.lmax[i] = lp.lmax[i] + rp.lmax[i];
			ret.rmax[i] = rp.rmax[i];
			if (rp.rmax[i] == rp.siz) ret.rmax[i] = lp.rmax[i] + rp.rmax[i];
			ret.tmax[i] = max( {lp.tmax[i], rp.tmax[i], lp.rmax[i] + rp.lmax[i] } );
		}
		ret.lzycov = -1;
		return ret;
	}

	void ps_cov(int p, int ptr) {
		sum(p)[ptr] = lmax(p)[ptr] = rmax(p)[ptr] = tmax(p)[ptr] = siz(p); 
		sum(p)[ptr ^ 1] = lmax(p)[ptr ^ 1] = rmax(p)[ptr ^ 1] = tmax(p)[ptr ^ 1] = 0;
		lzyswp(p) = 0, lzycov(p) = ptr;
	}

	void ps_swp(int p) {
		swap(sum(p)[0], sum(p)[1]); swap(lmax(p)[0], lmax(p)[1]);
		swap(rmax(p)[0], rmax(p)[1]); swap(tmax(p)[0], tmax(p)[1]);
		if (lzycov(p) != -1) lzycov(p) ^= 1, lzyswp(p) = 0;
		else lzyswp(p) ^= 1;
	}

	void ps_d(int p) {
		if (lzycov(p) != -1) {
			ps_cov(ls, lzycov(p)), ps_cov(rs, lzycov(p));
			lzycov(p) = -1;
		}
		if (lzyswp(p)) {
			ps_swp(ls), ps_swp(rs);
			lzyswp(p) = 0;
		}
	}

	void build(int p = 1, int l = 1, int r = n) {
		siz(p) = r - l + 1; lzycov(p) = -1;
		if (l == r) {
			get(typ); ps_cov(p, typ);
			return;
		} int mid = l + r >> 1;
		build(ls, l, mid); build(rs, mid+1, r);
		seg[p] = ps_p(seg[ls], seg[rs]);
	}

	void updcov(int p, int l, int r, int L, int R, int v) {
		if (L <= l and r <= R) {
			ps_cov(p, v);
			return ;
		} int mid = l + r >> 1; ps_d(p);
		if (L <= mid) updcov(ls, l, mid, L, R, v);
		if (mid < R) updcov(rs, mid+1, r, L, R, v);
		seg[p] = ps_p(seg[ls], seg[rs]);
	}
	void updcov(int l, int r, int v) { updcov(1, 1, n, l, r, v); }

	void updswp(int p, int l, int r, int L, int R) {
		if (L <= l and r <= R) {
			ps_swp(p); 
			return ;
		} int mid = l + r >> 1; ps_d(p);
		if (L <= mid) updswp(ls, l, mid, L, R);
		if (mid < R) updswp(rs, mid+1, r, L, R);
		seg[p] = ps_p(seg[ls], seg[rs]);
	}
	void updswp(int l, int r) { updswp(1, 1, n, l, r); }

	int qrytot(int p, int l, int r, int L, int R) {
		if (L <= l and r <= R) return sum(p)[1];
		int mid = l + r >> 1, ret = 0; ps_d(p);
		if (L <= mid) ret += qrytot(ls, l, mid, L, R);
		if (mid < R) ret += qrytot(rs, mid+1, r, L, R);
		return ret;
	}
	int qrytot(int l, int r) { return qrytot(1, 1, n, l, r); }

	node qrylen(int p, int l, int r, int L, int R) {
		if (L <= l and r <= R) return seg[p];
		int mid = l + r >> 1; node ret; ret.clear(), ps_d(p);
		if (L <= mid) ret = ps_p(ret, qrylen(ls, l, mid, L, R));
		if (mid < R) ret = ps_p(ret, qrylen(rs, mid+1, r, L, R));
		return ret;
	}
	int qrylen(int l, int r) { return qrylen(1, 1, n, l, r).tmax[1]; }
} Tr;

signed main() {
	get(n, m); Tr.build();
	while (m--) {
		get(typ, l, r); l ++, r ++ ;
		if (typ <= 1) Tr.updcov(l, r, typ);
		if (typ == 2) Tr.updswp(l, r);
		if (typ == 3) cout << Tr.qrytot(l, r) << '\n';
		if (typ == 4) cout << Tr.qrylen(l, r) << '\n';
	}
}
posted @ 2022-11-06 19:41  joke3579  阅读(84)  评论(11编辑  收藏  举报