鱼
题解:
容易发现等腰三角形个数似乎是
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 i−1 位置跳二元组 ( 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
1−n 那条边的三角形为根,对于每个三角形,除了父节点外还有两条边,令边上点编号较小的为左儿子,较大的为右儿子。
不难发现,题目显然让我们旋转到所有边都从 n n n 出发为止,否则可以证明一定能够继续旋转。也就是说,我们要旋转到二叉树变成一条除叶节点外只有右子树的链。考虑最优策略最多每次能让一个点进入最右边的那条链,因此答案就是 n − 2 − n-2- n−2−最右边链的长度。显然,一个父节点必须在子节点之前进入链,已经在链上的节点可以不考虑,也就是不计入子树 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;
}