HNOI2019 DAY1 题解

题目链接

题解:
容易发现等腰三角形个数似乎是 O ( n 2 ) O(n^2) O(n2) 的?于是可以计算出任意两个点作为底边,向两个方向的等腰三角形个数。之后显然枚举中心点,然后把其它点极角排序,算出所有等腰三角形的中垂线,然后再枚举一个作为鱼尾,二分+前缀和查询即可。复杂度 O ( n 2 l o g n ) O(n^2logn) O(n2logn)

#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。跳 f a i l fail fail 时,我们需要找到一个和当前完全一样的二元组才行,否则由于后面插入的字符不一样,就一定不能接下去。特殊的,当跳到第一个二元组时,如果字符一样且当前二元组长度大于第一个二元组长度,那么 f a i l fail fail 可以指向第一个二元组。

考虑如何统计答案。在跳 f a i l fail fail 的过程中,每跳一次可能会在当前二元组上多匹配一段区间,直接加上贡献即可。于是此部分可以 O ( n ) O(n) O(n) 解决。

但是这个复杂度是均摊的,可持久化的时候就不对了(但听说暴力直接过了?)。考虑优化跳 f a i l fail fail 的过程,即对于每个二元组,记 f ( i , j , k ) f(i,j,k) f(i,j,k) 表示从 i − 1 i-1 i1 位置跳二元组 ( j , k ) (j,k) (j,k) 会到达的节点。更新的话直接从 f ( f a i l i ) f(fail_i) f(faili) 复制一遍,并且把 f ( i , j , k ) f(i,j,k) f(i,j,k) 指向 i i i。计算答案的话,当前点对后面产生的贡献实际上是一个等差数列,区间打 s e t set set 标记即可。

于是离线建树,然后 d f s dfs dfs 的时候拿可持久化线段树维护就行了,复杂度 O ( n l o g n ) 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;
}

多边形

题目链接

题解:
凸多边形的三角剖分?赶紧转二叉树……具体的最小左旋建对偶图即可。然后会发现,题目中的“旋转”操作实际上就是二叉树的旋转。考虑令靠近 1 − n 1-n 1n 那条边的三角形为根,对于每个三角形,除了父节点外还有两条边,令边上点编号较小的为左儿子,较大的为右儿子。

不难发现,题目显然让我们旋转到所有边都从 n n n 出发为止,否则可以证明一定能够继续旋转。也就是说,我们要旋转到二叉树变成一条除叶节点外只有右子树的链。考虑最优策略最多每次能让一个点进入最右边的那条链,因此答案就是 n − 2 − n-2- n2最右边链的长度。显然,一个父节点必须在子节点之前进入链,已经在链上的节点可以不考虑,也就是不计入子树 s i z e size size。于是就是一个很经典的计数 d p dp dp 了, f [ i ] f[i] f[i] 表示 i i i 的子树中所有不在链上的节点,组成的不同拓扑序个数。于是大概就是儿子的 f f f 乘起来再乘上一个组合数。

最后考虑每次旋转后如何动态维护这个 d p dp dp,动态 d p dp dp (雾)?容易发现,根节点的 d p dp dp 值实际上就是所有节点把儿子插起来的组合数的乘积。于是如果当前旋转没有把任何点旋转到链上,那么直接除掉那两个变化点的贡献,再乘上他们的新贡献即可。否则的话,第一问的答案减1,然后从它开始到根节点所有点的 s i z e size size 减1,于是我们还要维护一个新的 d p dp dp,表示从根到某个点上的 s i z e size size 全部减1,答案会乘上多少。然后就可以在 O ( n l o g n ) 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;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值