HNOI2019 DAY2 题解

校园旅行

题目链接

题解:

这可能是 HNOI2019 里最神的一题了 orz。

首先不难发现一个暴力,记 f ( i , j ) f(i,j) f(i,j) 表示点对 i , j i,j i,j 之间是否存在回文路径,枚举他们的出边暴力转移即可。初始的时候所有的 f ( i , i ) f(i,i) f(i,i) 和两个颜色相同且相邻的点 f ( a , b ) f(a,b) f(a,b) t r u e true true,扔到队列里 b f s bfs bfs 即可。不难发现任意一对边至多只会被枚举 4 4 4 次,因此复杂度是 O ( m 2 ) O(m^2) O(m2) 的。

然后就是神仙想法了,我们可以减少边的数量。令连接两个颜色相同的点的边为同色边,否则为异色边。考虑最终的回文串长啥样,肯定是一段同色边一段异色边不断接起来。由于一条边可以来回走,因此我们要保证对应同色边或异色边的条数的奇偶性相同。也就是说,我们只在意对应的奇偶性而不在意具体的路径长度。

不妨把两种边分开考虑,把所有异色边全部删掉,那么剩下的图中一个连通块里的所有点颜色全部相同。如果一个连通块是二分图,那么就意味着无论怎么走,任意两个点之间路径长度的奇偶性都是固定的。因此对于是二分图的连通块,我们只要保留任意一棵生成树即可。

否则的话,就一定存在奇环。由于我们并不在意路径长度而只在意奇偶性,因此给随便一个点连一条自环即可。

对于异色边,连出来的连通块显然就是一个二分图,也任意保留一棵生成树即可。

因此,边数就变成了 O ( n ) O(n) O(n) 的,直接跑上面的暴力即可 O ( n 2 ) O(n^2) O(n2) 解决。

#include 
using namespace std;
typedef long long ll;

const int MAXN = 5005, MAXM = 1000005, MAXQ = 13000005;
struct Edge { int to, next; } edge[MAXM], gra[MAXM];
int head[MAXN], hd[MAXN], val[MAXN], n, m, Q, tot;
void addedge(int u, int v) {
	edge[++tot] = (Edge) { v, head[u] };
	head[u] = tot;
}
void addgra(int u, int v) {
	gra[++tot] = (Edge) { v, hd[u] };
	hd[u] = tot;
}
int clr[MAXN], quex[MAXQ], quey[MAXQ], flag;
bool f[MAXN][MAXN];
void dfs1(int u) {
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if (val[v] == val[u]) {
			if (~clr[v]) flag |= clr[v] == clr[u];
			else clr[v] = !clr[u], dfs1(v), addgra(u, v), addgra(v, u);
		}
	}
}
void dfs2(int u) {
	clr[u] = 1;
	for (int i = head[u]; i; i = edge[i].next) {
		int v = edge[i].to;
		if (val[v] != val[u] && !clr[v])
			dfs2(v), addgra(u, v), addgra(v, u);
	}
}
char str[MAXN];
int main() {
	scanf("%d%d%d", &n, &m, &Q);
	scanf("%s", str + 1);
	for (int i = 1; i <= n; i++) val[i] = str[i] - '0';
	for (int i = 1; i <= m; i++) {
		int x, y; scanf("%d%d", &x, &y);
		addedge(x, y), addedge(y, x);
	}
	tot = 0;
	memset(clr, -1, sizeof(clr));
	for (int i = 1; i <= n; i++) if (clr[i] < 0) {
		clr[i] = flag = 0, dfs1(i);
		if (flag) addgra(i, i);
	}
	memset(clr, 0, sizeof(clr));
	for (int i = 1; i <= n; i++)
		if (!clr[i]) dfs2(i);
	int he = 0, ta = 0;
	for (int i = 1; i <= n; i++) {
		f[i][i] = 1;
		quex[ta] = i, quey[ta] = i, ++ta;
		for (int j = hd[i]; j; j = gra[j].next) {
			int v = gra[j].to;
			if (v > i && val[v] == val[i])
				quex[ta] = i, quey[ta] = v, ++ta;
		}
	}
	while (he < ta) {
		int x = quex[he], y = quey[he]; ++he;
		for (int j = hd[x]; j; j = gra[j].next)
		for (int k = hd[y]; k; k = gra[k].next) {
			int a = gra[j].to, b = gra[k].to;
			if (a > b) swap(a, b);
			if (val[a] == val[b] && !f[a][b]) {
				f[a][b] = 1;
				quex[ta] = a, quey[ta] = b, ++ta;
			}
		}
	}
	while (Q--) {
		int x, y; scanf("%d%d", &x, &y);
		if (x > y) swap(x, y);
		puts(f[x][y] ? "YES" : "NO");
	}
	return 0;
}

