HNOI2019 DAY1 題解

WAautomaton發表於2019-04-14

題目連結

題解:
容易發現等腰三角形個數似乎是 O(n2)O(n^2) 的?於是可以計算出任意兩個點作為底邊,向兩個方向的等腰三角形個數。之後顯然列舉中心點,然後把其它點極角排序,算出所有等腰三角形的中垂線,然後再列舉一個作為魚尾,二分+字首和查詢即可。複雜度 O(n2logn)O(n^2logn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;

const double PI = 3.14159265358979324, EPS = 1E-10;
const int MAXN = 1005, MAXM = 1000005;
struct Point { int x, y, id; ll d; double ang; } po[MAXN][MAXN];
bool cmp(const Point &a, const Point &b) {
	return a.d == b.d ? a.ang < b.ang : a.d < b.d;
}
int xx[MAXN], yy[MAXN], app[MAXN][MAXN][2], sum[MAXM], n;
struct Node { int s; double ang; } arr[MAXM];
double angs[MAXM];
double range(double x) {
	return x > PI ? x - PI * 2 : (x - EPS < -PI ? x + PI * 2 : x);
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) scanf("%d%d", xx + i, yy + i);
	ll res = 0;
	for (int i = 1; i <= n; i++) {
		int cnt = 0;
		for (int j = 1; j <= n; j++) if (i != j) {
			po[i][++cnt].id = j;
			po[i][cnt].x = xx[j] - xx[i];
			po[i][cnt].y = yy[j] - yy[i];
			po[i][cnt].d = (ll)po[i][cnt].x * po[i][cnt].x + (ll)po[i][cnt].y * po[i][cnt].y;
			po[i][cnt].ang = range(atan2(po[i][cnt].y, po[i][cnt].x));
		}
		sort(po[i] + 1, po[i] + n, cmp);
		for (int j = 1; j < n; j++) {
			int k = j;
			while (k < n - 1 && po[i][k + 1].d == po[i][j].d) ++k;
			for (int l = j; l <= k; l++)
			for (int o = l + 1; o <= k; o++) {
				int a = po[i][l].id, b = po[i][o].id;
				if (b < a) swap(a, b);
				ll c = (ll)(xx[i] - xx[a]) * (yy[b] - yy[a]) - (ll)(yy[i] - yy[a]) * (xx[b] - xx[a]);
				if (c == 0) continue;
				++app[a][b][c > 0];
			}
			j = k;
		}
	}
	for (int i = 1; i <= n; i++) {
		int cnt = 0;
		for (int j = 1; j < n; j++) {
			int k = j;
			while (k < n - 1 && po[i][k + 1].d == po[i][j].d) ++k;
			for (int l = j; l <= k; l++)
			for (int o = l + 1; o <= k; o++) {
				int a = po[i][l].id, b = po[i][o].id;
				if (b < a) swap(a, b);
				ll c = (ll)(xx[i] - xx[a]) * (yy[b] - yy[a]) - (ll)(yy[i] - yy[a]) * (xx[b] - xx[a]);
				if (c == 0) continue;
				if (po[i][o].ang - po[i][l].ang - EPS > PI)
					arr[++cnt].ang = range((po[i][l].ang + po[i][o].ang) * 0.5 + PI);
				else arr[++cnt].ang = (po[i][o].ang + po[i][l].ang) * 0.5;
				arr[cnt].s = app[a][b][c < 0];
			}
			j = k;
		}
		sort(arr + 1, arr + cnt + 1, [](const Node &a, const Node &b) { return a.ang < b.ang; });
		for (int l = 1; l <= cnt; l++) angs[l] = arr[l].ang;
		for (int l = 1; l <= cnt; l++) sum[l] = sum[l - 1] + arr[l].s;
		for (int j = 1; j < n; j++) {
			int k = j;
			while (k < n - 1 && po[i][k + 1].d == po[i][j].d) ++k;
			for (int l = j; l <= k; l++)
			for (int o = l + 1; o <= k; o++) {
				double a, b;
				if (po[i][o].ang - po[i][l].ang - EPS > PI)
					a = range(po[i][o].ang - PI * 0.5), b = range(po[i][l].ang + PI * 0.5);
				else if (po[i][o].ang - po[i][l].ang + EPS < PI)
					a = range(po[i][o].ang + PI * 0.5), b = range(po[i][l].ang - PI * 0.5);
				else continue;
				if (a > b) swap(a, b);
				if (b - a > PI) {
					int c = lower_bound(angs + 1, angs + 1 + cnt, a - EPS) - angs - 1;
					int d = lower_bound(angs + 1, angs + 1 + cnt, b + EPS) - angs - 1;
					res += sum[cnt] - sum[d] + sum[c];
				} else {
					int c = lower_bound(angs + 1, angs + 1 + cnt, a + EPS) - angs - 1;
					int d = lower_bound(angs + 1, angs + 1 + cnt, b - EPS) - angs - 1;
					res += sum[d] - sum[c];
				}
			}
			j = k;
		}
		//printf("%d %lld\n", i, res);
	}
	printf("%lld\n", res << 2);
	return 0;
}

