2024年07月随便做做

[2024.07.15] 省选模拟

对不起。

但是为什么 B 题时间复杂度 \(O(n\sqrt n \log_2 A)\)\(O(n^2\log_2 n)\) 同分啊/kk。

A. tree

给定一棵大小为 \(n\) 的树,第 i 个节点有点权 \(w_i(1 ≤ w_i ≤ m)\),记树 上一个连通块 \(S\) 中最大的点权为 \(max_S\),现在需要你求 \(max_S = 1, 2, 3, · · · , m\) 的连通块个数,答案对 \(998244353\) 取模。


记录 \(f_{u, x}\) 表示以 \(u\) 为根的连通块,最大权值为 \(x\) 的方案数。

可以发现两个子树在父亲上的合并应该形如:

\[g_{x}=f_{u,x}+f_{v,x}+\sum_{x=\max (i,j)} f_{u,i}f_{v,j} \]

对多个子树也是同样的。在最后考虑父亲点的权值一定会考虑进去,所以再多算一次。

这个可以线段树合并来维护。问题是对于每个点,都要考虑 \(f_{u,x}\)\(x\) 最终的贡献。我们需要单独记录贡献的标记。这中间有很多细节,一定要理清楚贡献标记和乘法标记的优先级。

// Not afraid to dark.

#include <bits/stdc++.h>
using namespace std;

#define inline __inline__ __attribute__ ((always_inline))

namespace io {
    // io
};

const int N = 2e5, mod = 998244353;
int n, m, a[N + 5];
int firs[N + 5], nex[N * 2 + 5], to[N * 2 + 5], tot;
inline void Add (int u, int v){
	++ tot;
	nex[tot] = firs[u];
	firs[u] = tot;
	to[tot] = v;
	++ tot;
	nex[tot] = firs[v];
	firs[v] = tot;
	to[tot] = u;
}

const int P = N * 25;
int rt[N + 5], ls[P + 5], rs[P + 5], tt;
struct TAG {
	int x, y; // x (mul) y (query)
	inline void apply (TAG t){ y = (y + 1ll * x * t.y % mod) % mod; x = 1ll * x * t.x % mod; }
	TAG (int _x_ = 1, int _y_ = 0){ x = _x_, y = _y_; }
} tag[P + 5];
struct INFO {
	int s, r; // s (sum) r (contribute)
	inline void apply (TAG t){ r = (r + 1ll * s * t.y % mod) % mod; s = 1ll * s * t.x % mod; }
	INFO (int _s_ = 0, int _r_ = 0){ s = _s_, r = _r_; }
} info[P + 5];
inline int new_node (){ ++ tt, tag[tt] = TAG (), info[tt] = INFO (), ls[tt] = rs[tt]= 0; return tt; }
inline void apply (int p, TAG t){ tag[p].apply (t), info[p].apply (t); }
inline void push_down (int p){
	if (ls[p]) apply (ls[p], tag[p]);
	if (rs[p]) apply (rs[p], tag[p]);
	tag[p] = TAG ();
}
inline void push_up (int p){ info[p].s = (info[ls[p]].s + info[rs[p]].s) % mod; }
int Merge (int u, int v, int pu, int pv){
	if (! u && ! v) return 0;
	if (! u) return apply (v, TAG (pu + 1, 0)), v;
	if (! v) return apply (u, TAG (pv + 1, 0)), u;
	if (! ls[u] && ! rs[u]){
		info[u].r = (info[u].r + info[v].r) % mod;
		info[u].s = (1ll * info[u].s * (pv + info[v].s + 1) % mod + 1ll * info[v].s * (pu + info[u].s + 1) % mod + 1ll * (mod - 1) * info[u].s % mod * info[v].s % mod) % mod;
		return u;
	}
	push_down (u), push_down (v);
	rs[u] = Merge (rs[u], rs[v], (pu + (ls[u] ? info[ls[u]].s : 0)) % mod, (pv + (ls[v] ? info[ls[v]].s : 0)) % mod);
	ls[u] = Merge (ls[u], ls[v], pu, pv);
	push_up (u);
	return u;
}
int Upd (int u, int l, int r, int x){
	if (! u) return 0;
	int res = 0;
	if (r <= x){
		res = info[u].s;
		return apply (u, TAG (0, 0)), res;
	}
	push_down (u);
	int mid = (l + r) >> 1;
	res = res + Upd (ls[u], l, mid, x);
	if (x > mid)
		res = res + Upd (rs[u], mid + 1, r, x);
	return res % mod;
}
void Mdy (int &u, int l, int r, int x, int t){
	if (! u)
		u = new_node ();
	if (l == r)
		return info[u].s = t, void ();
	int mid = (l + r) >> 1;
	push_down (u);
	if (x <= mid)
		Mdy (ls[u], l, mid, x, t);
	else
		Mdy (rs[u], mid + 1, r, x, t);
	push_up (u);
}
void Output (int u, int l, int r){
	if (l == r)
		return io::write (info[u].r), putchar (r == m ? '\n' : ' '), void ();
	push_down (u);
	int mid = (l + r) >> 1;
	Output (ls[u], l, mid);
	Output (rs[u], mid + 1, r);
}

void Solve (int u, int father){
	for (int e = firs[u], v;e;e = nex[e]){
		v = to[e];
		if (v == father) continue;
		Solve (v, u);
		rt[u] = Merge (rt[u], rt[v], 0, 0);
	}
	Mdy (rt[u], 1, m, a[u], (Upd (rt[u], 1, m, a[u]) + 1) % mod);
	apply (rt[u], TAG (1, 1));
}

signed main (){
	freopen ("tree.in", "r", stdin);
	freopen ("tree.out", "w", stdout);
	io::read (n), io::read (m);
	for (int i = 1;i <= n;++ i)
		io::read (a[i]);
	for (int i = 1;i < n;++ i)
		Add (io::read (), io::read ());
	Solve (1, 0);
	Output (rt[1], 1, m);
	return 0;
}

B. train

每次询问从 \(r\) 跳向 \(l\),每次从当前 \(u\) 跳到 \(\max\{x|x\ge l\land \forall i\in [l,u),a_i\oplus a_u \le a_x\oplus a_w \}\),直到 \(u=l\)


最唐的一次。绕着正解想了各种歪解居然没有想到这个。

显然原题的要求可以转换成每次从 \(w\) 跳到 \(\min\{ x|x\ge l\land \forall i \in (x,w),a_i\oplus a_w<a_x\oplus a_w \}\)

然后可以根据这个条件对于序列二分分治,在每一个子问题中,对于右部的一个点考虑一步跳到 \(\le mid\) 的位置需要让 \(l\) 最多为多少,记为 \(lim\),这个是好求的。

然后对于每一个子问题,在右部建出 \(l=mid+1\) 时的跳的树。

在询问中,递归到一个子问题中,当前 \(l\le mid,r>mid\),则可以倍增找到 \(r\) 跳到的第一个 \(lim_u\ge l\) 的点,然后正常跳到左部即可。

想到建树和条件转化,为什么没有考虑分治呢?

// Not afraid to dark.

#include <bits/stdc++.h>
using namespace std;

#define inline __inline__ __attribute__ ((always_inline))

namespace io {
	// io
};

const int N = 1e5;

int n, q, a[N + 5];

int tot, son[N * 35 + 5][2], mx[N * 35 + 5];
int rt[N + 5];
inline void release (int x){ son[x][0] = son[x][1] = mx[x] = 0; }
inline void Insert (int u, int v, int x, int t){
	for (int i = 30, e;i >= 0;-- i){
		e = x >> i & 1; mx[u] = t;
		son[u][e] = ++ tot; son[u][e ^ 1] = son[v][e ^ 1];
		u = son[u][e], v = son[v][e];
	}
	mx[u] = t;
}
inline int Query_1 (int u, int x, int rs = 0){
	static int res; res = 0;
	for (int i = 30, e, r;i >= 0;-- i){
		e = x >> i & 1, r = rs >> i & 1;
		if (r)
			u = son[u][e ^ 1];
		else {
			if (son[u][e ^ 1])
				res = max (res, mx[son[u][e ^ 1]]);
			u = son[u][e];
		}
		if (u == 0)
			break;
	}
	return res;
}
inline int Query_2 (int u, int x, int l){
	for (int i = 30, e;i >= 0;-- i){
		e = x >> i & 1;
		if (mx[son[u][e ^ 1]] >= l)
			u = son[u][e ^ 1];
		else
			u = son[u][e];
	}
	return mx[u];
}
inline int Query_3 (int u, int x, int l){
	static int res; res = 0;
	for (int i = 30, e;i >= 0;-- i){
		e = x >> i & 1;
		if (mx[son[u][e ^ 1]] >= l)
			u = son[u][e ^ 1], res |= 1 << i;
		else
			u = son[u][e];
	}
	return res;
}

int fa[20][20][N + 5], mf[20][20][N + 5];

#define Fa fa[d]
#define Mf mf[d]
void DAC (int l, int r, int d){
	if (l == r)
		return ;
	int mid = (l + r) >> 1;
	DAC (l, mid, d + 1);
	DAC (mid + 1, r, d + 1);
	for (int i = mid + 1;i <= r;++ i){
		Fa[0][i] = Query_2 (rt[i - 1], a[i], mid + 1);
		if (i != mid + 1)
			Mf[0][i] = Query_1 (rt[mid], a[i], Query_3 (rt[i - 1], a[i], mid + 1));
		else
			Mf[0][i] = i - 1;
	}
	for (int i = mid + 1;i <= r;++ i)
		for (int j = 1;j <= 17;++ j){
			Fa[j][i] = Fa[j - 1][Fa[j - 1][i]];
			Mf[j][i] = max (Mf[j - 1][i], Mf[j - 1][Fa[j - 1][i]]);
		}
}
void qDAC (int l, int r, int d, int x, int y, int &ans){
	if (l == r)
		return ;
	int mid = (l + r) >> 1;
	if (y <= mid)
		qDAC (l, mid, d + 1, x, y, ans);
	else
	if (x > mid)
		qDAC (mid + 1, r, d + 1, x, y, ans);
	else {
		for (int i = 17;i >= 0;-- i)
			if (Mf[i][y] < x){
				y = Fa[i][y];
				ans += 1 << i;
			}
		y = Query_2 (rt[y - 1], a[y], x), ++ ans;
		qDAC (l, mid, d + 1, x, y, ans);
	}
}