白兔之舞

题目链接

题解:

比较套路的一题吧……看到和   m o d     k \bmod~k mod k 有关,就可以想起单位根反演。我们记 g ( i ) g(i) g(i) 表示   m o d     k = i \bmod~k=i mod k=i 时的答案, F ( x ) F(x) F(x) 表示路径长度作为指数,方案数作为系数的关于 x x x 的多项式,第 i i i 项系数为 f i f_i fi。易得:

g ( p ) = ∑ i = 0 L − p f i + p ⋅ 1 k ∑ j = 0 k − 1 ( ω k i ) j = 1 k ∑ j = 0 k − 1 F ( ω k j ) ω k − p j g(p)=\sum_{i=0}^{L-p}f_{i+p}\cdot\frac{1}{k}\sum_{j=0}^{k-1}(\omega_k^i)^j=\frac{1}{k}\sum_{j=0}^{k-1}F(\omega_k^j)\omega_k^{-pj} g(p)=i=0Lpfi+pk1j=0k1(ωki)j=k1j=0k1F(ωkj)ωkpj

接下来考虑如何求 F ( ω k j ) F(\omega_k^j) F(ωkj)。我们显然可以枚举走了 i i i 步,然后是个转移矩阵的 i i i 次方乘一个组合数。即:

F ( x ) = ∑ i = 0 L ( L i ) A i x i = ( A x + I ) L F(x)=\sum_{i=0}^L\binom{L}{i}A^ix^i=(Ax+I)^L F(x)=i=0L(iL)Aixi=(Ax+I)L

其中 A A A 是转移矩阵, I I I 是单位矩阵。最终多项式的系数和实际上就是 ( A x + I ) L (Ax+I)^L (Ax+I)L x x x 行第 y y y 列的值,因此矩阵快速幂即可计算出 F ( ω k j ) F(\omega_k^j) F(ωkj) 了。但是如果暴力计算还是 O ( k 2 ) O(k^2) O(k2) 的,于是用到了毛爷论文里的“非 2 2 2 的幂次的DFT算法”。

考虑把 − p j -pj pj 拆成只和 p − j p-j pj p p p j j j 有关的项。不难想到 − p j = 1 2 ( ( p − j ) 2 − p 2 − j 2 ) -pj=\frac{1}{2}((p-j)^2-p^2-j^2) pj=21((pj)2p2j2),但是有个 1 2 \frac{1}{2} 21 的系数,这会使得没有二次剩余的项变得麻烦。于是稍加变换,得到 − p j = 1 2 ( ( p − j ) ( p − j + 1 ) − p ( p + 1 ) − j ( j − 1 ) ) -pj=\frac{1}{2}((p-j)(p-j+1)-p(p+1)-j(j-1)) pj=21((pj)(pj+1)p(p+1)j(j1)) 即可。于是就变成了:

g ( p ) = ω k − p ( p + 1 ) 2 k ∑ j = 0 k − 1 F ( ω k j ) ω k ( p − j ) ( p − j + 1 ) 2 ω k − j ( j − 1 ) 2 g(p)=\frac{\omega_k^{-\frac{p(p+1)}{2}}}{k}\sum_{j=0}^{k-1}F(\omega_k^j)\omega_k^{\frac{(p-j)(p-j+1)}{2}}\omega_k^{-\frac{j(j-1)}{2}} g(p)=kωk2p(p+1)j=0k1F(ωkj)ωk2(pj)(pj+1)ωk2j(j1)

已经很像卷积了,唯一的遗憾就是 j j j 最大是到 k − 1 k-1 k1,而不是 p p p。不妨令 a i = ω k ( i − k ) ( i + 1 − k ) 2 , b i = F ( ω k i ) ω k − i ( i − 1 ) 2 a_i=\omega_k^{\frac{(i-k)(i+1-k)}{2}},b_i=F(\omega_k^i)\omega_k^{-\frac{i(i-1)}{2}} ai=ωk2(ik)(i+1k),bi=F(ωki)ωk2i(i1),并且当 i ≤ k i\le k ik b i = 0 b_i=0 bi=0,那么上面的算式可以变成:

