HNOI2019 DAY2 題解
校園旅行
題解:
這可能是 HNOI2019 裡最神的一題了 orz。
首先不難發現一個暴力,記 表示點對 之間是否存在迴文路徑,列舉他們的出邊暴力轉移即可。初始的時候所有的 和兩個顏色相同且相鄰的點 為 ,扔到佇列裡 即可。不難發現任意一對邊至多隻會被列舉 次,因此複雜度是 的。
然後就是神仙想法了,我們可以減少邊的數量。令連線兩個顏色相同的點的邊為同色邊,否則為異色邊。考慮最終的迴文串長啥樣,肯定是一段同色邊一段異色邊不斷接起來。由於一條邊可以來回走,因此我們要保證對應同色邊或異色邊的條數的奇偶性相同。也就是說,我們只在意對應的奇偶性而不在意具體的路徑長度。
不妨把兩種邊分開考慮,把所有異色邊全部刪掉,那麼剩下的圖中一個連通塊裡的所有點顏色全部相同。如果一個連通塊是二分圖,那麼就意味著無論怎麼走,任意兩個點之間路徑長度的奇偶性都是固定的。因此對於是二分圖的連通塊,我們只要保留任意一棵生成樹即可。
否則的話,就一定存在奇環。由於我們並不在意路徑長度而只在意奇偶性,因此給隨便一個點連一條自環即可。
對於異色邊,連出來的連通塊顯然就是一個二分圖,也任意保留一棵生成樹即可。
因此,邊數就變成了 的,直接跑上面的暴力即可 解決。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 5005, MAXM = 1000005, MAXQ = 13000005;
struct Edge { int to, next; } edge[MAXM], gra[MAXM];
int head[MAXN], hd[MAXN], val[MAXN], n, m, Q, tot;
void addedge(int u, int v) {
edge[++tot] = (Edge) { v, head[u] };
head[u] = tot;
}
void addgra(int u, int v) {
gra[++tot] = (Edge) { v, hd[u] };
hd[u] = tot;
}
int clr[MAXN], quex[MAXQ], quey[MAXQ], flag;
bool f[MAXN][MAXN];
void dfs1(int u) {
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (val[v] == val[u]) {
if (~clr[v]) flag |= clr[v] == clr[u];
else clr[v] = !clr[u], dfs1(v), addgra(u, v), addgra(v, u);
}
}
}
void dfs2(int u) {
clr[u] = 1;
for (int i = head[u]; i; i = edge[i].next) {
int v = edge[i].to;
if (val[v] != val[u] && !clr[v])
dfs2(v), addgra(u, v), addgra(v, u);
}
}
char str[MAXN];
int main() {
scanf("%d%d%d", &n, &m, &Q);
scanf("%s", str + 1);
for (int i = 1; i <= n; i++) val[i] = str[i] - '0';
for (int i = 1; i <= m; i++) {
int x, y; scanf("%d%d", &x, &y);
addedge(x, y), addedge(y, x);
}
tot = 0;
memset(clr, -1, sizeof(clr));
for (int i = 1; i <= n; i++) if (clr[i] < 0) {
clr[i] = flag = 0, dfs1(i);
if (flag) addgra(i, i);
}
memset(clr, 0, sizeof(clr));
for (int i = 1; i <= n; i++)
if (!clr[i]) dfs2(i);
int he = 0, ta = 0;
for (int i = 1; i <= n; i++) {
f[i][i] = 1;
quex[ta] = i, quey[ta] = i, ++ta;
for (int j = hd[i]; j; j = gra[j].next) {
int v = gra[j].to;
if (v > i && val[v] == val[i])
quex[ta] = i, quey[ta] = v, ++ta;
}
}
while (he < ta) {
int x = quex[he], y = quey[he]; ++he;
for (int j = hd[x]; j; j = gra[j].next)
for (int k = hd[y]; k; k = gra[k].next) {
int a = gra[j].to, b = gra[k].to;
if (a > b) swap(a, b);
if (val[a] == val[b] && !f[a][b]) {
f[a][b] = 1;
quex[ta] = a, quey[ta] = b, ++ta;
}
}
}
while (Q--) {
int x, y; scanf("%d%d", &x, &y);
if (x > y) swap(x, y);
puts(f[x][y] ? "YES" : "NO");
}
return 0;
}
白兔之舞
題解:
比較套路的一題吧……看到和 有關,就可以想起單位根反演。我們記 表示 時的答案, 表示路徑長度作為指數,方案數作為係數的關於 的多項式,第 項係數為 。易得:
接下來考慮如何求 。我們顯然可以列舉走了 步,然後是個轉移矩陣的 次方乘一個組合數。即:
其中 是轉移矩陣, 是單位矩陣。最終多項式的係數和實際上就是 第 行第 列的值,因此矩陣快速冪即可計算出 了。但是如果暴力計算還是 的,於是用到了毛爺論文裡的“非 的冪次的DFT演算法”。
考慮把 拆成只和 , 或 有關的項。不難想到 ,但是有個 的係數,這會使得沒有二次剩餘的項變得麻煩。於是稍加變換,得到 即可。於是就變成了:
已經很像卷積了,唯一的遺憾就是 最大是到 ,而不是 。不妨令 ,並且當 時 ,那麼上面的算式可以變成:
這樣就是一個卷積形式了,MTT即可。複雜度 。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int BIT = 18, MAXN = 1 << BIT;
int MOD;
namespace PolyMTT {
const double PI = 3.14159265358979323846264;
int hasinit;
struct C {
double x, y;
C operator+(const C &a) const { return (C) { x + a.x, y + a.y }; }
C operator-(const C &a) const { return (C) { x - a.x, y - a.y }; }
C operator*(const C &a) const { return (C) { x * a.x - y * a.y, x * a.y + y * a.x }; }
} w[2][MAXN];
void init() {
hasinit = 1;
const int temp = 1 << BIT;
for (int i = 0; i < MAXN; i++) w[0][i] = (C) { cos(PI * 2 * i / temp), sin(PI * 2 * i / temp) };
for (int i = 0; i < MAXN; i++) w[1][i] = (C) { cos(PI * 2 * i / temp), -sin(PI * 2 * i / temp) };
}
void fft(C *a, int n, int rev) {
if (!hasinit) init();
for (int i = 0, j = 0; i < n; i++) {
if (i < j) swap(a[i], a[j]);
for (int k = n >> 1; (j ^= k) < k; k >>= 1);
}
static C ww[MAXN];
for (int h = 2; h <= n; h <<= 1) {
int hh = h >> 1, t = (1 << BIT) / h;
for (int i = 0; i < hh; i++) ww[i] = w[rev][i * t];
for (int i = 0; i < n; i += h) {
C *aj = a + i, *ah = aj + hh, *wn = ww;
for (int j = i; j < i + hh; ++j, ++aj, ++ah, ++wn) {
const C x = *aj, y = *ah * *wn;
*aj = x + y, *ah = x - y;
}
}
}
if (rev) for (int i = 0; i < n; i++) a[i].x /= n, a[i].y /= n;
}
void mtt(int *a, int *b, int *c, int n, int m, int p) {
if ((ll)n * m <= 4096) {
typedef unsigned long long ull;
static ull res[MAXN];
for (int i = 0; i < p; i++) res[i] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m && i + j < p; j++) res[i + j] += (ull)a[i] * b[j];
if (!(i & 15)) for (int j = 0; j < p; j++) res[j] %= MOD;
}
for (int i = 0; i < p; i++) c[i] = res[i] % MOD;
} else {
static C A[MAXN], B[MAXN], D[MAXN];
int len = 1; while (len < n + m - 1) len <<= 1;
for (int i = 0; i < len; i++) A[i] = B[i] = (C) { 0.0, 0.0 };
for (int i = 0; i < n; i++) A[i] = (C) { (double)(a[i] & 0x7FFF), (double)(a[i] >> 15) };
for (int i = 0; i < m; i++) B[i] = (C) { (double)(b[i] & 0x7FFF), (double)(b[i] >> 15) };
fft(A, len, 0), fft(B, len, 0);
for (int i = 0; i < len; i++) {
int j = (len - i) & (len - 1);
C ca = (C) { (A[i].x + A[j].x) * 0.5, (A[i].y - A[j].y) * 0.5 };
C cb = (C) { (B[i].x + B[j].x) * 0.5, (B[i].y - B[j].y) * 0.5 };
D[i] = ca * cb;
}
for (int i = 0; i < len; i++) A[i] = A[i] * B[i];
fft(A, len, 1), fft(D, len, 1);
for (int i = 0; i < p; i++) {
ll bd = (ll)round(D[i].x) % MOD + MOD, ac = (bd - (ll)round(A[i].x) % MOD + MOD) % MOD;
c[i] = ((ac << 30) + (((ll)round(A[i].y) % MOD + MOD) << 15) + bd) % MOD;
}
}
}
}
ll modpow(ll a, int b, int c = MOD) {
ll res = 1;
for (; b; b >>= 1) {
if (b & 1) res = res * a % c;
a = a * a % c;
}
return res;
}
int ww[MAXN], A[MAXN], B[MAXN], n, K, L, x, y, G;
int find_g() {
int p = MOD - 1;
vector<int> vec;
for (int i = 2; i * i <= p; i++) if (p % i == 0) {
vec.push_back(i);
while (p % i == 0) p /= i;
}
if (p > 1) vec.push_back(p);
for (int g = 1;; g++) {
int flag = 1;
for (int i : vec) if (modpow(g, (MOD - 1) / i) == 1) { flag = 0; break; }
if (flag) return g;
}
}
struct Matrix {
ll a[3][3]; int n;
Matrix() { n = 0; memset(a, 0, sizeof(a)); }
void init(int t) {
n = t, memset(a, 0, sizeof(a));
for (int i = 0; i < t; i++) a[i][i] = 1;
}
Matrix operator*(const Matrix &m) const {
Matrix res; res.n = n;
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) if (a[i][j])
for (int k = 0; k < n; k++)
res.a[i][k] += a[i][j] * m.a[j][k];
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++) res.a[i][j] %= MOD;
return res;
}
} mat;
Matrix modpow(Matrix m, int p) {
Matrix res; res.init(m.n);
for (; p; p >>= 1) {
if (p & 1) res = res * m;
m = m * m;
}
return res;
}
int main() {
scanf("%d%d%d%d%d%d", &n, &K, &L, &x, &y, &MOD);
G = find_g(); --x, --y;
ll wn = modpow(G, (MOD - 1) / K);
for (int i = 0; i < n; i++)
for (int j = 0; j < n; j++)
scanf("%lld", &mat.a[i][j]);
mat.n = n;
ww[0] = 1;
for (int i = 0; i < K; i++) {
ww[i + 1] = ww[i] * wn % MOD;
Matrix res = mat;
for (int j = 0; j < n; j++)
for (int k = 0; k < n; k++)
res.a[j][k] = (mat.a[j][k] * ww[i] + (j == k)) % MOD;
res = modpow(res, L);
A[i] = res.a[x][y];
}
for (int i = 0; i < K; i++)
A[i] = (ll)A[i] * ww[K - (ll)i * (i - 1) / 2 % K] % MOD;
for (int i = 0; i < K << 1; i++)
B[i] = ww[((ll)(i - K) * (i - K + 1) / 2 % K + K) % K];
PolyMTT::mtt(A, B, A, K << 1, K << 1, K << 1);
ll inv = modpow(K, MOD - 2);
for (int i = 0; i < K; i++) {
ll t = A[i + K];
printf("%lld\n", t * inv % MOD * ww[K - (ll)i * (i + 1) / 2 % K] % MOD);
}
return 0;
}
序列
題解:
感覺這題是D2裡最簡單的了……顯然對於一個連續的下降段,它們修改成相同的數最優。於是貢獻就是,稍加計算就可以知道 取平均數時最優。然後我們假設把所有的下降段都縮成一個數,這樣又會出現新的下降段,不斷縮直到數列不降,這種策略一定最優。
考慮使用單調棧維護。從左向右掃,如果棧頂元素大於當前元素到棧頂元素之間所有數字的平均數,就彈出。於是第一問就可以在 的時間內計算出來了。
考慮第二問怎麼做。我們可以維護一個從左向右的單調棧和一個從右向左的單調棧,如果強制線上的話可以把單調棧可持久化一下,不過這題可以離線做,那就不用可持久化了。
考慮如何合併兩個棧以及中間元素,我們可以大致模擬一下,從中間元素開始向右掃,每次找到最大的 使單調棧中的第 個元素小於等於 到當前位置的平均數,然後區間set成同一個值。於是找 可以用二分解決。那麼我們掃到什麼時候才算結束呢?肯定是找到最大的 ,這個 找到對應的 之後,形成的那個值要小於等於右邊單調棧的第 個元素。
因此我們二分 ,裡面再套一個二分計算 即可。複雜度 。
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int MAXN = 100005, MOD = 998244353;
const double EPS = 1E-10;
int sta[MAXN], stb[MAXN], arr[MAXN], ans[MAXN], n, m;
ll suma[MAXN], sumb[MAXN], sum[MAXN], sum2[MAXN];
double numa[MAXN], numb[MAXN];
vector<int> bk[MAXN];
struct Query { int k, id; };
vector<Query> ask[MAXN];
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;
}
double get_avg(int l, int r) { return (double)(sum[r] - sum[l - 1]) / (r - l + 1); }
int main() {
scanf("%d%d", &n, &m);
int tpa = 0, tpb = 0;
for (int i = 1; i <= n; i++) {
scanf("%d", arr + i);
sum[i] = sum[i - 1] + arr[i];
sum2[i] = (sum2[i - 1] + (ll)arr[i] * arr[i]) % MOD;
while (tpa > 0 && numa[sta[tpa]] - EPS > get_avg(sta[tpa] + 1, i)) --tpa;
int lst = sta[tpa];
ll now = sum[i] - sum[lst];
numa[i] = (double)now / (i - lst);
now %= MOD;
ll t = now * modpow(i - lst, MOD - 2) % MOD;
suma[i] = (suma[lst] + sum2[i] - sum2[lst] - t * now) % MOD;
sta[++tpa] = i;
}
printf("%lld\n", (suma[n] + MOD) % MOD);
stb[0] = n + 1;
for (int i = n; i > 0; i--) {
while (tpb > 0 && numb[stb[tpb]] + EPS < get_avg(i, stb[tpb] - 1)) bk[i].push_back(stb[tpb--]);
int nxt = stb[tpb] - 1;
ll now = sum[nxt] - sum[i - 1];
numb[i] = (double)now / (nxt - i + 1);
now %= MOD;
ll t = now * modpow(nxt - i + 1, MOD - 2) % MOD;
sumb[i] = (sumb[nxt + 1] + sum2[nxt] - sum2[i - 1] - t * now) % MOD;
stb[++tpb] = i;
}
for (int i = 1; i <= m; i++) {
int x, k; scanf("%d%d", &x, &k);
ask[x].push_back((Query) { k, i });
}
tpa = 0;
auto find_left = [&](int mid, int sub)->int {
int a = 0, b = tpa + 1;
while (a + 1 < b) {
int meow = (a + b) >> 1;
double t = (double)(sum[stb[mid] - 1] - sum[sta[meow]] + sub) / (stb[mid] - 1 - sta[meow]);
if (t - EPS > numa[sta[meow]]) a = meow;
else b = meow;
}
return a;
};
for (int i = 1; i <= n; i++) {
--tpb;
for (int j = (int)bk[i].size() - 1; j >= 0; j--) stb[++tpb] = bk[i][j];
for (Query q : ask[i]) {
int l = 0, r = tpb + 1, sub = q.k - arr[i];
while (l + 1 < r) {
int mid = (l + r) >> 1, a = find_left(mid, sub);
if ((double)(sum[stb[mid] - 1] - sum[sta[a]] + sub) / (stb[mid] - sta[a] - 1) + EPS < numb[stb[mid]]) l = mid;
else r = mid;
}
int tl = find_left(l, sub);
ll now = (sum[stb[l] - 1] - sum[sta[tl]] + sub) % MOD;
ll t = now * modpow(stb[l] - 1 - sta[tl], MOD - 2) % MOD;
ans[q.id] = (suma[sta[tl]] + sumb[stb[l]] + sum2[stb[l] - 1] - sum2[sta[tl]] - (ll)arr[i] * arr[i] + (ll)q.k * q.k - t * now) % MOD;
}
while (tpa > 0 && numa[sta[tpa]] - EPS > get_avg(sta[tpa] + 1, i)) --tpa;
sta[++tpa] = i;
}
for (int i = 1; i <= m; i++) printf("%d\n", (ans[i] + MOD) % MOD);
return 0;
}
相關文章
- HNOI2019 DAY1 題解
- P8125 [BalticOI 2021 Day2] The short shank 題解
- offer68題 Day2
- 題解:P9788 [ROIR 2020 Day2] 區域規劃
- LeetCode刷題記錄——day2LeetCode
- 【題解】Solution Set - NOIP2024集訓Day2 線段樹
- DAY2 做題變態青蛙跳臺階
- day2
- DAY2微軟主題論壇完整回顧!微軟
- Day2(補)
- 前端入門-day2(常見css問題及解答)前端CSS
- (Day2)常用stl
- Laravel 框架 day2Laravel框架
- day2 Java NIOJava
- 撩課-Web大前端每天5道面試題-Day2Web前端面試題
- Scrum衝刺-Day2Scrum
- ZJUT12 Day2
- Day2:html和cssHTMLCSS
- HDU-ACM 2024 Day2ACM
- 8.8 Day2 - 嘿嘿黑黑黑
- Scrum 衝刺部落格-day2Scrum
- 演算法訓練day2演算法
- 24暑假集訓day2上午
- 程式碼隨想錄-day2
- python基礎學習day2Python
- 團隊專案衝刺-day2
- linux學習day2——tmux和vimLinux
- 前十天衝刺(DAY2)
- java基礎Day2 windows常用快捷鍵JavaWindows
- 24暑假集訓day2下午
- World Tour Finals 2022 Day2 E: Adjacent Xor GameGAM
- Day2——基於ECS快速搭建Docker環境Docker
- day2 超過經理收入的員工
- 【華為機試線上訓練】Day2
- jzoj 6797. 【2014廣州市選day2】hanoi
- 職業生涯1.0知識儲備中...day2
- Day2:Windows常用快捷鍵與基本的Dos命令Windows
- 題解