inline void ARKNIGHTS (){
	while (tot)
		release (tot --);
	io::read (n), io::read (q);
	for (int i = 1;i <= n;++ i){
		io::read (a[i]);
		Insert (rt[i] = ++ tot, rt[i - 1], a[i], i);
	}
	DAC (1, n, 1);
	for (int l, r, ans;q --;){
		io::read (l), io::read (r);
		qDAC (1, n, 1, l, r, ans = 0);
		io::write (ans), putchar ('\n');
	}
}

signed main (){
	freopen ("train.in", "r", stdin);
	freopen ("train.out", "w", stdout);
	for (int T = io::read ();T --;)
		ARKNIGHTS ();
	return 0;
}

C. circle

给出 \(n\)\(a_{1,\cdots ,n}\)。每次询问给出 \(b_1,\cdots ,b_m\) 以及 \(l,r\),可以做任意次操作,每次可以在区间首尾相接构成的环上选出一段长度为 \(b_i\) 的区间加上任意实数,问最后能否使整个区间都为 \(0\)


显然,因为对操作次数没有限制,所以相当于每次可以将一个长度为 $R = \gcd {b_1,\cdots,b_m,r-l+1} $ 的区间同时加上一个整数。

发现实际上可以全部消掉当且仅当 \(\forall i=0,\cdots R-1:sum_i=\sum_{j\in[l,r],j\equiv i\mod R} a_i\) 应该全部是相等的。必要性显然。充分性可以考虑如果有这个性质,那么从第一个位置开始每次以当前位置为左界操作操作,直到当前位置变为 \(0\),那么最后的 \(R\) 个数一定是相等的。

然后就转化成了 \(sum_{0,\cdots,R-1}\) 要相等的问题。

考虑 hash。如果

\[\sum_{i=l}^r a_i\cdot base^{i\textrm{ mod } k}\equiv sum_0[l,r]\sum_{i=0}^{k-1} base^i\mod p \]

那么就一定是可以的。

考虑对 \(R\) 进行根号分治。

\(R\le \sqrt n\) 时,分块后对于每个 \(R\) 值记录块内的 \(sum_0\)\(\sum a_i\cdot base^{i\textrm{ mod }k}\),这部分修改和查询都是简单的。

\(R> \sqrt n\) 时,考虑对于一个 \(R\),所有可能的 \(i-i\textrm{ mod } R\)\(O(\frac n R) =O(\sqrt n)\) 级别的,而且是一段一段出现的。那么在 \([l,r]\) 中枚举 \(i - i \textrm{ mod } R\) 相同的一段,对于每一段在分块上求出 \(\sum a_i\cdot base^i\) 的值,再统一除以 \(base^{i-i\textrm{ mod } R}\) 即可。\(sum_0\) 可以直接暴力求出。

总共的时间复杂度为 \(O(n\sqrt n)\)

// Not afraid to dark.

#include <bits/stdc++.h>
using namespace std;

#define inline __inline__ __attribute__ ((always_inline))

#define int long long

namespace io {
	// io
};

const int N = 2e5;

inline int power (int x, int p, int mod){
	static __int128 res, a;
	res = 1, a = x;
	while (p > 0){
		if (p & 1)
			res = res * a % mod;
		a = a * a % mod;
		p >>= 1;
	}
	return (int) res;
}

namespace HASH {
	const int b1 = 137, b2 = 149, b3 = 151;
	const int mod1 = 1222827239, mod2 = 1610612741, mod3 = 1000000007;
	int i1, i2, i3;
	int bp1[N + 5], bp2[N + 5], bp3[N + 5];
	int qp1[N + 5], qp2[N + 5], qp3[N + 5];
	int ip1[N + 5], ip2[N + 5], ip3[N + 5];
	inline void init (int n){
		i1 = power (b1, mod1 - 2, mod1), i2 = power (b2, mod2 - 2, mod2), i3 = power (b3, mod3 - 2, mod3);
		bp1[0] = bp2[0] = bp3[0] = 1;
		qp1[0] = qp2[0] = qp3[0] = 1;
		ip1[0] = ip2[0] = ip3[0] = 1;
		for (int i = 1;i <= n;++ i){
			bp1[i] = bp1[i - 1] * b1 % mod1; qp1[i] = (qp1[i - 1] + bp1[i]) % mod1; ip1[i] = ip1[i - 1] * i1 % mod1;
			bp2[i] = bp2[i - 1] * b2 % mod2; qp2[i] = (qp2[i - 1] + bp2[i]) % mod2; ip2[i] = ip2[i - 1] * i2 % mod2;
			bp3[i] = bp3[i - 1] * b3 % mod3; qp3[i] = (qp3[i - 1] + bp3[i]) % mod3; ip3[i] = ip3[i - 1] * i3 % mod3;
		}
	}
	struct hash_number {
		int x, y, z;
		hash_number (int _x_ = 0, int _y_ = 0, int _z_ = 0){ x = _x_; y = _y_; z = _z_; }
		inline bool friend operator == (const hash_number a, const hash_number b){
			return a.x == b.x && a.y == b.y && a.z == b.z;
		}
		inline void push_back (int a){
			x = (x * b1 + a % mod1) % mod1;
			y = (y * b2 + a % mod2) % mod2;
			z = (z * b3 + a % mod3) % mod3;
		}
		inline hash_number operator << (const int t){
			return hash_number (x * bp1[t] % mod1, y * bp2[t] % mod2, z * bp3[t] % mod3);
		}
		inline hash_number operator >> (const int t){
			return hash_number (x * ip1[t] % mod1, y * ip2[t] % mod2, z * ip3[t] % mod3);
		}
		inline hash_number friend operator + (const hash_number a, const hash_number b){
			return hash_number ((a.x + b.x) % mod1, (a.y + b.y) % mod2, (a.z + b.z) % mod3);
		}
		inline hash_number friend operator - (const hash_number a, const hash_number b){
			return hash_number ((a.x - b.x + mod1) % mod1, (a.y - b.y + mod2) % mod2, (a.z - b.z + mod3) % mod3);
		}
	};
	inline hash_number Hash (int X){
		return hash_number ((X % mod1 + mod1) % mod1, (X % mod2 + mod2) % mod2, (X % mod3 + mod3) % mod3);
	}
	inline hash_number BOUND (hash_number B, int R){
		return hash_number (B.x * qp1[R - 1] % mod1, B.y * qp2[R - 1] % mod2, B.z * qp3[R - 1] % mod3);
	}
}
using HASH::hash_number;
using HASH::Hash;
using HASH::BOUND;

const int T = 450;
int n, q, a[N + 5];
int sq, len, block[N + 5], bl[T + 5], br[T + 5];
hash_number ctr[T + 5][T + 5], s0[T + 5][T + 5], pre[N + 5];

inline void modify (int x, int y){
	for (int i = 1;i <= T;++ i){
		ctr[block[x]][i] = ctr[block[x]][i] + (Hash (y) << (x % i));
		if (x % i == 0)
			s0[block[x]][i] = s0[block[x]][i] + Hash (y);
	}
}
inline void reconstruct (int x){
	for (int i = bl[x];i <= br[x];++ i){
		if (i == bl[x]) pre[i] = hash_number ();
		else pre[i] = pre[i - 1];
		pre[i] = pre[i] + (Hash (a[i]) << i);
	}
}
inline hash_number query (int l, int r){
	static hash_number res; res = hash_number ();
	if (block[l] == block[r]){
		res = pre[r];
		if (l != bl[block[l]])
			res = res - pre[l - 1];
		return res;
	}
	res = pre[br[block[l]]] + pre[r];
	if (l != bl[block[l]])
		res = res - pre[l - 1];
	for (int i = block[l] + 1;i < block[r];++ i)
		res = res + pre[br[i]];
	return res;
}

inline void Build (){
	sq = (int) sqrt (n);
	len = (n - 1) / sq + 1;
	for (int i = 1;i <= sq;++ i){
		bl[i] = br[i - 1] + 1, br[i] = min (bl[i] + len - 1, n);
		for (int j = bl[i];j <= br[i];++ j)
			block[j] = i, modify (j, a[j]);
		reconstruct (i);
	}
}

signed main (){
	freopen ("circle.in", "r", stdin);
	freopen ("circle.out", "w", stdout);
	io::read (n), io::read (q);
	HASH::init (n);
	for (int i = 1;i <= n;++ i)
		io::read (a[i]);
	Build ();
	for (int l, r, m, x, w;q --;)
		if (io::read () == 1){
			io::read (x), io::read (w);
			modify (x, w - a[x]);
			a[x] = w;
			reconstruct (block[x]);
		} else {
			io::read (l), io::read (r), io::read (m);
			int R = r - l + 1;
			for (;m --;)
				R = __gcd (R, io::read ());
			hash_number A, B;
			if (R <= T){
				for (int i = l;i <= min (r, br[block[l]]);++ i){
					A = A + (Hash (a[i]) << (i % R));
					if (i % R == 0)
						B = B + Hash (a[i]);
				}
				for (int i = block[l] + 1;i < block[r];++ i)
					A = A + ctr[i][R], B = B + s0[i][R];
				if (block[l] != block[r])
					for (int i = bl[block[r]];i <= r;++ i){
						A = A + (Hash (a[i]) << (i % R));
						if (i % R == 0)
							B = B + Hash (a[i]);
					}
				B = BOUND (B, R);
			} else {
				auto get_r = [&] (const int t){ return min (r, t - t % R + R - 1); };
				auto get_next = [&] (const int t) { return t - t % R + R; };
				int L = l, T;
				while (L <= r){
					T = get_r (L);
					hash_number xx;
					A = A + (xx = query (L, T) >> (L - L % R));
					L = T + 1;
				}
				L = get_next (l - 1);
				while (L <= r){
					B = B + Hash (a[L]);
					L = get_next (L);
				}
				B = BOUND (B, R);
			}
			if (A == B)
				puts ("YES");
			else
				puts ("NO");
		}
	return 0;
}

