HNOI2019 DAY1 題解
魚
題解:
容易發現等腰三角形個數似乎是 的?於是可以計算出任意兩個點作為底邊,向兩個方向的等腰三角形個數。之後顯然列舉中心點,然後把其它點極角排序,算出所有等腰三角形的中垂線,然後再列舉一個作為魚尾,二分+字首和查詢即可。複雜度 。
#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。跳 時,我們需要找到一個和當前完全一樣的二元組才行,否則由於後面插入的字元不一樣,就一定不能接下去。特殊的,當跳到第一個二元組時,如果字元一樣且當前二元組長度大於第一個二元組長度,那麼 可以指向第一個二元組。
考慮如何統計答案。在跳 的過程中,每跳一次可能會在當前二元組上多匹配一段區間,直接加上貢獻即可。於是此部分可以 解決。
但是這個複雜度是均攤的,可持久化的時候就不對了(但聽說暴力直接過了?)。考慮優化跳 的過程,即對於每個二元組,記 表示從 位置跳二元組 會到達的節點。更新的話直接從 複製一遍,並且把 指向 。計算答案的話,當前點對後面產生的貢獻實際上是一個等差數列,區間打 標記即可。
於是離線建樹,然後 的時候拿可持久化線段樹維護就行了,複雜度 。
#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,然後從它開始到根節點所有點的 減1,於是我們還要維護一個新的 ,表示從根到某個點上的 全部減1,答案會乘上多少。然後就可以在 的時間內解決了。
#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;
}
相關文章
- HNOI2019 DAY2 題解
- [IOI2018]-day1 簡要題解
- 國慶day1補題
- Offer68題 Day1
- Day1 最短路專題
- 【CH Round #48 - Streaming #3(NOIP模擬賽Day1)】 題解
- LeetCode刷題記錄——day1LeetCode
- 【刷題打卡】day1 - 字串string字串
- Spring高階註解-Day1Spring
- day1
- Python Day1Python
- Learning Java day1Java
- Laravel 框架 day1Laravel框架
- day1打卡
- Day1小結(7.13)
- NOI2019 Day1
- 自專案Day1
- Numpy學習 Day1
- Vue.js - Day1Vue.js
- day1 3/4 Monday
- 蒼穹外賣 - day1
- day1 指標學習指標
- MySQL深入學習-day1MySql
- python學習之路—day1Python
- python暑期課程 day1Python
- 24暑假集訓day1上午
- 程式碼隨想錄-day1
- HTML與CSS基礎day1HTMLCSS
- TA學習記錄Day1
- Java 學習筆記--Day1Java筆記
- 團隊專案衝刺--day1
- HTML之小白的入門Day1HTML
- 笨方法學C 筆記 (day1)筆記
- Day1: 用Github管理Pipeline指令碼Github指令碼
- 【JAVA Web基礎學習】Day1JavaWeb
- 團隊專案Scrum衝刺day1Scrum
- 團隊專案Scrum衝刺-day1Scrum
- linux學習day1——linux常見命令Linux