JOJO

題目連結

題解:
沒有2操作的話應該是比較好做的吧。注意到相鄰插入的字元都不一樣,因此我們對二元組做KMP。跳 failfail 時,我們需要找到一個和當前完全一樣的二元組才行,否則由於後面插入的字元不一樣,就一定不能接下去。特殊的,當跳到第一個二元組時,如果字元一樣且當前二元組長度大於第一個二元組長度,那麼 failfail 可以指向第一個二元組。

考慮如何統計答案。在跳 failfail 的過程中,每跳一次可能會在當前二元組上多匹配一段區間,直接加上貢獻即可。於是此部分可以 O(n)O(n) 解決。

但是這個複雜度是均攤的,可持久化的時候就不對了(但聽說暴力直接過了?)。考慮優化跳 failfail 的過程,即對於每個二元組,記 f(i,j,k)f(i,j,k) 表示從 i1i-1 位置跳二元組 (j,k)(j,k) 會到達的節點。更新的話直接從 f(faili)f(fail_i) 複製一遍,並且把 f(i,j,k)f(i,j,k) 指向 ii。計算答案的話,當前點對後面產生的貢獻實際上是一個等差數列,區間打 setset 標記即可。

於是離線建樹,然後 dfsdfs 的時候拿可持久化線段樹維護就行了,複雜度 O(nlogn)O(nlogn)

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;

const int MAXN = 100005, MAXT = 10000005;
struct Node { Node *ls, *rs; ll sum; int tag, id, l, r; }
	nd[MAXT], *rt[MAXN][26], *tot = nd;