[2024.07.23] 省选模拟

第一题太简单就不写了(虽然我分治之后清空数组写错挂了 65)。

[JSC2023-final] E - Min Subtraction

非常神秘的题目。

首先有一个结论:原题的操作可以转换为每次选择相邻的两个正数然后各自减去 \(1\),可以发现最终得到的序列的 \(0\) 的最大个数一定等于原操作得到的 \(0\) 的最大个数。

首先后者一定是小于等于前者的,因为新的操作显然强于原本的操作。所以相当于需要证明的是:存在能够用新操作达到的最优解,用原操作也可以达到。

考虑一个最优解,\((a_i,a_{i+1})\) 被减去了 \(c_i\) 次。

  • 如果存在一个地方有 \(c_i=0\),那么可以直接分成两个子问题,归纳下去证明,直到遇到所有 \(c_i>0\) 的情况。
  • 如果存在 \(a_1=c_1\) 或者 \(a_n=c_{n-1}\),那么显然对这些边界做原操作可以得到一样的结果,那么把这个边界删去之后归纳下去证明。
  • 否则一定有操作过后的 \(a_1'\)\(a_n'\) 均大于 \(0\),那么这时边界上的 \(c\) 加或者减都对 \(0\) 的个数没有更劣的影响。然后发现如果将 \(\forall c_i\leftarrow c_i+(-1)^{i-1}\),那么中间的 \(a_i'\) 是不会变的。一直这样做,直到满足前两个情况。这样显然是可以的。

所以得到了一个更简单的题面,那么怎么做呢?还是不会。

然后又有一个结论:最优解中得到的 \(a_1',a_2',\cdots,a_i'\) 中的 \(0\) 的个数任意确定合法的 \(c_1,c_2,\cdots,c_i\)\(a_1',a_2',\cdots,a_i'\)\(0\) 的最大个数。

证明考虑如果对于 \(a_1',\cdots,a_i'\) 最优解中有 \(c\)\(0\)\(i\) 之后的有 \(d\)\(0\)。那么考虑如果存在一个方案使得 \(a_1',\cdots,a_i'\) 中至少有 \(c+1\)\(0\),那么考虑把这个方案的 \(c_1,\cdots,c_i\) 和最优解中的 \(c_{i+1},\cdots,c_{n-1}\) 拼起来。如果 \(a_{i+1}'\ge 0\),那么这时总共至少有 \((c+1)+(d-1)=c+d\)\(0\),且方案合法,那么说明存在一个最优解,\(a_1',\cdots,a_i'\) 至少有 \(c+1\)\(0\)。否则可以将 \(c_{i+1}\leftarrow c_{i+1}-a_{i+1}'\),这时 \(a_{i+1}'\) 一定会变成 \(0\),而 \(a_{i+2}'\) 有可能原本是 \(0\) 之后不是,所以至少也会有 \((c+1)+(d-1)-1+1=c+d\)\(0\),所以如果可以使前面的 \(0\) 的个数增大,一定不会更劣。

所以据此得到一种贪心的做法。

考虑到 \(i\) 时,记录 \([l,r]\) 表示要让 \(a_{1,\cdots,i-1}\)\(0\) 的个数最大,那么 \(a_{i}\) 的取值范围为 \([l,r]\)。同时记录 \(cnt_0\) 表示当前最多有多少个 \(0\)

  • 初始 \(i=1,l=r=a_1,cnt_0=0\)
  • 考虑 \(i-1\) 时范围为 \([l,r]\),加入 \(z=a_i\)\(z\ge l\),此时考虑当 \(a_{i-1}\in[l,\min (z,r)]\) 时一定可以将 \(a_{i-1}'\) 消成 \(0\)\([l,r]\leftarrow [z-\min(z,r),z-l],cnt_0\leftarrow cnt_0+1\)
  • 考虑 \(i-1\) 时范围为 \([l,r]\),加入 \(z=a_i\)\(z< l\),此时考虑一定无法消去 \(a_{i-1}\),所以直接有 \([l,r]\leftarrow[0,z]\)

最后的答案显然是 \(cnt_0+[l=0]\)

Submission #55906949 - 第四回日本最強プログラマー学生選手権 -決勝- (atcoder.jp)

[UTPC 2023] P - Priority Queue 3

妙妙题,感觉所有性质都得出了,但是组合起来就是想不到后置计算方案数。

性质:

  • 如果 \(A_j\) 是没有被加入 \(Y\)\(A\) 元素中最大的,那么一定不会加入 \(< A_j\)\(\bar A\) 中元素。
  • 对于任意可行方案的前缀 \(j\)(定义见上)是单调不升的。

于是得到一个 dp 的做法,记 \(f(i,j,k,t=0/1)\) 表示考虑到第 \(i\) 个位置时,最大的没有被加入 \(Y\)\(A\) 中元素为 \(A_j\)\(k=|X\cap A|\)\(t\) 表示 \(A_j\) 是否在 \(X\) 中时的一个值,注意这里不是方案数,因为我们并不会即时计算方案数,而是会在特定时候统一计算。

如果我们想从 \(f(i-1,j,k,t)\) 转移到 \(f(i,*,*,*)\),考虑以下几种情况:

  • 当前操作为 +,且我们打算加 \(\bar A\) 中的元素进去。那么计算出一个 \(p\) 表示 \(>A_j\) 的空位个数,这个是可以使用状态中的信息计算出来的。有转移:

\[f(i,j,k,t)\leftarrow f(i,j,k,t)+p\cdot f(i-1,j,k,t) \]

  • 当前操作为 +,且我们打算加 \(A\) 中的元素进去。我们考虑可能会加 \(A_j\)(如果 \(t=0\))或者非 \(A_j\) 的某个 \(A_e\),但是具体加哪个先不确定,转移:

\[f(i,j,k+1,t)\leftarrow f(i,j,k+1,t)+f(i-1,j,k,t) \\ f(i,j,k+1,1)\leftarrow (i,j,k+1,t)+[t=0]\cdot f(i-1,j,k,t) \]

  • 当前操作为 -,且我们没有弹出 \(A_j\),而是某一个 \(A_e\),这时仍然不会确定具体加了哪个数,我们称这样弹出的某个 \(A_e\) 是不确定,我们仍不考虑计数,转移:

\[f(i,j,k-1,t)\leftarrow f(i,j,k-1,t) + [k>0][k>1\lor t=0]\cdot f(i-1,j,k,t) \]

  • 当前操作为 -,且我们决定弹出 \(A_j\),我们称这样的情况为结算。这时考虑 \(j\to j'\) 会发生改变,那么 \(A_{j'+1},\cdots,A_{j-1}\) 中的所有数应该都被弹出了,因此记录 \(z\) 表示总共有 \(z\) 个不确定的 \(A_e\),那么我们会从中选出 \(j-j'-1\) 个数确定下来,方案数为 \({x\choose j-j'-1}(j-j'-1)!\),转移:

\[f(i,j,0,0)\leftarrow f(i,j,0,0)+[k=t=1]{x\choose j-j'-1}(j-j'-1)! \cdot f(i-1,j,k,t) \]

Submission #55904711 - UTPC 2023 (atcoder.jp)

Miscellaneous

*[2024.06.28] 珍珠

一个 \(n\) 个珠子的项链可翻转、可旋转,染 \(01\) 颜色,问本质不同的项链中,满足存在一段子串为给定 \(s\) ,这些项链的个数。

\(n\le 10^9,m=|s|,m\le 22\)

考虑带权 Burnside 引理,转换成:

\[\frac{1}{|G|}\sum_{g\in G} W(C(g)) \]

其中 \(W(S)=\sum_{c\in S}w(c)\)\(w(c)\)\(1\) 当且仅当 \(c\) 满足题目要求,否则为 \(0\)

对于后面的和式,我们可以将 \(G\) 考虑分为两部分。

\(\Delta=(2,3,\cdots,n,1),\rho = (n,n-1,\cdots, 1)\)

会发现一部分是未反转的部分,记为 \(G_1=\{\Delta^t|t\in \N^*\}\)

而另一部分是反转过的,记为 \(G_2=\{\Delta^t\circ \rho|t\in \N^*\}\)

会发现有循环和反转在环上的性质显然有:\(G_1\cup G_2=G,G_1\cap G_2=\empty\)

且因为 \(\Delta^n=\iota\),因此 \(|G_1|=|G_2|=n\)


首先考虑 \(G_2\) 部分的 \(\sum W(C(g))\) 怎么算。

考虑 \(n\) 的奇偶性,如果 \(n\) 为奇数,就只有一种情况,否则会出现两种情况。这里我们讨论奇数,因为偶数的情况类似,只是更加麻烦不易理解。

考虑 \(n\) 为奇数的情况,对于一个 \(t\)\(\Delta^t\circ \rho=(t,t-1,\cdots,1,n,n-1,\cdots,t+1)\)。因此\(C(\Delta^t\circ \rho)\) 中的串一定都是由两个回文串拼接成的,但是如果我们再将 \(\Delta^{-t}\) 作用在这些串上,一定会一一对应 \(C(\rho)\) 中的串,因为置换有逆,因此这个作用可以看作 \(C(\Delta^t\circ \rho)\)\(C(\rho)\) 的双射。

而且同时知道 \(w(c)=w(\Delta^{k}*c)\),结合双射可知有 \(W(C(\Delta^t\circ \rho))=W(C(\rho))\)

因此可知 \(\forall f,g\in G_2,W(C(f))=W(C(g))\)

所以我们只需要考虑 \(W(C(\rho))\) 也就是有多少个长度为 \(n\) 的回文串满足首尾拼接成环后出现 \(s\)\(s^R\)。正着不太好做,考虑反着算,即计算有多少串不满足条件。

考虑记扫描字符串的状态 \((x,y)\) 表示以当前顺序扫描到的字符串,\(x\) 为最长的后缀长度满足这个后缀是 \(s\) 的前缀,\(y\) 为最长的后缀长度满足这个后缀是 \(s^R\) 的前缀。会发现这些状态只有 \(O(m)\) 个,可以通过 AC 自动机或者 KMP 自动机维护。

因此可以先 \(O(2^m)\) 枚举前面的串,判断首位拼接是否合法并记录到状态各自的初始方案数,随后进行矩阵乘法,然后再在中心判断是否出现 \(s,s^R\) ,如果没有出现,即可计入答案。

这部分时间复杂度为 \(O(m^3\log_2 n+m2^m)\)

如果要考虑 \(n\) 为偶数则还需考虑一种情况,就是两个奇长度的回文串的拼接,这种情况只能转换成一个单字符再加上一个奇回文串的情况,和上面的计算方法同理。


随后是 \(G_1\) 部分,这部分是简单的。

存在三个性质,强度是递进的:

  • \(\Delta^t\) 中有 \(\gcd (t, n)\) 个置换环。
  • \(\Delta^t\)\(i\)\(i+\gcd (t,n)\) (如果存在)一定属于一个轮换。
  • \(\Delta^t\)\(\forall 1\le i\le \gcd (t,n)\) 归属的轮换各不相同。

证明是简单的初等数论知识。

拥有这三个性质,我们知道对于 \(\Delta^t\) 考虑 \(W(C(\Delta^t))\) 只需要考虑长度为 \(\gcd(t,n)\) 并满足首位拼接后出现 \(s\)\(s^R\),统计这样的串的个数即为 \(W(C(\Delta^t))\)

所以考虑 \(F(x)\) 表示长度为 \(x\) 并满足首位拼接后出现 \(s\)\(s^R\) 的串的个数。

\(F(x)\) 可以用矩阵乘法计算。同样先计算出从头到尾没出现过 \(s\)\(s^R\) 的串的个数再做减法,我们可以先钦定末尾的状态是什么,做完矩阵乘法后再取出同样状态位置上的数计入答案即可。

那么知道有:

\[\begin{aligned} &\sum_{g\in G_1} W(C(g)) \\ =&\sum_{t=1}^nW(C(\Delta^t)) \\ =&\sum_{t=1}^n F(\gcd (t,n)) \\ =&\sum_{t|n}F(t)\sum_{k=1}^{\frac{n}{t}}[\gcd (k,\frac{n}{t})=1] \\ =&\sum_{t|n}F(t)\varphi (\frac{n}{t}) \end{aligned} \]

时间复杂度 \(O(d(n)m^3\log_2 n)\)


总的时间复杂度是 \(O(d(n)m^3\log_2n+m2^m)\)

// Not afraid to dark.

#include <bits/stdc++.h>
using namespace std;

clock_t start_time, end_time;
#define GET_START start_time = clock ();
#define GET_END end_time = clock (); fprintf (stderr, "TIME COSSEMED : %0.3lf\n", 1.0 * (end_time - start_time) / CLOCKS_PER_SEC);
#define inline __inline__ __attribute__ ((always_inline))

#define int long long

namespace io {
    // io
}

template < int SIZE > 
struct Automaton {
    int nex[SIZE + 5][2], len[SIZE + 5], tot;
    int a[SIZE + 5], cnt;
    inline void init (){
        nex[0][0] = nex[0][1] = len[0] = 0;
        tot = cnt = 0;
    }
    inline int new_node (){
        ++ tot;
        nex[tot][0] = nex[tot][1] = len[tot] = 0;
        return tot;
    }
    inline void Insert (int x){
        static int u, fw;
        a[++ cnt] = x;
        u = tot;
        fw = nex[u][x];
        nex[u][x] = new_node ();
        nex[tot][0] = nex[fw][0];
        nex[tot][1] = nex[fw][1];
    }
    inline int to (int x, int c){
        return nex[x][c];
    }
};

const int M = 22, mod = 1e9 + 7;

int n, m, l, ans;
char s[M + 5], t[M + 5];

Automaton < M + 1 > pre, suf;

int cn;
vector < pair < int , int > > state;
map < pair < int , int > , int > st;

struct matrix {
    int a[M << 1 | 1][M << 1 | 1];
    inline void init (int c = 0){
        for (int i = 0;i < cn;++ i)
            for (int j = 0;j < cn;++ j)
                a[i][j] = (i == j ? c : 0);
    }
    inline matrix friend operator * (const matrix a, const matrix b){
        static matrix res;
        static int i, j, k;
        static __int128 x;
        res.init ();
        for (i = 0;i < cn;++ i)
            for (j = 0;j < cn;++ j){
                for (k = 0, x = 0;k < cn;++ k)
                    x += (__int128) a.a[i][k] * b.a[k][j];
                res.a[i][j] = x % mod;
            }
        return res;
    }
    matrix (int c = 0){
        init (c);
    }
};

inline matrix power (matrix a, int p){
    matrix res (1);
    while (p > 0){
        if (p & 1)
            res = res * a;
        a = a * a;
        p >>= 1;
    }
    return res;
}
inline int power (int a, int p){
    int res = 1;
    while (p > 0){
        if (p & 1)
            res = res * a % mod;
        a = a * a % mod;
        p >>= 1;
    }
    return res;
}

inline int nex (int x, int c){
    static int p, q;
    tie (p, q) = state[x];
    p = pre.to (p, c);
    q = suf.to (q, c);
    return st[make_pair (p, q)];
}
inline bool tar (int x){
    return state[x].first == m || state[x].second == m;
}

inline void Calc_Reverse (){
    matrix res, g;
    int rt = 0;
    auto od_calc = [&] (){
        int T = 0;
        for (int i = 0, pt, j, u, v;i < cn;++ i)
            if (res.a[0][i]){
                u = i;
                for (pt = 0;pt < 2;++ pt){
                    v = nex (u, pt);
                    if (tar (v))
                        continue;
                    if (state[i].first >= state[i].second)
                        for (j = state[i].first;j >= 1;-- j){
                            v = nex (v, s[j] - '0');
                            if (tar (v))
                                goto E_1;
                        }
                    else
                        for (j = state[i].second - 1;j >= 0;-- j){
                            v = nex (v, s[m - j] - '0');
                            if (tar (v))
                                goto E_1;
                        }
                    rt = (rt + res.a[0][i]) % mod;
                    T += res.a[0][i];
                    E_1 : ;
                }
            }
    };
    for (int i = 0, c;i < cn;++ i)
        for (c = 0;c < 2;++ c)
            if (! tar (i) && ! tar (nex (i, c)))
                g.a[i][nex (i, c)] = 1;
    if (n & 1){
        int t = min (m, n >> 1);
        for (int i = 0, j, u, v;i < (1 << t);++ i){
            for (j = t - 1, v = 0;j >= 0;-- j){
                v = nex (v, i >> j & 1);
                if (tar (v))
                    goto E_2;
            }
            for (j = u = 0;j < t;++ j){
                u = nex (u, i >> j & 1);
                v = nex (v, i >> j & 1);
                if (tar (v))
                    goto E_2;
            }
            ++ res.a[0][u];
            E_2 : ;
        }
        res = res * power (g, (n >> 1) - t);
        od_calc ();
        rt = (power (2, (n + 1) >> 1) - rt + mod) * n % mod;
    } else {
        int t = min (m, n >> 1);
        for (int i = 0, j, u, v;i < (1 << t);++ i){
            for (j = t - 1, v = 0;j >= 0;-- j){
                v = nex (v, i >> j & 1);
                if (tar (v))
                    goto E_3;
            }
            for (j = u = 0;j < t;++ j){
                u = nex (u, i >> j & 1);
                v = nex (v, i >> j & 1);
                if (tar (v))
                    goto E_3;
            }
            ++ res.a[0][u];
            E_3 : ;
        }
        res = res * power (g, (n >> 1) - t);
        for (int i = 0, j, u;i < cn;++ i)
            if (res.a[0][i]){
                u = i;
                if (state[i].first >= state[i].second){
                    for (j = state[i].first;j >= 1;-- j){
                        u = nex (u, s[j] - '0');
                        if (tar (u))
                            goto E_4;
                    }
                } else{
                    for (j = state[i].second - 1;j >= 0;-- j){
                        u = nex (u, s[m - j] - '0');
                        if (tar (u))
                            goto E_4;
                    }
                }
                rt = (rt + res.a[0][i] % mod) % mod;
                E_4 : ;
            }
        res.init ();
        t = min (m, (n >> 1) - 1);
        for (int i = 0, j, u, v, s, pt;i < (1 << t);++ i){
            for (j = t - 1, u = 0;j >= 0;-- j){
                u = nex (u, i >> j & 1);
                if (tar (u))
                    goto E_5;
            }
            for (pt = 0;pt < 2;++ pt){
                v = nex (u, pt);
                if (tar (v))
                    goto E_6;
                for (j = s = 0;j < t;++ j){
                    s = nex (s, i >> j & 1);
                    v = nex (v, i >> j & 1);
                    if (tar (v))
                        goto E_6;
                }
                ++ res.a[0][s];
                E_6 : ;
            }
            E_5 : ;
        }
        res = res * power (g, (n >> 1) - 1 - t);
        od_calc ();
        rt = (power (2, n >> 1) + power (2, (n >> 1) + 1) - rt + mod) * (n >> 1) % mod;
    }
    ans = (ans + rt) % mod;
}
inline void Calc_inord (){
    vector < int > fac;
    int r = n;
    for (int i = 2;i * i <= r;++ i)
        while (r % i == 0){
            if (fac.empty () || fac.back () != i)
                fac.push_back (i);
            r /= i;
        }
    if (r > 1)
        fac.push_back (r);
    auto phi = [&] (const int &x){
        int res = x;
        for (int y : fac)
            if (x % y == 0)
                res /= y, res *= (y - 1);
        return res;
    };
    matrix A, g;
    for (int i = 0, c;i < cn;++ i)
        for (c = 0;c < 2;++ c)
            if (! tar (i) && ! tar (nex (i, c)))
                g.a[i][nex (i, c)] = 1;
    auto F = [&] (const int x){
        int res = 0;
        A.init ();
        for (int i = 0;i < cn;++ i){
            if (max (state[i].first, state[i].second) == m)
                continue;
            A.a[i][i] = 1;
        }
        A = A * power (g, x);
        for (int i = 0;i < cn;++ i)
            res = (res + A.a[i][i]) % mod;
        return (power (2, x) - res + mod) % mod;
    };
    int rt = 0;
    for (int i = 1;i * i <= n;++ i)
        if (n % i == 0){
            rt = (rt + phi (n / i) * F (i) % mod) % mod;
            if (n / i != i)
                rt = (rt + phi (i) * F (n / i) % mod) % mod;
        }
    ans = (ans + rt) % mod;
}

signed main (){
    GET_START

    io::read (n), io::read (m);
    io::get_string (s);
    if (n == 1)
        return putchar ('1'), putchar ('\n'), 0;
    if (n == 2){
        if (m == 2)
            return putchar ('1'), putchar ('\n'), 0;
        else
            return putchar ('2'), putchar ('\n'), 0;
    }
    pre.init ();
    for (int i = 1;i <= m;++ i)
        pre.Insert (s[i] - '0'), suf.Insert (s[m - i + 1] - '0');
    state.push_back (make_pair (0, 0));
    for (int i = 1, j, u;i <= m;++ i){
        for (j = 1, u = 0;j <= i;++ j)
            u = suf.to (u, s[j] - '0');
        state.push_back (make_pair (i, u));
        for (j = m, u = 0;j > m - i;-- j)
            u = pre.to (u, s[j] - '0');
        state.push_back (make_pair (u, i));
    }
    sort (state.begin (), state.end ());
    state.erase (unique (state.begin (), state.end ()), state.end ());
    cn = (int) state.size ();
    for (int i = 0;i < cn;++ i)
        st[state[i]] = i;
    
    Calc_Reverse ();
    Calc_inord ();
    io::write (ans * power ((n << 1) % mod, mod - 2) % mod), putchar ('\n');

    GET_END
    return 0;
}

Codeforces 1987G1 - Spinning Round (Easy Version)

考虑对于以 \(1\) 为根的情况,一定可以分成两段不交的路径,分别线段树维护单调栈扫前后缀即可。

但是需要注意的是不以 \(1\) 为根的情况,这时发现对于一段前缀,如果最终不以 \(1\) 为根且要以自身为一段路径,一定会以后面第一个大于当前后缀最大值的点为根,对于每个前缀这个点是唯一的,因此扫后缀是顺便记录即可。扫前缀时同理。

Submission #268435009 - Codeforces

Codeforces 1987G2 - Spinning Round (Hard Version)

不再考虑复杂的线段树,换换思维考虑 dp。

建出笛卡尔树,发现如果不是连向笛卡尔树上的父亲,一定就会连向从它往上的祖先中,第一个改变指向的点的父亲。

于是考虑 DP 方程,记 \(f(x,0/1/2)\) 表示 \(x\) 子树中最终从左侧跳出的最长路径、最终从右侧跳出的最长路径、分别从左右两边跳出的两条路径和的最大值。

转移是简单的。

Submission #268861885 - Codeforces

QOJ #3029. The Great Drone Show

相当于求每条边什么时候断裂。

考虑一条边是否断裂可以转化成存在一个整数 \(lim_e\),使得 \(|z_u-z_v|\le lim_e\) 时不断裂,\(|z_u-z_v|\ge lim_e+1\) 时断裂,如果固定 \(z_v\) 则转化成 \(z_u\) 一定要在某个区间内,这个 \(lim\) 是容易找到的。

考虑原图是一棵树怎么做,可以将儿子到父亲的边挂在父亲上,每次一个点的 \(z\) 发生变化,我们先更新它和父亲之间的边,具体地是改变一个区间,判断是不是断裂,是就删去,否则就会在父亲上去掉原本挂着的区间然后挂上新的。然后考虑儿子,把挂上来的区间中不包含新的 \(z_u\) 的全部删掉并断裂。这些都可以用 set 做到。

考虑原本的图与树的区别在于,给边定向之后变成 DAG,出度可能很大。但是因为平面图我们可以每次将度数最小的取出来并删掉,以这个删掉的顺序作为拓扑序,可以得到一个所有点出度小于等于 \(5\) 的 DAG,就做完了,时间复杂度 \(O(n\log_2 n)\)

Submission #467693 - QOJ.ac

[THUPC Final 2024 A] 古明地枣的袜子

将操作按更新位置排序之后分块处理,对于每个块分别更新答案。

如果是其他块影响了这个块,那么可以前缀和预处理出来。

那么本块中只剩 \(O(\sqrt n)\) 个操作,相当于也只有本质 \(O(n)\) 个询问。

这里可以线段树做到单块 \(O(n\log n)\) 而且很好写,常数也小。也可以分治,我们在块内分治并按操作时间归并,同时记录 \(f(l,r)\) 表示归并之后只考虑 \(l,r\) 之间的操作的答案,这个需要超级细节的统计做到左右的合并,分析时间复杂度 \(T(B)=2T(B/2)+B^2=B^2\),因此单块在 \(O(n)\) 时间内就可以完成合并。

之所以是超级细节是因为操作位置可以重复因此有些位置并不能作为起点,需要各种判断。

Submission #469407 - QOJ.ac

[The 2nd Universal Cup. Stage 18] F. Fortune Wheel

BFS 求出不走随机的时候的步数,然后枚举随机目标大小,设为 \(i\) 则会随机期望 \(\frac{n}{i}\) 次,然后跳到任意一个 dis 前 \(i\) 小的位置上,枚举即可。

Submission #469903 - QOJ.ac

Codeforces 741C - Arpa's overnight party and Mehrdad's silent

发现直接从 \(2i-1\) 连边到 \(2i\),显然如果这其中所有边的两个顶点颜色不同就会满足原本的条件,而且这是一个匹配。发现原图也是一个匹配,两个匹配任意合并一定是一个二分图,因此一定存在二分图染色。

Submission #269645326 - Codeforces

CodeForces 1656G - Cycle Palindrome

随便排出一个回文串,使 \(i\to p_i:c_i=c_{p_i}\)。发现如果交换 \(p_i,p_j\) 会将新的位置上的两个字符交换,同时有:如果 \(i,p_i\)\(j,p_j\) 原本不在一个环上,就会使他们合并成一个环;否则会分裂成两个环。

因此我们可以在回文串上从两边往中间合并,有不同的情况,但最终都是可以做的。

Submission #269652195 - Codeforces

CodeForces 1844E - Great Grids

发现有一个规律,如果把 \(A,B,C\) 分别在三进制上表示为 \(0,1,2\),则一行上的所有数和上一行的差值是相等的,列也是如此。

我们把第 \(i\) 行和第 \(i-1\) 行的差值记为 \(a_i\),第 \(j\) 列和第 \(j-1\) 列差值记为 \(b_j\)

发现原题给的限制相当于是告诉了 \(a_i-b_j=0\) 或者 \(a_i+b_j=0\),扩展域并查集即可。

Submission #269655464 - Codeforces

[The 1st Universal Cup. Stage 14] C.LaLa and Lamp

每个点可以得出一个关于三条线的 01 方程。但是发现显然有很多线性相关的。

因此枚举最后两行和左下角的情况,发现已经可以得出所有线,判断对于其他位置是否合法即可。

Submisson #470647 - QOJ.ac

CodeForces 1646F - Playing Around the Table

如果所有人手里的牌恰好是 \(1,2,\cdots,n\) 各一张,那么可以构造出一种 \(\frac{(n^2-n)}{2}\) 次操作的方案,这个是简单的。

考虑选一个位置,它存在重复的牌。从这个位置开始一直往后给自己重复的牌,考虑这样做一定可以执行到上面的状态,因为除了一开始的人,每个人做选择时考虑的实际上手里应该有 \(n+1\) 张牌。

接下来考虑为什么这样一定可以做到 \(\frac{(n^2-n)}{2}\) 次以内。是因为每种牌如果出手就不会再回到自己这里,因为如果这样,那么这种牌一定不止 \(n\) 张。因此每一步都不是无用的。考虑每一个牌最多会移动 \(\frac{(n^2-n)}{2}\) 次,但是总共有 \(n\) 种牌,但是一次会移动 \(n\) 步,所以总共的步数也是 \(\frac{(n^2-n)}{2}\) 的。

合起来满足题目的要求。

Submission #269693347 - Codeforces

CodeForces 1693F - I Might Be Wrong

发现每次排序时 \(cnt_0=cnt_1\),否则通过调整法一定可以不劣:

不妨设 \(cnt_0>cnt_1\)

发现如果 \(s_l=0\),直接 \(l'=l+1\) 更优;如果 \(s_l=1\),那么一定存在一个前缀 \([l,r']\) 使 \(cnt'_0=cnt'_1\),此时我们可以做两次操作:\([l,r'],[l+1,r]\) 与原本的操作代价一样。

所以从前往后,枚举起点,考虑最大的 \(cnt_0=cnt_1\) 的区间即可。case 比较多。

Submission #269712451 - Codeforces

QOJ #895. Color

等价于最终图上对于每个颜色存在 \(\frac{m+1}{2}\) 的匹配。

记一个颜色的 \(s_0,s_1,s_2\) 分别表示匹配的两个点都没在当前图中的个数,一个点在图中的个数,两个点在图中的个数。

考虑当有一种颜色有 \(s_1+s_2>\frac{m+1}{2}\) 时无解,否则有解。

我们考虑怎么将 \(n\) 扩展为 \(n+1\)

建出二分图,左边 \(m\) 个点表示颜色,右边前 \(n\) 个点表示前面的点,最后一个点表示不连,最后一个点的出流量为 \(m-n\),其他点都是 \(1\)。如果一个点在原图没有这种颜色的边,就连边;然后对于每个颜色,向第 \(n+1\) 个点连 \(2s_0\) 个边。

如果有完美匹配则可以扩展,如果一条边满流,说明新加的点和这个边的右点连接,颜色为左点。

发现将 \(n+1\) 这个点拆成 \(m-n\) 个点,每个点的出度或者入度均为 \(m+1-n\),因此一定存在完美匹配。

Submission #471421 - QOJ.ac

UOJ #354. 新年的投票

Sub 3:

我们执行这样一个策略:第 \(i\) 个人观察前面 \(i-1\) 个人中是否有 \(1\)。如果有则投 \(0\) 票;否则把自己当作 \(1\),向全体的异或和投出 \(2^{i-1}\) 票。

这样最终绝对的话语权会掌握在第一个为 \(1\) 的人手中,他投出的票比其他所有人加起来还要多。而且他投出的是正确的票。

这时只会在全为 \(0\) 时错误。

Sub 1&2:

我们可以分成 \((1),(2,3),(4,5,6,7),(8,9,10,11,12,13,14,15)\) 这些组,然后把一组看作一个人执行 Sub 3 的操作。

因为第一组一定不会投 \(0\) 票,所以投 \(0\) 票的组可以看作一半投 \(0\) 一半投 \(1\),投 \(2^{i-1}\) 票则可以看作每个人投了一票给同一个答案。

总共错误的情况是:\({1\choose 0}({2\choose 0}+{2\choose 2})({4\choose 0}+{4\choose 2}+{4\choose 4})\cdots=2^{1-1}\times 2^{2-1}\times2^{4-1}\times 2^{8-1}=2048\)

Sub 4:

很神秘,我的说明并不符合自然的思考路线,因此仅仅作为答案。

记每个位置对应为 \(x_i=0/1\)\(s=\sum x_i\)

考虑把给异或和为 \(1\) 投票记为投 \(-1\) 票,\(0\) 记为投 \(1\) 票。那么最后结果和票数的正负有关。

我们想构造出一个关于 \(s\)\(k\) 次多项式 \(p\),使得 \(p(s)\) 在最多的 \(s\) 上取到正确的符号(负则表示投票结果为 \(1\),相反亦然),此时将 \(s=\sum x_i\) 代入。对于一个大小小于等于 \(k\) 的可重集 \(S\)\(\prod_{i\in S}x_i\) 存在一个系数。如果这个集合中的所有 \(x_i\) 均为 \(1\),那么票数会加上这个系数。我们每次补上 \(S\)\([1,n]\) 上的补集中的最小值直到 \(S\) 对应的不重集大小等于 \(k\),然后将这个系数加在这个集合对应的人的策略上(如果这些 \(x_i\) 均为 \(1\) 时,只有这个人会投出对应的票数)。

考虑到二项分布,结合 \(n=12,k=7\),最终得出我们在 \(s\in [3,9]\) 取到正确的值,\(s<3\) 时取负号,\(s>9\) 时取正号。因此在 \(s=0,2,11\) 时错误,总共有 \({12\choose 0}+{12\choose 2}+{12\choose 11}=79\) 种错误情况(逆天)。

考虑怎么构造 \(p(s)\),我们知道应该有:

\[\frac{p(s)}{|p(s)|}=(-1)^{3}\prod_{j=0}^6 (-1)^{[s>j+3]} \]

那么不难构造出:

\[p(s)=(-1)^3\prod_{j=0}^6(3+j+0.5-s) \]

这个会存在小数的情况,但是我们可以在乘式的每一项中都乘 \(2\),总共会乘以 \(128\),就不会出现小数了。通过计算器算出乘以 \(128\) 后最终的票数不会超过 \(5\times 10^7\)。于是最终可以得出一个:

\[p(s)=\sum_{j=0}^7c_{j}s^j \]

\(s=\sum x_i\) 代入,然后将系数化到相应的人处即可。

// Not afraid to dark.

#include <bits/stdc++.h>
using namespace std;

#define inline __inline__ __attribute__ ((always_inline))

//#define int long long

namespace io {
    // io
};

inline void VOTE (int x, int y){
	if (x == 1)
		io::write (y), putchar (' ');
	else
		io::write (- y), putchar (' ');
}

namespace SUB_1_2 {
	inline void Solve (){
		static int bel[16], vs[5];
		bel[0] = 1;
		bel[1] = bel[2] = 2;
		bel[3] = bel[4] = bel[5] = bel[6] = 3;
		bel[7] = bel[8] = bel[9] = bel[10] = bel[11] = bel[12] = bel[13] = bel[14] = 4;
		for (int i = 0;i < 15;++ i){
			int xors;
			for (int s = 0;s < (1 << 14);++ s){
				vs[1] = vs[2] = vs[3] = vs[4] = xors = 0;
				for (int j = 0;j < 14;++ j){
					vs[bel[j + (j >= i)]] ^= s >> j & 1, xors ^= s >> j & 1;
//					printf ("(%d %d)", j + (j >= i), bel[j + (j >= i)]);
				}
				for (int k = 1;k <= bel[i];++ k)
					if (k == bel[i]){
//						if (i == 0 && s == 0)
//							io::write (xors ^ vs[k] ^ 1);
						io::write (xors ^ vs[k] ^ 1);
					} else
					if (vs[k] == 1){
//						if (s == 1)
//							putchar ('0');
						io::write (i & 1);
						break;
					}
			}
			putchar ('\n');
		}
	}
}
namespace SUB_3 {
	inline void Solve (){
		for (int i = 0;i < 15;++ i, putchar ('\n'))
			for (int s = 0;s < (1 << 14);++ s){
				int xors = 1;
				for (int j = 0;j < 14;++ j)
					xors ^= s >> j & 1;
				for (int j = 0;j <= i;++ j)
					if (i == j){
						VOTE (xors, 1 << i);
					} else
					if (s >> j & 1){
						VOTE (0, 0);
						break;
					}
				putchar (' ');
			}
	}
}

namespace SUB_4 {
	#define popcount(x) __builtin_popcount (x)
	#define lowb(x) ((x) & - (x))
	inline int lowbit (int x){
//		printf ("%d\n", lowb (((1 << 12) - 1) ^ x));
		return lowb (((1 << 12) - 1) ^ x);
	}
	int res[8];
	int sum[1 << 12], ans[1 << 12][1 << 7];
	inline void Solve (){
		res[0] = 1;
		for (int k = 0;k < 7;++ k){
			for (int i = 6;i >= 0;-- i){
				res[i + 1] -= res[i] * 2;
				res[i] = res[i] * (7 + k * 2);
			}
//			for (int i = 0;i < 8;++ i)
//				printf ("%d ", res[i]);
//			puts ("");
		}
		sum[0] = 1;
		for (int k = 0;k <= 7;++ k){
			for (int i = (1 << 12) - 1;i >= 0;-- i){
				if (sum[i] == 0)
					continue;
				int ss = sum[i];
				int u = i, s = 0;
				while (popcount (u) != 7){
//					printf ("%d\n", u);
					u |= lowbit (u);
				}
				for (int j = 11;j >= 0;-- j)
					if (u >> j & 1){
						s <<= 1;
						s |= i >> j & 1;
					}
				for (int j = 0;j < (1 << 7);++ j)
					if ((j & s) == s)
						ans[u][j] += ss * res[k];
//				ans[u][s] += ss * res[k];
				sum[i] = 0;
				for (int j = 0;j < 12;++ j)
					sum[i | (1 << j)] += ss;
			}
		}
		for (int i = 0;i < (1 << 12);++ i)
			if (popcount (i) == 7){
				for (int j = 0;j < (1 << 7);++ j)
					io::write (ans[i][j]), putchar (' ');
				putchar ('\n');
			}
	}
}

signed main (){
//	freopen ("vote1.ans", "w", stdout);
//	freopen ("vote2.ans", "w", stdout);
//	freopen ("vote3.ans", "w", stdout);
//	freopen ("vote4.ans", "w", stdout);
//	SUB_1_2::Solve ();
//	SUB_3::Solve ();
//	SUB_4::Solve ();
	return 0;
}

提交记录 #696324 - Universal Online Judge (uoj.ac)

UOJ #698. 枪打出头鸟

线性基求交和最大权的模板题目。

提交记录 #701518 - Universal Online Judge (uoj.ac)

~[THUPC Final 2023 F] Freshman Dream

因为题目的限制,对于 \(B\) 每一列单独列出异或方程组(显然 \(B\) 每一列在各自的方案上是不影响的),移项使右边全部变为 \(0\)。把左边的方程组表示成矩阵 \(A\),可以发现对于 \(B\) 中的每一列 \(A\) 应该是均匀随机的,我们可以证明它的自由元有极大概率极小。

具体地,假设 \(A\) 的秩为 \(k\)。因为线性空间 \(A\) 和它的零空间有双射关系,也就是数量一致。我们估计 \(k\) 维零空间对应的 \(A\) 的个数一定小于等于 \({2^n\choose k}\)。同时 \(A\) 中所有向量都不落在钦定零空间上的概率是 \((\frac{1}{2^k})^n\),因为我们任意选择 \(n-k\) 个确定的元,剩下的 \(k\) 个位置都只有一种选择。

因此大概估算出 \(A\) 的秩为 \(k\) 的概率为 \(O(\frac{(2^n)^{\underline k}}{k!}\times \frac{1}{2^{kn}})=O(\frac 1 {k!})\)

(我是嘴巴大师)

[HNOI 2024] 卡农

考虑实际上相当于选出 \(m\) 个数,值域在 \([0,2^n-1]\) 中,他们的异或和为 \(0\)。实际上在选出 \(m-1\) 个数时,第 \(m\) 个数就是唯一确定的了。

考虑感觉 \(m\) 个数的集合不是很好做,因为记 \(f(m)\) 为有序地选的方案数。

容易推出递推式:

\[f(m)={2^n-1\choose m-1}-f(m-1)-(m-1)(2^n-m+1)f(m-2) \]

然后就简单了。

Source code - Virtual Judge (vjudge.net)

Atcoder AGC060F - Spanning Trees of Interval Graph

考虑矩阵 \(A\) 可逆时有以下等式:

\[\left[ \begin{matrix} I & 0 \\ V^T & I \end{matrix}\right] \left[ \begin{matrix} A+UV^T & U \\ 0 & I \end{matrix}\right] \left[ \begin{matrix} I & 0 \\ -V^T & I \end{matrix}\right] = \left[ \begin{matrix} A & u \\ 0 & I + V^TA^{-1}U \end{matrix}\right] \]

因此得到 \(A\) 可逆时有 \(\det (A+UV^T)=\det A\det (I+V^TA^{-1}U)\) 成立。

那么放在矩阵数定理上容易想到度数矩阵 \(D\) 和邻接矩阵 \(A\) ,如果可以将 \(A\) 表示成 \(UV^T\),则有 \(\det (D-A)=\det D\det(I-V^TD^{-1}U)\) 成立。

那么原题如何构造呢。

考虑点边容斥,有:

\[\begin{aligned} &U_{i,2j-1}=V_{i,2j-1}=[j\in [l_i,r_i]] \\ &U_{i,2j}=[j\in [l_i,r_i)] \\ &V_{i,2j}=-[j\in [l_i,r_i)] \end{aligned} \]

可以发现:

\[\begin{aligned} A_{ij}=&\sum_{k} U_{ik}V_{kj}^T\\=&\sum_{k} U_{ik}V_{jk}\\=&\sum_{k}[k\in [l_i,r_i]][k\in [l_j,r_j]]-\sum_{k}[k\in[l_i,r_i)][k\in [l_j,r_j)]\\=&[[l_i,r_i]\cap [l_j,r_j]\ne \empty] \end{aligned} \]

然后求 \(\det (I-V^TD^{-1}U)\) 即可。

Submission #55698980 - AtCoder Grand Contest 060

[HAOI2015] 数组游戏

考虑将原题转化成每次可以在后面放石子,让最后所有石子均为偶数的玩家获胜。

于是可以得到: \(sg(i)=\mathrm{mex}_{k> 1} \{\oplus_{j=2}^k sg(j)\}\)

然后发现 \(\lfloor\frac{n}{i}\rfloor\) 相等的一段是 \(sg(i)\) 是相等的。

证明也比较简单,考虑 \(\lfloor\frac{n}{x}\rfloor=\lfloor\frac{n}{y}\rfloor\) 的一段,那么 \(\lfloor\frac{n}{kx}\rfloor=\lfloor\frac{n}{ky}\rfloor\)

然后发现可以枚举每一段右端点为 \(x\),然后对 \(\lfloor\frac{n}{x}\rfloor\) 进行整除分块。

可以发现,如果 \(\lfloor\frac{\lfloor\frac{n}{x}\rfloor}{p}\rfloor=\lfloor\frac{\lfloor\frac{n}{x}\rfloor}{q}\rfloor\) 那么有 \(\lfloor\frac{n}{px}\rfloor=\lfloor\frac{n}{qx}\rfloor\)\(sg\) 值是一样的。

时间复杂度:

小于 \(\sqrt n\) 的一段:\(\sum_{i=1}^{\sqrt {n}} \sqrt{\frac{n}{x}}\approx {\sqrt {n}}\int_{1}^{\sqrt {n}} x^{-\frac {1} {2}}=\sqrt {n} (2n^{\frac {1} {4}} - 2)=O(n^{\frac{3}{4}})\)

大于 \(\sqrt n\) 的一段,总共有 \(\sqrt n\) 个不同的 \(\lfloor \frac {n}{x} \rfloor\) 对应的区间,每个区间进行整除分块,时间复杂度 \(O(\sqrt n\cdot \sqrt {\frac{n}{\sqrt n}})=O(n^{\frac{3}{4}})\)

所以总共的时间复杂度为 \(O(n^{\frac 3 4})\)

Source code - Virtual Judge (vjudge.net)

[SNOI2020] 取石子

是我太菜了,对不起。

结论是:将 \(n-1\) 进行斐波那契分解(不考虑第 \(0\) 位,只考虑 \(F_1=1,F_2=2,\cdots\)),那么先手必胜当且仅当 \(k\) 大于等于斐波那契分解上的最后一位。


证明

  • \(k\ge F_{last}\) 时,减去 \(F_{last}\);轮到下一个人操作时一定有 \(F_{last'}\ge F_{last+2}> F_{last}\)(因为根据 Zeckendorf's representation 可知一定不会出现连续的两个 \(1\)),因此不可能直接减去最后一位。
  • 如果减去任何一个小于 \(F_{last}\) 的数为 \(D\),记他在斐波那契分解上的最后一位为 \(F_d\),则减去 \(D\) 之后的数的最低位最大为 \(F_{d+1}\),一定不会超过 \(2F_d\),因此下一个人一定可以操作。而且下一个人如果按照上面的策略操作,则当前这个人永远不能获得减去分解最后一位的机会。所以这时这个操作的一定不会赢。

做一个斐波那契的数位 dp 即可。

Source code - Virtual Judge (vjudge.net)

Atcoder AGC043C - Giant Graph

显然我们要选尽可能多的 \((u_1+u_2+u_3)\) 大的点。

因此考虑贪心,记录 \(f_{i,j,k}\) 表示是否选了,\(1\) 表示选了,\(0\) 表示没选。

那么应该有:

  • 如果 \((i,j,k)\) 的所有下标和更大的相邻点的 \(f\) 均为 \(0\),则 \(f_{i,j,k}=1\)
  • 否则 \(f_{i,j,k}=0\)

发现这个很像博弈里面的胜负关系。

最后发现 \(f_{i,j,k}=1\) 当且仅当如下游戏先手必败:

初始对于 \(G_1\) 中的 \(i\)\(G_2\) 中的 \(j\)\(G_3\) 中的 \(k\) 这三个位置分别放上一枚棋子。两个人轮流操作,一次可以选择一枚棋子,移动到相邻且更大的位置上,无法移动的人失败。

显然 \(G_1\) \(G_2\) \(G_2\) 对应的三个有向图游戏 \(S_1,S_2,S_3\) 是独立的,因此有:

\[SG(S_1(i)+S_2(j)+S_3(k))=SG_{(S_1)}(i)\oplus SG_{(S_2)}(j)\oplus SG_{(S_3)}(k) \]

发现 \(SG_{(S_k)}(x)\) 是容易计算的,而 \(f_{i,j,k}=1\) 当且仅当 \(SG_{(S_1)}(i)\oplus SG_{(S_2)}(j)\oplus SG_{(S_3)}(k)=0\)

到这里基本上就已经形势明了。异或式子总有办法做的,但是发现实际上因为 \(\forall k,x: SG_{S_k}(x)\le 2\sqrt m_k\),所以直接枚举前两个的取值就可以了。

Submission #55737901 - AtCoder Grand Contest 043

ural 2132. Graph Decomposition. Version 2 / 2019 高中数学联赛真题

这么简单的问题放高联合适吗

通过构造方法得出不合法当且仅当每个连通块的边数都为偶数。

可以建出 dfs 树,这样可以保证没有横叉边。

然后我们可以从叶子开始从深向浅依次对执行以下操作:

对于点 \(u\)

记录 \(son_u\) 表示儿子中没处理掉的与 \(u\) 相连的边的集合,\(back_u\) 表示 \(u\) 点返祖边集合。

显然我们可以在 \(\{back_u,son_u\}\) 集合内配对,最后只有剩一条或者不剩余的情况情况,分类讨论:

  • 如果剩余一条,就与 \((u,father_u)\) 配对。
  • 否则将 \((u,father_u)\) 加入 \(son_u\)

归纳发现这样的构造方式无解当且仅当 \(2\nmid m\),但这个条件是无解的必要条件,因此这个问题就做完了。

好像比大多数人的方法还要简单一些。

Source code - Virtual Judge (vjudge.net)

~Unknown Problem of Minimum Spanning Tree(2024.07.19 from zjk)

\(n\) 个点 \(m\) 条边的无向图,每条边的边权是 \(c_i + x\)\(c_i − x\)\(q\) 次询问,每次给一个 \(x\),求最小生成树边权和。

\(n, q ≤ 10^5 , m ≤ 4 \times 10^5\)

\(x\) 看作横轴,边权看成纵轴。我们考虑边权 \(c_i+vx\) 的排名一定是关于 \(x\) 单调的。

进一步地,考虑对于每条边一定存在一个分界点,使得在分界点的一边它一定在最小生成树中,而在另一边它一定不在最小生成树中。

思考怎么使用这个性质。

显然的想法是二分,但是实际上操作起来非常之复杂。问题相当于转化成 \(O(\log_2 n)\) 合并两个并查集维护的连通关系,感觉不太会做。

但是说到连通性,考虑判断这些边是否在最小生成树中的条件互相是有联系的,因此尝试将 \(m\) 条边一起考虑。

由二分想到分治,于是我们对 \((1,n)\) 做分治。

考虑子问题 \((l,r,E)\) 表示考虑 \(x\in[l,r]\),可选的边集为 \(E\) 时的情况。在 \(x=mid\) 处求出任意一棵最小生成树 \(T\),此时对于一条边 \(c_i-x\),有:

  • 如果它在 \(T\) 中,那么当 \(x>mid\) 时它一定也在最小生成树中,因为不会有边在 \(x>mid\) 且增大时(原本比它大)变得比它小,因此当 \(x\in[mid+1,r]\) 时,根据 Kruskal 的思路它一定会存在于最小生成树(之一)中。因此在考虑 \([mid+1,r]\) 时可以直接将这条边连接的两点合并起来(这里可以使用可持久化并查集),而不用将这条边传进 \(E_{[mid+1,r]}\) 中;只用在 \(E_{[l,mid]}\) 中传入这条边即可。
  • 如果它不在 \(T\) 中,那么当 \(x\le mid\) 时它一定可以不在最小生成树中,那么就只用在 \(E_{[mid+1,r]}\) 中传入这条边即可。

对于 \(c_i+x\) 也是同理,然后就可以分治进入 \((l,mid,E_{[l,mid]}),(mid+1,r,E_{mid+1,r})\) 中即可,在 \(l=r\) 的子问题中可以计算出最小生成树的边权和。

这样做正确性是可以保证的。但是真的不会超时吗?

显然不会,因为如果一条边在一个子问题中被考虑,则在左右两个子问题中只有一个边集会包含它,因此最多只会有 \(O(\log_2 n)\) 个子问题包含它,因此时间复杂度 \(O(n\log_2 n \log_2 m)\)。但是因为边权的形式比较简单,所以预处理排序之后可以归并做到 \(O(n\alpha (m)\log_2 n+m\log_2 m)\)

闲话:以上实以分治之名,行整体二分之事。

Atcoder AGC038F - Two Permutations

这个题目,我好像在哪里见过。

考虑显然的一点是因为有排列的限制所以对于一个置换环,要么全部 \(a_i=p_i\) 要么全部 \(a_i=i\)\(b_i\)\(q_i\) 也是同理。

所以对于每个置换环可以有两种选择,一种是选择 \(a_i=i\) 另一种是 \(a_i=p_i\)。发现置换环上可以把所有点合并起来。

假设 \(i\) 之于 \(p\) 的置换环是 \(x_i\),之于 \(q\) 的置换环是 \(y_i\)

对于一个置换环 \(c\),如果他中所有的 \(a_i=p_i\)\(b_i=q_i\) 同理),那么记为 \(c\in S\),否则记为 \(c\in T\)。如果先假设全部都能满足,再减去代价,我们考虑是否可以构思代价的

考虑先假设全部都能满足,再考虑减去代价。那么应该存在各种情况对应的代价:

  • 如果 \(i=p_i=q_i\),则代价直接为 \(1\)
  • 如果 \(i=p_i\ne q_i\),则 \(y_i\in S\) 时代价为 \(1\)
  • 如果 \(i=q_i\ne p_i\),则 \(x_i\in S\) 时代价为 \(1\)
  • 如果 \(i\ne p_i,i\ne q_i\),则 \(x_i,y_i\in S\) 时代价为 \(1\),如果在此基础上还有 \(p_i=q_i\)\(x_i,y_i\in T\) 时代价也为 \(1\)

但是两个点同时在某个集合时的贡献不知道怎么算,这时候怎么办啊。

通常会考虑反转定义,设 \(y_i'\in S\) 实际上表示 \(y_i\in T\),其他 \(y\) 同理。则只需要对 \(x_i\)\(y_i'\) 考虑最小割即可。因为 \(x\) 之间和 \(y\) 之间各自没有限制,因此可以得到原图是一张二分图,时间复杂度 \(O(n^{\frac{3}{2}})\)

Submission #55847391 - AtCoder Grand Contest 038

Codeforces 1250K - Projectors

首先贪心是肯定不行的,容易举出反例。

但是对于 \(k\) 可重区间存在的性质是,满足为 \(k\) 可重区间集的充要条件是任意一个点最多被 \(k\) 个区间覆盖。这个几乎就等于是定义最小割为 \(k\)

考虑放在这个题上就是,要求将研讨会分出一部分当作讲课,使得两部分各自在时间轴上最多有 \(x,y\) 个区间交于一点。

那么其实对于每个时刻,只要我们知道有多少普通投影仪被用了,就知道多少研讨会需要使用高清投影仪,也就知道了这个点对于高清和普通分别被多少区间覆盖了。由此推断出,每个时刻使用的普通摄像机一定是一个范围,相当于每个时刻闲置的普通摄像机个数一定是一个范围,整个无解当且仅当某个时刻限制的普通摄像机个数超过了范围。

随便推一下范围,发现每个时刻闲置的普通摄像机个数必须小于等于 \(x+y-sum_t\),其中 \(sum_t\) 表示当前时刻 \(t\) 总共正在进行 \(sum_t\) 个(研讨会+讲课)。

然后就对研讨会连 \((l,r,1)\) 这样的边,对闲置连 \((t,t+1,x+y-sum_t)\) 这样的边。

如果最大流大于等于 \(y\) 那么就是对的,但是为了构造方案我们让最大流最多只有 \(y\),然后去得到方案就行了。

Submission #272261671 - Codeforces

QOJ #8506. Beating the Record

非常好的题目,但是为什么精度要设置得这么高啊。

首先可以列出的方程是:记 \(f(i,t)\) 表示这把游戏已经过了 \(t\) 秒,当前在第 \(i\) 个 level 处选择策略,期望还有 \(f(i,t)\) 的时间可以打破记录,于是有:

\[\begin{cases} f(i,t)=0 & i=n+1\land t<T \\ f(i,t)=\min \{f(1,s)+s, T_1[i],T_2[i]\} & \mathrm{otherwise} \\ \end{cases} \]

其中 \(T_k[i]\) 表示在第 \(i\) 个 level 处选择策略 \(k\) 的期望剩余时间,显然有:

\[T_k[i]=P_k[i] \big(f(i+1,t+G_k[i])+G_k[i]\big) + (1-P_k)\min\{f(i+1,t+B_k[i])+B_k[i],f(1,s)+s\} \]

然后可以人类智慧一下,称 \(\min\) 式的展开为 \(\min_{x,y,z,p} \{z+p\cdot\min(x,y)\}\to \min_{x,y,z,p}\{z+px,z+py\}\),那么我们对上面的等式除了 \(f(1,s)\) 的所有非 \(0\)\(f\) 进行展开 。于是显然可以得到:一定会存在固定的 \(x,y\) 集合满足:

\[f(1,s)=\min_{x,y}\{x+y\cdot f(1,s) \} \]

或者更改一下说法,在决策树上列举出每种情况,可以得到:

\[f(1,s)=\min_{E[t],p}\{p\cdot E[t]+(1-p)(f(1,s)+s)\}=\min_{E[t],p}\{p\cdot E[t]+(1-p)s+(1-p)f(1,s)\} \]

相当于是有 \(p\) 的概率可能达成目标,这时的结果可以用 \(E[t]\) 表示打破纪录的情况下用时的期望,还有 \((1-p)\) 的概率重开得到 \(f(1,s)+s\)

发现就算不知道 \(f(1,s)\) 的值是多少,这个 \(\min\) 式一定都是成立的,而且 \((E[t],p)\) 都是不需要知道 \(f(1,s)\) 的值就可以求出的。

然后我们可以把式中的 \(p\cdot E[t]+(1-p)s\) 看作是截距,\(1-p\) 看作斜率,\(f(1,s)\) 看作横坐标,那么 \(\min\) 式的图像是一个上凸的函数,记为 \(g(x)\)。想要求出 \(f(1,s)\) 是多少,实际上就是求 \(y=x\)\(g(x)\) 的交点的坐标值。那么要是有多个交点或者没有交点怎么办?事实上不会的,因为所有截距 \(p\cdot E[t]+(1-p)s\) 一定大于 \(0\),而且所有斜率 \(1-p\) 应该都有 \(0\le 1-p<1\),小于 \(y-x\) 的斜率。所以图像应该满足: \(g(x)\)\(0\) 点的值一定大于 \(0\),之后一直在 \(y=x\) 的上方,直到在某个位置与 \(y=x\) 图像相交,之后一直在 \(y=x\) 图像的下方。因此 \(g(x)\)\(x\) 的相对大小关系是单调的。然后就可以二分了。

Submission #490559 - QOJ.ac

QOJ #8759. 小班课

为啥 jiangly 选的题目都这么思维啊/kk,搞得我感觉像没学过 OI 一样。

考虑答案的上界是什么?就是显然地建图,然后求出二分图最大匹配。根据构造题一般结论不会太复杂的原则,猜测答案就是这个最大匹配的大小。最终果然是对的。

考虑对于当前的情况,取出任意一个最大匹配,称一个人的正确选择是让他第一个选课时他的选择,当前选择是他在取出的最大匹配中的选择,这时分类讨论:

  • 如果存在一个人,当前选择和正确选择一致,那么直接把这个人删去,并把课程的容量减一。
  • 否则,如果存在一个正确选择,使得没有一个当前选择等于这个正确选择,那么这个人可以直接把当前选择改成正确选择,回到第一种情况。
  • 否则,考虑建出关于课程的图,对于每一个匹配中的人从他的当前选择连向正确选择,那么可以知道的是,原图最多有 \(n\) 个有邻边的点,而有 \(n\) 条边,而且\(n\) 个点中任意一个都有出边。所以一定存在一个环,那么我们把这个环上所有边对应的某些人的当前选择改成正确选择,显然仍然流量正确。

于是得证。

但是直接这样构造时间复杂度巨高无比,喜提最劣解。

还可以匈牙利算法构造,这是最优解的做法。

Submission #491285 - QOJ.ac

~QOJ #8757. 图

考虑拆出 \(k\) 个图 \(G_1,G_2,\cdots,G_k\),要求如果 \(u,v\)\(G_i\) 中连通,那么要求一定有 \(u,v\)\(G_1,G_2,\cdots,G_{i-1}\) 中都是连通的。

考虑这些图初始都为空,每次加入一条边 \((u,v)\),我们会从 \(G_1\) 开始向后枚举,找到第一个 \(u,v\) 不连通的图 \(G_i\),将 \((u,v)\) 加入;没找到就不管它。

那么因为 \(k=\lceil\frac{m}{n-1}\rceil\),然后每个 \(G_i\) 中最多有 \(n-1\) 条边,因此 \(G_k\) 中一定有边。因此存在 \(u,v\)\(G_k\) 中连通,所以有 \(G_1,G_2,\cdots,G_k\) 中它们都连通,因此可以找到 \(k\) 条没有边相交的路径。

(我是嘴巴大师)

~QOJ#8780. Training, Round 2

显然一系列 biset 操作可以在 \(O(\frac{n^3}w)\) 的时间复杂度内解决这个问题。

但是会发现,实际上我们的 dp 等价于每次选择在二维平面上选择一个矩形,将其中染色过的点 \((x,y)\) 的上方 \((x,y+1)\) 和右方 \((x+1,y)\) 染色。我们称 \((x,y)\to (x+1,y),(x,y+1)\) 的操作为使用,那么每个点只有第一次使用是有意义的,我们考虑只操作第一次使用,那么每个点是会被操作一次。染色但是还没有被操作的情况的维护是简单的。

本文作者:saubguiu

本文链接:https://www.cnblogs.com/imcaigou/p/18285817

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   saubguiu  阅读(84)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
💬
评论
📌
收藏
💗
关注
👍
推荐
🚀
回顶
收起
🔑