g ( p ) = ω k − p ( p + 1 ) 2 k ∑ j = 0 p + k a p + k − j b j g(p)=\frac{\omega_k^{-\frac{p(p+1)}{2}}}{k}\sum_{j=0}^{p+k}a_{p+k-j}b_j g(p)=kωk2p(p+1)j=0p+kap+kjbj

这样就是一个卷积形式了,MTT即可。复杂度 O ( k l o g k ) O(klogk) O(klogk)

#include 
using namespace std;
typedef long long ll;

const int BIT = 18, MAXN = 1 << BIT;
int MOD;
namespace PolyMTT {
	const double PI = 3.14159265358979323846264;
	int hasinit;
	struct C {
		double x, y;
		C operator+(const C &a) const { return (C) { x + a.x, y + a.y }; }
		C operator-(const C &a) const { return (C) { x - a.x, y - a.y }; }
		C operator*(const C &a) const { return (C) { x * a.x - y * a.y, x * a.y + y * a.x }; }
	} w[2][MAXN];
	void init() {
		hasinit = 1;
		const int temp = 1 << BIT;
		for (int i = 0; i < MAXN; i++) w[0][i] = (C) { cos(PI * 2 * i / temp), sin(PI * 2 * i / temp) };
		for (int i = 0; i < MAXN; i++) w[1][i] = (C) { cos(PI * 2 * i / temp), -sin(PI * 2 * i / temp) };
	}
	void fft(C *a, int n, int rev) {
		if (!hasinit) init();
		for (int i = 0, j = 0; i < n; i++) {
			if (i < j) swap(a[i], a[j]);
			for (int k = n >> 1; (j ^= k) < k; k >>= 1);
		}
		static C ww[MAXN];
		for (int h = 2; h <= n; h <<= 1) {
			int hh = h >> 1, t = (1 << BIT) / h;
			for (int i = 0; i < hh; i++) ww[i] = w[rev][i * t];
			for (int i = 0; i < n; i += h) {
				C *aj = a + i, *ah = aj + hh, *wn = ww;
				for (int j = i; j < i + hh; ++j, ++aj, ++ah, ++wn) {
					const C x = *aj, y = *ah * *wn;
					*aj = x + y, *ah = x - y;
				}
			}
		}
		if (rev) for (int i = 0; i < n; i++) a[i].x /= n, a[i].y /= n;
	}
	void mtt(int *a, int *b, int *c, int n, int m, int p) {
		if ((ll)n * m <= 4096) {
			typedef unsigned long long ull;
			static ull res[MAXN];
			for (int i = 0; i < p; i++) res[i] = 0;
			for (int i = 0; i < n; i++) {
				for (int j = 0; j < m && i + j < p; j++) res[i + j] += (ull)a[i] * b[j];
				if (!(i & 15)) for (int j = 0; j < p; j++) res[j] %= MOD;
			}
			for (int i = 0; i < p; i++) c[i] = res[i] % MOD;
		} else {
			static C A[MAXN], B[MAXN], D[MAXN];
			int len = 1; while (len < n + m - 1) len <<= 1;
			for (int i = 0; i < len; i++) A[i] = B[i] = (C) { 0.0, 0.0 };
			for (int i = 0; i < n; i++) A[i] = (C) { (double)(a[i] & 0x7FFF), (double)(a[i] >> 15) };
			for (int i = 0; i < m; i++) B[i] = (C) { (double)(b[i] & 0x7FFF), (double)(b[i] >> 15) };
			fft(A, len, 0), fft(B, len, 0);
			for (int i = 0; i < len; i++) {
				int j = (len - i) & (len - 1);
				C ca = (C) { (A[i].x + A[j].x) * 0.5, (A[i].y - A[j].y) * 0.5 };
				C cb = (C) { (B[i].x + B[j].x) * 0.5, (B[i].y - B[j].y) * 0.5 };
				D[i] = ca * cb;
			}
			for (int i = 0; i < len; i++) A[i] = A[i] * B[i];
			fft(A, len, 1), fft(D, len, 1);
			for (int i = 0; i < p; i++) {
				ll bd = (ll)round(D[i].x) % MOD + MOD, ac = (bd - (ll)round(A[i].x) % MOD + MOD) % MOD;
				c[i] = ((ac << 30) + (((ll)round(A[i].y) % MOD + MOD) << 15) + bd) % MOD;
			}
		}
	}
}
ll modpow(ll a, int b, int c = MOD) {
	ll res = 1;
	for (; b; b >>= 1) {
		if (b & 1) res = res * a % c;
		a = a * a % c;
	}
	return res;
}
int ww[MAXN], A[MAXN], B[MAXN], n, K, L, x, y, G;
int find_g() {
	int p = MOD - 1;
	vector<int> vec;
	for (int i = 2; i * i <= p; i++) if (p % i == 0) {
		vec.push_back(i);
		while (p % i == 0) p /= i;
	}
	if (p > 1) vec.push_back(p);
	for (int g = 1;; g++) {
		int flag = 1;
		for (int i : vec) if (modpow(g, (MOD - 1) / i) == 1) { flag = 0; break; }
		if (flag) return g;
	}
}
struct Matrix {
	ll a[3][3]; int n;
	Matrix() { n = 0; memset(a, 0, sizeof(a)); }
	void init(int t) {
		n = t, memset(a, 0, sizeof(a));
		for (int i = 0; i < t; i++) a[i][i] = 1;
	}
	Matrix operator*(const Matrix &m) const {
		Matrix res; res.n = n;
		for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) if (a[i][j])
		for (int k = 0; k < n; k++)
			res.a[i][k] += a[i][j] * m.a[j][k];
		for (int i = 0; i < n; i++)
		for (int j = 0; j < n; j++) res.a[i][j] %= MOD;
		return res;
	}
} mat;
Matrix modpow(Matrix m, int p) {
	Matrix res; res.init(m.n);
	for (; p; p >>= 1) {
		if (p & 1) res = res * m;
		m = m * m;
	}
	return res;
}
int main() {
	scanf("%d%d%d%d%d%d", &n, &K, &L, &x, &y, &MOD);
	G = find_g(); --x, --y;
	ll wn = modpow(G, (MOD - 1) / K);
	for (int i = 0; i < n; i++)
	for (int j = 0; j < n; j++)
		scanf("%lld", &mat.a[i][j]);
	mat.n = n;
	ww[0] = 1;
	for (int i = 0; i < K; i++) {
		ww[i + 1] = ww[i] * wn % MOD;
		Matrix res = mat;
		for (int j = 0; j < n; j++)
		for (int k = 0; k < n; k++)
			res.a[j][k] = (mat.a[j][k] * ww[i] + (j == k)) % MOD;
		res = modpow(res, L);
		A[i] = res.a[x][y];
	}
	for (int i = 0; i < K; i++)
		A[i] = (ll)A[i] * ww[K - (ll)i * (i - 1) / 2 % K] % MOD;
	for (int i = 0; i < K << 1; i++)
		B[i] = ww[((ll)(i - K) * (i - K + 1) / 2 % K + K) % K];
	PolyMTT::mtt(A, B, A, K << 1, K << 1, K << 1);
	ll inv = modpow(K, MOD - 2);
	for (int i = 0; i < K; i++) {
		ll t = A[i + K];
		printf("%lld\n", t * inv % MOD * ww[K - (ll)i * (i + 1) / 2 % K] % MOD);
	}
	return 0;
}