struct Edge { int to, next; } edge[MAXN];
ll ans[MAXN];
int head[MAXN], id[MAXN], val[MAXN], len[MAXN], n, m, turn, ecnt;
int pre[MAXN], mx[MAXN][26], lqs[MAXN];
char opt[5];
Node *newnode(int l, int r, int t) {
	Node *p = tot++; p->sum = (ll)(r - l + 1) * t;
	p->l = l, p->r = r, p->tag = t;
	return p;
}
Node *copy(Node *d, int l, int r) {
	Node *p = tot++;
	if (d != nullptr) return &(*p = *d);
	p->l = l, p->r = r; return p;
}
void pushdown(Node *d) {
	if (!d->tag) return;
	int mid = (d->l + d->r) >> 1;
	d->ls = newnode(d->l, mid, d->tag);
	d->rs = newnode(mid + 1, d->r, d->tag);
	d->tag = 0;
}
void modify(int r, int val, int id, Node *&d, int a = 1, int b = m) {
	d = copy(d, a, b);
	if (d->l > r) return;
	if (d->r < r) { d->sum = (ll)(d->r - d->l + 1) * (d->tag = val); return; }
	if (d->l == d->r) { d->sum = d->tag = val, d->id = id; return; }
	pushdown(d);
	int mid = (a + b) >> 1;
	modify(r, val, id, d->ls, a, mid);
	modify(r, val, id, d->rs, mid + 1, b);
	d->sum = d->ls->sum + d->rs->sum;
}
void query(int r, ll &ans, int &id, Node *d) {
	if (d == nullptr || d->l > r) return;
	if (d->r < r) { ans += d->sum; return; }
	if (d->l == d->r) { ans += d->sum, id = d->id; return; }
	pushdown(d);
	query(r, ans, id, d->ls);
	query(r, ans, id, d->rs);
}
ll get_sum(int x) { return (ll)x * (x + 1) >> 1; }
void addedge(int u, int v) {
	edge[++ecnt] = (Edge) { v, head[u] };
	head[u] = ecnt;
}
void dfs(int u, int dep) {
	int fail = 0;
	lqs[dep] = val[u];
	if (dep > 1) {
		ans[u] += get_sum(min(len[u], mx[dep][val[u]]));
		query(len[u], ans[u], fail, rt[dep][val[u]]);
		if (!fail && len[u] > pre[1] && lqs[1] == lqs[dep])
			ans[u] += (ll)pre[1] * max(0, len[u] - mx[dep][val[u]]), fail = 1;
	} else if (dep == 1) ans[u] += get_sum(len[u] - 1);
	else fail = -1;
	mx[dep][val[u]] = max(mx[dep][val[u]], len[u]);
	if (dep > 0) modify(len[u], pre[dep - 1], dep, rt[dep][val[u]]);
	for (int i = head[u]; i; i = edge[i].next) {
		for (int j = 0; j < 26; j++) {
			mx[dep + 1][j] = mx[fail + 1][j];
			rt[dep + 1][j] = rt[fail + 1][j];
		}
		int v = edge[i].to;
		pre[dep + 1] = pre[dep] + len[v];
		ans[v] = ans[u];
		dfs(v, dep + 1);
	}
}
int main() {
	scanf("%d", &n);
	for (int i = 1; i <= n; i++) {
		int a, b; scanf("%d%d", &a, &b);
		if (a == 1) {
			addedge(id[i - 1], id[i] = ++turn);
			scanf("%s", opt);
			len[turn] = b, val[turn] = opt[0] - 'a';
			m = max(m, b);
		} else id[i] = id[b];
	}
	dfs(0, 0);
	for (int i = 1; i <= n; i++) printf("%lld\n", ans[id[i]] % 998244353);
	return 0;
}

多邊形

題目連結

題解:
凸多邊形的三角剖分?趕緊轉二叉樹……具體的最小左旋建對偶圖即可。然後會發現,題目中的“旋轉”操作實際上就是二叉樹的旋轉。考慮令靠近 1n1-n 那條邊的三角形為根,對於每個三角形,除了父節點外還有兩條邊,令邊上點編號較小的為左兒子,較大的為右兒子。

不難發現,題目顯然讓我們旋轉到所有邊都從 nn 出發為止,否則可以證明一定能夠繼續旋轉。也就是說,我們要旋轉到二叉樹變成一條除葉節點外只有右子樹的鏈。考慮最優策略最多每次能讓一個點進入最右邊的那條鏈,因此答案就是 n2n-2-最右邊鏈的長度。顯然,一個父節點必須在子節點之前進入鏈,已經在鏈上的節點可以不考慮,也就是不計入子樹 sizesize。於是就是一個很經典的計數 dpdp 了,f[i]f[i] 表示 ii 的子樹中所有不在鏈上的節點,組成的不同拓撲序個數。於是大概就是兒子的 ff 乘起來再乘上一個組合數。

最後考慮每次旋轉後如何動態維護這個 dpdp,動態 dpdp (霧)?容易發現,根節點的 dpdp 值實際上就是所有節點把兒子插起來的組合數的乘積。於是如果當前旋轉沒有把任何點旋轉到鏈上,那麼直接除掉那兩個變化點的貢獻,再乘上他們的新貢獻即可。否則的話,第一問的答案減1,然後從它開始到根節點所有點的 sizesize 減1,於是我們還要維護一個新的 dpdp,表示從根到某個點上的 sizesize 全部減1,答案會乘上多少。然後就可以在 O(nlogn)O(nlogn) 的時間內解決了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef pair<int, int> P;

