HNOI2019 DAY2 題解

WAautomaton發表於2019-04-15

校園旅行

題目連結

題解:

這可能是 HNOI2019 裡最神的一題了 orz。

首先不難發現一個暴力,記 f(i,j)f(i,j) 表示點對 i,ji,j 之間是否存在迴文路徑,列舉他們的出邊暴力轉移即可。初始的時候所有的 f(i,i)f(i,i) 和兩個顏色相同且相鄰的點 f(a,b)f(a,b)truetrue,扔到佇列裡 bfsbfs 即可。不難發現任意一對邊至多隻會被列舉 44 次,因此複雜度是 O(m2)O(m^2) 的。

然後就是神仙想法了,我們可以減少邊的數量。令連線兩個顏色相同的點的邊為同色邊,否則為異色邊。考慮最終的迴文串長啥樣,肯定是一段同色邊一段異色邊不斷接起來。由於一條邊可以來回走,因此我們要保證對應同色邊或異色邊的條數的奇偶性相同。也就是說,我們只在意對應的奇偶性而不在意具體的路徑長度。

不妨把兩種邊分開考慮,把所有異色邊全部刪掉,那麼剩下的圖中一個連通塊裡的所有點顏色全部相同。如果一個連通塊是二分圖,那麼就意味著無論怎麼走,任意兩個點之間路徑長度的奇偶性都是固定的。因此對於是二分圖的連通塊,我們只要保留任意一棵生成樹即可。

否則的話,就一定存在奇環。由於我們並不在意路徑長度而只在意奇偶性,因此給隨便一個點連一條自環即可。

對於異色邊,連出來的連通塊顯然就是一個二分圖,也任意保留一棵生成樹即可。

因此,邊數就變成了 O(n)O(n) 的,直接跑上面的暴力即可 O(n2)O(n^2) 解決。

#include <bits/stdc++.h>
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;
}

白兔之舞

題目連結

題解:

比較套路的一題吧……看到和 &VeryThinSpace;mod&VeryThinSpace; k\bmod~k 有關,就可以想起單位根反演。我們記 g(i)g(i) 表示 &VeryThinSpace;mod&VeryThinSpace; k=i\bmod~k=i 時的答案,F(x)F(x) 表示路徑長度作為指數,方案數作為係數的關於 xx 的多項式,第 ii 項係數為 fif_i。易得:

g(p)=i=0Lpfi+p1kj=0k1(ωki)j=1kj=0k1F(ωkj)ωkpjg(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}

接下來考慮如何求 F(ωkj)F(\omega_k^j)。我們顯然可以列舉走了 ii 步,然後是個轉移矩陣的 ii 次方乘一個組合數。即:

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

其中 AA 是轉移矩陣,II 是單位矩陣。最終多項式的係數和實際上就是 (Ax+I)L(Ax+I)^Lxx 行第 yy 列的值,因此矩陣快速冪即可計算出 F(ωkj)F(\omega_k^j) 了。但是如果暴力計算還是 O(k2)O(k^2) 的,於是用到了毛爺論文裡的“非 22 的冪次的DFT演算法”。

考慮把 pj-pj 拆成只和 pjp-jppjj 有關的項。不難想到 pj=12((pj)2p2j2)-pj=\frac{1}{2}((p-j)^2-p^2-j^2),但是有個 12\frac{1}{2} 的係數,這會使得沒有二次剩餘的項變得麻煩。於是稍加變換,得到 pj=12((pj)(pj+1)p(p+1)j(j1))-pj=\frac{1}{2}((p-j)(p-j+1)-p(p+1)-j(j-1)) 即可。於是就變成了:

g(p)=ωkp(p+1)2kj=0k1F(ωkj)ωk(pj)(pj+1)2ωkj(j1)2g(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}}

已經很像卷積了,唯一的遺憾就是 jj 最大是到 k1k-1,而不是 pp。不妨令 ai=ωk(ik)(i+1k)2,bi=F(ωki)ωki(i1)2a_i=\omega_k^{\frac{(i-k)(i+1-k)}{2}},b_i=F(\omega_k^i)\omega_k^{-\frac{i(i-1)}{2}},並且當 iki\le kbi=0b_i=0,那麼上面的算式可以變成:

g(p)=ωkp(p+1)2kj=0p+kap+kjbjg(p)=\frac{\omega_k^{-\frac{p(p+1)}{2}}}{k}\sum_{j=0}^{p+k}a_{p+k-j}b_j

這樣就是一個卷積形式了,MTT即可。複雜度 O(klogk)O(klogk)

#include <bits/stdc++.h>
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裡最簡單的了……顯然對於一個連續的下降段,它們修改成相同的數最優。於是貢獻就是(xai)2\sum(x-a_i)^2,稍加計算就可以知道 xx 取平均數時最優。然後我們假設把所有的下降段都縮成一個數,這樣又會出現新的下降段,不斷縮直到數列不降,這種策略一定最優。

考慮使用單調棧維護。從左向右掃,如果棧頂元素大於當前元素到棧頂元素之間所有數字的平均數,就彈出。於是第一問就可以在 O(n)O(n) 的時間內計算出來了。

考慮第二問怎麼做。我們可以維護一個從左向右的單調棧和一個從右向左的單調棧,如果強制線上的話可以把單調棧可持久化一下,不過這題可以離線做,那就不用可持久化了。

考慮如何合併兩個棧以及中間元素,我們可以大致模擬一下,從中間元素開始向右掃,每次找到最大的 ll 使單調棧中的第 l1l-1 個元素小於等於 ll 到當前位置的平均數,然後區間set成同一個值。於是找 ll 可以用二分解決。那麼我們掃到什麼時候才算結束呢?肯定是找到最大的 rr,這個 rr 找到對應的 ll 之後,形成的那個值要小於等於右邊單調棧的第 r1r-1 個元素。

因此我們二分 rr,裡面再套一個二分計算 ll 即可。複雜度 O(nlog2n)O(nlog^2n)

#include <bits/stdc++.h>
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;
}

相關文章