序列

题目链接

题解:

感觉这题是D2里最简单的了……显然对于一个连续的下降段,它们修改成相同的数最优。于是贡献就是 ∑ ( x − a i ) 2 \sum(x-a_i)^2 (xai)2,稍加计算就可以知道 x x x 取平均数时最优。然后我们假设把所有的下降段都缩成一个数,这样又会出现新的下降段,不断缩直到数列不降,这种策略一定最优。

考虑使用单调栈维护。从左向右扫,如果栈顶元素大于当前元素到栈顶元素之间所有数字的平均数,就弹出。于是第一问就可以在 O ( n ) O(n) O(n) 的时间内计算出来了。

考虑第二问怎么做。我们可以维护一个从左向右的单调栈和一个从右向左的单调栈,如果强制在线的话可以把单调栈可持久化一下,不过这题可以离线做,那就不用可持久化了。

考虑如何合并两个栈以及中间元素,我们可以大致模拟一下,从中间元素开始向右扫,每次找到最大的 l l l 使单调栈中的第 l − 1 l-1 l1 个元素小于等于 l l l 到当前位置的平均数,然后区间set成同一个值。于是找 l l l 可以用二分解决。那么我们扫到什么时候才算结束呢?肯定是找到最大的 r r r,这个 r r r 找到对应的 l l l 之后,形成的那个值要小于等于右边单调栈的第 r − 1 r-1 r1 个元素。