const int MAXN = 100005, MAXM = 400005, MOD = 1000000007;
set<int> edge[MAXN]; map<P, int> mp;
int vis[MAXM], fr[MAXM], to[MAXM], id[MAXN][3], n, W, ecnt;
void addedge(int u, int v) {
	edge[u].insert(v);
	mp[P(u, v)] = ++ecnt;
	fr[ecnt] = u, to[ecnt] = v;
}
vector<int> tr[MAXN];
int sz[MAXN], par[MAXN], num[MAXN], son[MAXN][2], ans1;
ll fac[MAXN], rev[MAXN], f[MAXN], g[MAXN], ans2 = 1;
ll C(int a, int b) { return fac[a] * rev[b] % MOD * rev[a - b] % MOD; }
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;
}
void dfs(int u, int fa) {
	num[u] = max(id[u][0], max(id[u][1], id[u][2]));
	int t = 0;
	for (int v : tr[u]) {
		if (v == fa) continue;
		dfs(v, u);
		sz[u] += sz[v];
		son[u][t++] = v;
	}
	f[u] = C(sz[u], sz[son[u][0]]);
	ans2 = ans2 * f[u] % MOD;
	if (t == 2 && num[son[u][0]] > num[son[u][1]]) swap(son[u][0], son[u][1]);
	if (t == 1 && num[son[u][0]] == num[u]) swap(son[u][0], son[u][1]);
	if (num[u] == n) ++ans1;
	else ++sz[u];
}
void dfs2(int u, int fa) {
	for (int v : tr[u]) {
		if (v == fa) continue;
		g[v] = g[u] * sz[v] % MOD * modpow(sz[u], MOD - 2) % MOD;
		dfs2(v, u);
	}
}
void print(int a, ll b) {
	if (!W) printf("%d\n", a);
	else printf("%d %lld\n", a, b);
}
int main() {
	scanf("%d%d", &W, &n);
	assert(n > 3);
	for (int i = 1; i <= n - 3; i++) {
		int x, y; scanf("%d%d", &x, &y);
		addedge(x, y), addedge(y, x);
	}
	for (int i = 1; i <= n; i++) {
		int x = i, y = i % n + 1;
		addedge(x, y), addedge(y, x);
	}
	int tot = 0, rt, bes;
	for (int i = 1; i <= ecnt; i++) if (!vis[i]) {
		vis[i] = ++tot;
		edge[fr[i]].erase(to[i]);
		int lst = fr[i], cnt = 0;
		id[tot][cnt++] = fr[i];
		for (int u = to[i]; u != fr[i];) {
			if (cnt < 3) id[tot][cnt] = u;
			++cnt;
			int x = lst;
			if (lst == *edge[u].rbegin())
				lst = u, u = *edge[lst].begin();
			else lst = u, u = *edge[lst].upper_bound(x);
			edge[lst].erase(u), vis[mp[P(lst, u)]] = tot;
		}
		if (cnt >= 3) bes = tot;
	}
	for (int i = 1; i <= ecnt; i += 2) {
		if (vis[i + 1] == bes || vis[i] == bes) {
			if (fr[i] == n && to[i] == 1) rt = vis[i] == bes ? vis[i + 1] : vis[i];
			continue;
		}
		tr[vis[i]].push_back(vis[i + 1]);
		tr[vis[i + 1]].push_back(vis[i]);
	}
	for (int i = fac[0] = 1; i <= n; i++) fac[i] = fac[i - 1] * i % MOD;
	rev[n] = modpow(fac[n], MOD - 2);
	for (int i = n; i > 0; i--) rev[i - 1] = rev[i] * i % MOD;
	dfs(rt, 0);
	ans1 = tot - 1 - ans1, g[rt] = 1;
	dfs2(rt, 0);
	print(ans1, ans2);
	int m; scanf("%d", &m);
	while (m--) {
		int a, b; scanf("%d%d", &a, &b);
		int id1 = vis[mp[P(a, b)]], id2 = vis[mp[P(b, a)]];
		if (par[id2] != id1) swap(id1, id2);
		if (num[id1] < n) {
			print(ans1, ans2 * C(sz[id1] - 1, sz[son[id2][0]]) % MOD *
				C(sz[son[id2][1]] + sz[son[id1][1]], sz[son[id2][1]]) % MOD * modpow(f[id1] * f[id2] % MOD, MOD - 2) % MOD);
		} else print(ans1 - 1, ans2 * g[id2] % MOD);
	}
	return 0;
}