---
tags: DDJRC
---
# DDJ Regular Contest Round#5 Tutorial
## a626: A. 簽到
<code>tags: geometry</code>
#### 結論: 三個有向面積$\overrightarrow{PA}\times\overrightarrow{PB},\ \overrightarrow{PB}\times\overrightarrow{PC},\ \overrightarrow{PC}\times\overrightarrow{PA}$同號等價於$P$在三角形內
自己畫幾張圖觀察一下三個面積的方向就會發現這很顯然
這裡簡單證明一下,不管$P$在哪裡$\triangle ABC$的面積都可以寫成
$$\frac{1}{2}\left|\overrightarrow{PA}\times\overrightarrow{PB}+\overrightarrow{PB}\times\overrightarrow{PC}+\overrightarrow{PC}\times\overrightarrow{PA}\right|$$
如果$P$在$\triangle ABC$內面積可以寫成
$$\frac{1}{2}\left|\overrightarrow{PA}\times\overrightarrow{PB}\right|+\frac{1}{2}\left|\overrightarrow{PB}\times\overrightarrow{PC}\right|+\frac{1}{2}\left|\overrightarrow{PC}\times\overrightarrow{PA}\right|$$
如果$P$在$\triangle ABC$外,則上式會大於$\triangle ABC$的面積
這足以證明 **$P$在$\triangle ABC$內**是**三個有向面積同號**的充分必要條件
:::spoiler Solution
```cpp=
#include <bits/stdc++.h>
#define ff first
#define ss second
using namespace std;
using pll = pair<long long, long long>;
inline pll operator-(const pll &a, const pll &b) { return {a.ff - b.ff, a.ss - b.ss}; }
inline long long operator^(pll a, pll b) { return a.ff * b.ss - a.ss * b.ff; }
inline int sig(long long x) { return (x > 0) - (x < 0); }
void solve() {
pll p, a, b, c;
cin >> p.ff >> p.ss;
cin >> a.ff >> a.ss >> b.ff >> b.ss >> c.ff >> c.ss;
int t = sig((a-p)^(b-p)) + sig((b-p)^(c-p)) + sig((c-p)^(a-p));
if (t == 3 || t == -3) cout << "YES\n";
else cout << "NO\n";
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
int T;
cin >> T;
while (T--)
solve();
return 0;
}
```
:::
---
## a622: B. 關燈遊戲
<code>tags: greedy, number theory</code>
#### 結論: 一直操作編號最大的亮燈直到全滅
兩個顯然的性質
1. 操作順序不影響結果
2. 操作兩次等於不操作,所以規定每盞燈最多操作一次
假設目前編號最大的亮燈是$p$,根據$1.$我們可以先來考慮如何把$p$滅掉,想要把$p$滅掉可以對$p$的任意一個倍數操作,將$p$的倍數分成$p$和$kp\ (k\ge2)$討論,如果選擇操作$kp$,則最大的亮燈變為$kp$,現在討論如何滅掉$kp$,但根據$2.$我們不能再對$kp$操作,只能在選擇一個更大的倍數操作,最大的亮燈編號將會不斷變大永遠無法全滅,因此只能選擇操作$p$使最大的亮燈編號不斷變小直到全滅
實作的部分可以從大到小暴力枚舉因數操作,時間複雜度$O(n\sqrt{n})$
也可以枚舉每個數的倍數求出哪些數的因數包含自己並記錄起來,時間和空間複雜度都是$O(n\log n)$
###### 註: 調和級數的複雜度是$O(\log n)$
一個比較乾淨的作法是從大到小枚舉每個數$i$的倍數去計算$i$改變了幾次,在處理到$i$之前,$sta[i]$的定義是亮/暗(0/1),處理完$i$後$sta[i]$的定義變成操作/不操作(0/1),時間複雜度$O(n\log n)$
:::spoiler Solution
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1e5 + 5;
bool sta[maxn];
void solve() {
int n, ans = 0;
cin >> n;
for (int i = 1; i <= n; i++) cin >> sta[i];
for (int i = n; i; i--) {
for (int j = i+i; j <= n; j += i)
sta[i] ^= sta[j];
ans += sta[i];
}
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
```
:::
---
## a625: C. 括號序列
<code>tags: data structures</code>
如果把`(`當$+1$`)`當$-1$,一個合法的序列要滿足
1. 和為$0$,`(`的數量要等於`)`的數量
2. 任何一個前綴和都要$\ge0$,`(`的數量要大於等於`)`的數量
於是維護前綴和
對於詢問$[l, r]$需要判斷是否有$sum[r]-sum[l-1]=0$和$min(sum[l],sum[l+1],...,sum[r])-sum[l-1]\ge0$
對於修改$i$需要將$sum[i]\sim sum[n]$$\ +2$或$-2$
### Subtask 1 (30%)
暴力
### Subtask 2 (70%)
維護一個區間最小區間加值線段樹
:::spoiler Solution
```cpp=
#include <bits/stdc++.h>
using namespace std;
const int maxn = 4e5 + 5;
int sum[maxn];
string str;
struct segment_tree {
static const int maxn = 16e5 + 5;
int mi[maxn] = {}, tag[maxn] = {};
inline void updata(int p, int v) { mi[p] += v, tag[p] += v; }
inline void pull(int p) { mi[p] = min(mi[p<<1], mi[p<<1|1]) + tag[p]; }
inline void push(int p) {
if (tag[p]) updata(p<<1, tag[p]), updata(p<<1|1, tag[p]);
tag[p] = 0;
}
void build(int l, int r, int p) {
if (l == r) { mi[p] = sum[l]; return; }
int mid = (l+r)>>1;
build(l, mid, p<<1), build(mid+1, r, p<<1|1);
pull(p);
}
int query(int l, int r, int ql, int qr, int p) {
if (ql == 0) return 0;
if (ql == l && qr == r) return mi[p];
push(p);
int mid = (l+r)>>1;
if (qr <= mid) return query(l, mid, ql, qr, p<<1);
else if (mid < ql) return query(mid+1, r, ql, qr, p<<1|1);
else return min(query(l, mid, ql, mid, p<<1), query(mid+1, r, mid+1, qr, p<<1|1));
}
void modify(int l, int r, int ml, int mr, int v, int p) {
if (ml == l && mr == r) { updata(p, v); return; }
int mid = (l+r)>>1;
push(p);
if (mr <= mid) modify(l, mid, ml, mr, v, p<<1);
else if (mid < ml) modify(mid+1, r, ml, mr, v, p<<1|1);
else modify(l, mid, ml, mid, v, p<<1), modify(mid+1, r, mid+1, mr, v, p<<1|1);
pull(p);
}
} seg;
void solve() {
int n, m;
cin >> n >> m;
cin >> str;
for (int i = 1; i <= n; i++)
sum[i] = sum[i-1] + 1 - 2*(str[i-1] == ')');
seg.build(1, n, 1);
int opt, l, r;
while (m--) {
cin >> opt;
if (opt == 1) {
cin >> l >> r;
if ((r-l+1)&1) cout << "NO\n";
else if (seg.query(1, n, r, r, 1) != seg.query(1, n, l-1, l-1, 1))
cout << "NO\n";
else if (seg.query(1, n, l, r, 1) - seg.query(1, n, l-1, l-1, 1) >= 0)
cout << "YES\n";
else cout << "NO\n";
}
else if (opt == 2) {
cin >> l;
if (str[l-1] == '(') {
str[l-1] = ')';
seg.modify(1, n, l, n, -2, 1);
}
else {
str[l-1] = '(';
seg.modify(1, n, l, n, 2, 1);
}
}
}
}
signed main() {
solve();
return 0;
}
```
:::
---
## a621: D. 樹的分數
<code>tags: trees, combinatorics, math</code>
考慮直接算出原樹的分數,用$cnt_u$表示包含節點$u$的路徑個數,則節點$u$對分數的貢獻就是$u\times cnt_u$,根據[排序不等式](https://zh.wikipedia.org/wiki/%E6%8E%92%E5%BA%8F%E4%B8%8D%E7%AD%89%E5%BC%8F),只要將小的編號配小的權重,大的編號配大的權重就可以有最大分數
### Subtask 1 (15%)
顯然越靠近重心$cnt$越大,越靠近葉子$cnt$越小,且從一邊數來第$k$個節點和從另一邊數來第$k$個節點的$cnt$一樣,也就是這兩個節點的編號交換不影響分數,因此答案就是$2^{\left \lfloor \frac{n}{2} \right \rfloor}$
### Subtask 2 (25%)
現在必須算出每個$cnt$的值,以$u$為根包含$u$的路徑有兩種,起點或終點為$u$,和起點在$u$的某個子數上終點在$u$的另一個子樹上,所以有
$$cnt_u=(n-1)+\sum_{i=1}^{d_u-1} \sum_{j=i+1}^{d_u} siz[v_i]\times siz[v_j]$$
其中$d_u$表示點$u$的度數,$v_i$表示$u$的第$i$個鄰居,$siz[v_i]$表示子樹的大小,若同樣的$cnt$值有$k$個,則這$k$個節點的編號可以互換,對答案貢獻$k!$,暴力計算$cnt$的複雜度為$O(\sum d_i^2)=O(n^2)$
### Subtask 3 (60%)
只要將Subtask2的求法優化一下即可,注意到$$2\sum_{i=1}^{d-1} \sum_{j=i+1}^{d} siz[v_i]\times siz[v_j]=(\sum_{i=1}^{d} siz[v_i])^2-\sum_{i=1}^{d} siz[v_i]^2=(n-1)^2-\sum_{i=1}^{d} siz[v_i]^2$$
或者換一種計算方法,全部路徑扣掉起點終點都在同一個子樹的路徑
$$cnt_u=C_2^n-\sum_{i=1}^{d_u} C_2^{siz[v_i]}$$
這樣複雜度變為$O(\sum d)=O(n)$
:::spoiler Solution
```cpp=
#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
const int maxn = 2e5 + 5;
map<long long, int> cnt;
long long fac[maxn];
vector<int> G[maxn];
int n;
long long dfs(int u, int f) {
long long ret = 1, t;
long long s = 1ll * n * (n-1) / 2;
for (int v : G[u]) {
if (v == f) continue;
t = dfs(v, u);
s -= t * (t-1) / 2;
ret += t;
}
s -= (n-ret) * (n-ret-1) / 2;
cnt[s]++;
return ret;
}
void solve() {
cin >> n;
for (int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
G[u].emplace_back(v);
G[v].emplace_back(u);
}
dfs(1, 0);
fac[0] = 1;
for (int i = 1; i <= n; i++)
fac[i] = fac[i-1] * i % mod;
long long ans = 1;
for (pair<long long, int> p : cnt)
ans = ans * fac[p.second] % mod;
cout << ans << '\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
```
:::
---
## a614: E. 數列貼貼
<code>tags: dp, math, matrices</code>
### Subtask 1 (20%)
暴力遞推
### Subtask 2 (80%)
將$a_{i+1}a_i$展開
$$a_{i+1}a_i=(xa_i+ya_{i-1})(xa_{i-1}+ya_{i-2})=x^2a_ia_{i-1}+xya_ia_{i-2}+xya_{i-1}a_{i-1}+y^2a_{i-1}a_{i-2}$$現在嘗試湊出從$\vec v_i=(a_ia_{i-1},a_ia_{i-2},a_{i-1}a_{i-1},a_{i-1}a_{i-2})$到$\vec v_{i+1}=(a_{i+1}a_i,a_{i+1}a_{i-1},a_ia_i,a_ia_{i-1})$
的線性轉移
$\qquad\qquad\qquad a_{i+1}a_i=(x^2,xy,xy,y^2)\cdot \vec v_i$
$\qquad\qquad\qquad a_{i+1}a_{i-1}=(xa_i+ya_{i-1})a_{i-1}=xa_ia_{i-1}+ya_{i-1}a_{i-1}=(x,0,y,0)\cdot \vec v_i$
$\qquad\qquad\qquad a_{i}a_{i}=a_i(xa_{i-1}+ya_{i-2})=xa_ia_{i-1}+ya_{i}a_{i-2}=(x,y,0,0)\cdot \vec v_i$
$\qquad\qquad\qquad a_ia_{i-1}=(1,0,0,0)\cdot \vec v_i$
所以轉移方程為
$$
\begin{bmatrix}
x^2 & xy & xy & y^2 \\
x & 0 & y & 0 \\
x & y & 0 & 0 \\
1 & 0 & 0 & 0
\end{bmatrix}
\begin{bmatrix}
a_ia_{i-1} \\
a_ia_{i-2} \\
a_{i-1}a_{i-1} \\
a_{i-1}a_{i-2} \\
\end{bmatrix}
=\begin{bmatrix}
a_{i+1}a_{i} \\
a_{i+1}a_{i-1} \\
a_{i}a_{i} \\
a_{i}a_{i-1} \\
\end{bmatrix}
$$再設$S_i=a_1a_2+a_2a_3+...+a_{i-2}a_{i-1}$加入轉移式
$$
\begin{bmatrix}
x^2 & xy & xy & y^2 & 0 \\
x & 0 & y & 0 & 0 \\
x & y & 0 & 0 & 0 \\
1 & 0 & 0 & 0 & 0 \\
1 & 0 & 0 & 0 & 1 \\
\end{bmatrix}
\begin{bmatrix}
a_ia_{i-1} \\
a_ia_{i-2} \\
a_{i-1}a_{i-1} \\
a_{i-1}a_{i-2} \\
S_i \\
\end{bmatrix}
=\begin{bmatrix}
a_{i+1}a_{i} \\
a_{i+1}a_{i-1} \\
a_{i}a_{i} \\
a_{i}a_{i-1} \\
S_{i+1} \\
\end{bmatrix}
$$用矩陣快速冪求$\vec v_{n+1}$,時間複雜度$O(5^3\log n)$
:::spoiler Solution
```cpp=
#include <bits/stdc++.h>
using namespace std;
const long long mod = 998244353;
struct matrix {
static const int N = 5;
long long a[N][N] = {}, n = 0, m = 0;
matrix(int _n, int _m) : n(_n), m(_m) {}
long long* operator[](const int &x) { return a[x]; }
};
matrix operator*(matrix A, matrix B) {
int n = A.n, m = B.m, h = A.m;
matrix ret(n, m);
for (int i = 0; i < n; i++)
for (int j = 0; j < m; j++)
for (int k = 0; k < h; k++)
ret[i][j] = (ret[i][j] + A[i][k] * B[k][j]) % mod;
return ret;
}
matrix ksm(matrix A, long long b) {
matrix ret(A.n, A.m);
for (int i = 0; i < A.n; i++) ret[i][i] = 1;
while (b) {
if (b&1) ret = ret * A;
A = A * A;
b >>= 1;
}
return ret;
}
void solve() {
long long a1, a2, a3, x, y, n;
cin >> a1 >> a2 >> x >> y >> n;
matrix T(5, 5), v(5, 1);
T[0][0] = x*x % mod, T[0][1] = T[0][2] = x*y % mod, T[0][3] = y*y % mod;
T[1][0] = T[2][0] = x, T[1][2] = T[2][1] = y;
T[3][0] = T[4][0] = T[4][4] = 1;
a3 = (x*a2 + y*a1) % mod;
v[0][0] = a3*a2 % mod, v[1][0] = a3*a1 % mod;
v[2][0] = a2*a2 % mod, v[3][0] = a2*a1 % mod;
v[4][0] = a1*a2 % mod;
cout << (ksm(T, n-2) * v)[4][0] << '\n';
}
signed main() {
ios::sync_with_stdio(false), cin.tie(0), cout.tie(0);
solve();
return 0;
}
```
:::