因此我们二分 r r r,里面再套一个二分计算 l l l 即可。复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)

#include 
using namespace std;
typedef long long ll;

const int MAXN = 100005, MOD = 998244353;
const double EPS = 1E-10;
int sta[MAXN], stb[MAXN], arr[MAXN], ans[MAXN], n, m;
ll suma[MAXN], sumb[MAXN], sum[MAXN], sum2[MAXN];
double numa[MAXN], numb[MAXN];
vector<int> bk[MAXN];
struct Query { int k, id; };
vector<Query> ask[MAXN];
ll modpow(ll a, int b) {
	ll res = 1;
	for (; b; b >>= 1) {
		if (b & 1) res = res * a % MOD;
		a = a * a % MOD;
	}
	return res;
}
double get_avg(int l, int r) { return (double)(sum[r] - sum[l - 1]) / (r - l + 1); }
int main() {
	scanf("%d%d", &n, &m);
	int tpa = 0, tpb = 0;
	for (int i = 1; i <= n; i++) {
		scanf("%d", arr + i);
		sum[i] = sum[i - 1] + arr[i];
		sum2[i] = (sum2[i - 1] + (ll)arr[i] * arr[i]) % MOD;
		while (tpa > 0 && numa[sta[tpa]] - EPS > get_avg(sta[tpa] + 1, i)) --tpa;
		int lst = sta[tpa];
		ll now = sum[i] - sum[lst];
		numa[i] = (double)now / (i - lst);
		now %= MOD;
		ll t = now * modpow(i - lst, MOD - 2) % MOD;
		suma[i] = (suma[lst] + sum2[i] - sum2[lst] - t * now) % MOD;
		sta[++tpa] = i;
	}
	printf("%lld\n", (suma[n] + MOD) % MOD);
	stb[0] = n + 1;
	for (int i = n; i > 0; i--) {
		while (tpb > 0 && numb[stb[tpb]] + EPS < get_avg(i, stb[tpb] - 1)) bk[i].push_back(stb[tpb--]);
		int nxt = stb[tpb] - 1;
		ll now = sum[nxt] - sum[i - 1];
		numb[i] = (double)now / (nxt - i + 1);
		now %= MOD;
		ll t = now * modpow(nxt - i + 1, MOD - 2) % MOD;
		sumb[i] = (sumb[nxt + 1] + sum2[nxt] - sum2[i - 1] - t * now) % MOD;
		stb[++tpb] = i;
	}
	for (int i = 1; i <= m; i++) {
		int x, k; scanf("%d%d", &x, &k);
		ask[x].push_back((Query) { k, i });
	}
	tpa = 0;
	auto find_left = [&](int mid, int sub)->int {
		int a = 0, b = tpa + 1;
		while (a + 1 < b) {
			int meow = (a + b) >> 1;
			double t = (double)(sum[stb[mid] - 1] - sum[sta[meow]] + sub) / (stb[mid] - 1 - sta[meow]);
			if (t - EPS > numa[sta[meow]]) a = meow;
			else b = meow;
		}
		return a;
	};
	for (int i = 1; i <= n; i++) {
		--tpb;
		for (int j  = (int)bk[i].size() - 1; j >= 0; j--) stb[++tpb] = bk[i][j];
		for (Query q : ask[i]) {
			int l = 0, r = tpb + 1, sub = q.k - arr[i];
			while (l + 1 < r) {
				int mid = (l + r) >> 1, a = find_left(mid, sub);
				if ((double)(sum[stb[mid] - 1] - sum[sta[a]] + sub) / (stb[mid] - sta[a] - 1) + EPS < numb[stb[mid]]) l = mid;
				else r = mid;
			}
			int tl = find_left(l, sub);
			ll now = (sum[stb[l] - 1] - sum[sta[tl]] + sub) % MOD;
			ll t = now * modpow(stb[l] - 1 - sta[tl], MOD - 2) % MOD;
			ans[q.id] = (suma[sta[tl]] + sumb[stb[l]] + sum2[stb[l] - 1] - sum2[sta[tl]] - (ll)arr[i] * arr[i] + (ll)q.k * q.k - t * now) % MOD;
		}
		while (tpa > 0 && numa[sta[tpa]] - EPS > get_avg(sta[tpa] + 1, i)) --tpa;
		sta[++tpa] = i;
	}
	for (int i = 1; i <= m; i++) printf("%d\n", (ans[i] + MOD) % MOD);
	return 0;
}

你可能感兴趣的:(比赛)