题目链接
题解:
这可能是 HNOI2019 里最神的一题了 orz。
首先不难发现一个暴力,记 f ( i , j ) f(i,j) f(i,j) 表示点对 i , j i,j i,j 之间是否存在回文路径,枚举他们的出边暴力转移即可。初始的时候所有的 f ( i , i ) f(i,i) f(i,i) 和两个颜色相同且相邻的点 f ( a , b ) f(a,b) f(a,b) 为 t r u e true true,扔到队列里 b f s bfs bfs 即可。不难发现任意一对边至多只会被枚举 4 4 4 次,因此复杂度是 O ( m 2 ) O(m^2) O(m2) 的。
然后就是神仙想法了,我们可以减少边的数量。令连接两个颜色相同的点的边为同色边,否则为异色边。考虑最终的回文串长啥样,肯定是一段同色边一段异色边不断接起来。由于一条边可以来回走,因此我们要保证对应同色边或异色边的条数的奇偶性相同。也就是说,我们只在意对应的奇偶性而不在意具体的路径长度。
不妨把两种边分开考虑,把所有异色边全部删掉,那么剩下的图中一个连通块里的所有点颜色全部相同。如果一个连通块是二分图,那么就意味着无论怎么走,任意两个点之间路径长度的奇偶性都是固定的。因此对于是二分图的连通块,我们只要保留任意一棵生成树即可。
否则的话,就一定存在奇环。由于我们并不在意路径长度而只在意奇偶性,因此给随便一个点连一条自环即可。
对于异色边,连出来的连通块显然就是一个二分图,也任意保留一棵生成树即可。
因此,边数就变成了 O ( n ) O(n) O(n) 的,直接跑上面的暴力即可 O ( n 2 ) O(n^2) O(n2) 解决。
#include
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;
}
题目链接
题解:
比较套路的一题吧……看到和   m o d   k \bmod~k mod k 有关,就可以想起单位根反演。我们记 g ( i ) g(i) g(i) 表示   m o d   k = i \bmod~k=i mod k=i 时的答案, F ( x ) F(x) F(x) 表示路径长度作为指数,方案数作为系数的关于 x x x 的多项式,第 i i i 项系数为 f i f_i fi。易得:
g ( p ) = ∑ i = 0 L − p f i + p ⋅ 1 k ∑ j = 0 k − 1 ( ω k i ) j = 1 k ∑ j = 0 k − 1 F ( ω k j ) ω k − p j g(p)=\sum_{i=0}^{L-p}f_{i+p}\cdot\frac{1}{k}\sum_{j=0}^{k-1}(\omega_k^i)^j=\frac{1}{k}\sum_{j=0}^{k-1}F(\omega_k^j)\omega_k^{-pj} g(p)=i=0∑L−pfi+p⋅k1j=0∑k−1(ωki)j=k1j=0∑k−1F(ωkj)ωk−pj
接下来考虑如何求 F ( ω k j ) F(\omega_k^j) F(ωkj)。我们显然可以枚举走了 i i i 步,然后是个转移矩阵的 i i i 次方乘一个组合数。即:
F ( x ) = ∑ i = 0 L ( L i ) A i x i = ( A x + I ) L F(x)=\sum_{i=0}^L\binom{L}{i}A^ix^i=(Ax+I)^L F(x)=i=0∑L(iL)Aixi=(Ax+I)L
其中 A A A 是转移矩阵, I I I 是单位矩阵。最终多项式的系数和实际上就是 ( A x + I ) L (Ax+I)^L (Ax+I)L 第 x x x 行第 y y y 列的值,因此矩阵快速幂即可计算出 F ( ω k j ) F(\omega_k^j) F(ωkj) 了。但是如果暴力计算还是 O ( k 2 ) O(k^2) O(k2) 的,于是用到了毛爷论文里的“非 2 2 2 的幂次的DFT算法”。
考虑把 − p j -pj −pj 拆成只和 p − j p-j p−j , p p p 或 j j j 有关的项。不难想到 − p j = 1 2 ( ( p − j ) 2 − p 2 − j 2 ) -pj=\frac{1}{2}((p-j)^2-p^2-j^2) −pj=21((p−j)2−p2−j2),但是有个 1 2 \frac{1}{2} 21 的系数,这会使得没有二次剩余的项变得麻烦。于是稍加变换,得到 − p j = 1 2 ( ( p − j ) ( p − j + 1 ) − p ( p + 1 ) − j ( j − 1 ) ) -pj=\frac{1}{2}((p-j)(p-j+1)-p(p+1)-j(j-1)) −pj=21((p−j)(p−j+1)−p(p+1)−j(j−1)) 即可。于是就变成了:
g ( p ) = ω k − p ( p + 1 ) 2 k ∑ j = 0 k − 1 F ( ω k j ) ω k ( p − j ) ( p − j + 1 ) 2 ω k − j ( j − 1 ) 2 g(p)=\frac{\omega_k^{-\frac{p(p+1)}{2}}}{k}\sum_{j=0}^{k-1}F(\omega_k^j)\omega_k^{\frac{(p-j)(p-j+1)}{2}}\omega_k^{-\frac{j(j-1)}{2}} g(p)=kωk−2p(p+1)j=0∑k−1F(ωkj)ωk2(p−j)(p−j+1)ωk−2j(j−1)
已经很像卷积了,唯一的遗憾就是 j j j 最大是到 k − 1 k-1 k−1,而不是 p p p。不妨令 a i = ω k ( i − k ) ( i + 1 − k ) 2 , b i = F ( ω k i ) ω k − i ( i − 1 ) 2 a_i=\omega_k^{\frac{(i-k)(i+1-k)}{2}},b_i=F(\omega_k^i)\omega_k^{-\frac{i(i-1)}{2}} ai=ωk2(i−k)(i+1−k),bi=F(ωki)ωk−2i(i−1),并且当 i ≤ k i\le k i≤k 时 b i = 0 b_i=0 bi=0,那么上面的算式可以变成:
g ( p ) = ω k − p ( p + 1 ) 2 k ∑ j = 0 p + k a p + k − j b j g(p)=\frac{\omega_k^{-\frac{p(p+1)}{2}}}{k}\sum_{j=0}^{p+k}a_{p+k-j}b_j g(p)=kωk−2p(p+1)j=0∑p+kap+k−jbj
这样就是一个卷积形式了,MTT即可。复杂度 O ( k l o g k ) O(klogk) O(klogk)。
#include
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里最简单的了……显然对于一个连续的下降段,它们修改成相同的数最优。于是贡献就是 ∑ ( x − a i ) 2 \sum(x-a_i)^2 ∑(x−ai)2,稍加计算就可以知道 x x x 取平均数时最优。然后我们假设把所有的下降段都缩成一个数,这样又会出现新的下降段,不断缩直到数列不降,这种策略一定最优。
考虑使用单调栈维护。从左向右扫,如果栈顶元素大于当前元素到栈顶元素之间所有数字的平均数,就弹出。于是第一问就可以在 O ( n ) O(n) O(n) 的时间内计算出来了。
考虑第二问怎么做。我们可以维护一个从左向右的单调栈和一个从右向左的单调栈,如果强制在线的话可以把单调栈可持久化一下,不过这题可以离线做,那就不用可持久化了。
考虑如何合并两个栈以及中间元素,我们可以大致模拟一下,从中间元素开始向右扫,每次找到最大的 l l l 使单调栈中的第 l − 1 l-1 l−1 个元素小于等于 l l l 到当前位置的平均数,然后区间set成同一个值。于是找 l l l 可以用二分解决。那么我们扫到什么时候才算结束呢?肯定是找到最大的 r r r,这个 r r r 找到对应的 l l l 之后,形成的那个值要小于等于右边单调栈的第 r − 1 r-1 r−1 个元素。
因此我们二分 r r r,里面再套一个二分计算 l l l 即可。复杂度 O ( n l o g 2 n ) O(nlog^2n) O(nlog2n)。
#include
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;
}