我的生命已如风中残烛...

思维好题 问题等价于\(m\)个数和为0, 求所有前缀和都不小于零的排列的个数.
将所有权值 \(-1\) 就可以了.
可以在最后添加一个\(-1\), 这样就是求前\(m\)个大于, 把排列连成环, 这样就有一个很好的性质, 每个圆排列只有一种断开的方法.
这个画个图比较好理解, 假设当前已经是合法的方案, 左右移动一下再断开都是不合法的.
圆排列的排列数是\(m!\), 因为\(-1\)要钦定一个, 所以除以\(-1\)的个数.
最后答案就是\(\frac{m!}{m+1-n}\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <bits/stdc++.h>
using namespace std;

const int MOD = 998244353;
int n, m, x;

int main() {
int res = 1;
scanf("%d", &n);
for (int i = 1; i <= n; i++) scanf("%d", &x), m += x;
for (int i = 1; i <= m; i++) if (i != m - n + 1) res = res * 1ll * i % MOD;
printf("%d\n", res);
return 0;
}

luogu: P6372 区间加区间sin和 线段树上只维护\(sin\)和是很难更新的,想到和角公式,维护\(cos\)的和。 \[sin(x+y)=sin(x)*cos(y)+cos(x)*sin(y)\] \[cos(x+y)=cos(x)*cos(y)-sin(x)*sin(y)\] 这道题卡常,所以要尽量减少\(sin\)\(cos\)的运算。
注意一下懒标记用\(long long\)存就可以了。
时间复杂度\(O((n+m)log n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<cstdio>
#include<cmathjax>
using namespace std;
#define lson (p << 1)
#define rson ((p << 1) | 1)
#define mid ((l + r) >> 1)

const int N = 5e5 + 10;
int n, m;
long long tag[N << 2];
double sinx, cosx, b[N], a[N], Sin[N << 2], Cos[N << 2];

int read() {
int w = 0;
char ch = getchar();
while(ch > '9' || ch < '0') ch = getchar();
while(ch <= '9' && ch >= '0') {
w = w * 10 + ch - 48;
ch = getchar();
}
return w;
}
void build(int l,int r,int p) {
if(l == r) {
Sin[p] = a[l];
Cos[p] = b[l];
return;
}
build(l, mid, lson);
build(mid + 1, r, rson);
Sin[p] = Sin[rson] + Sin[lson];
Cos[p] = Cos[rson] + Cos[lson];
}
void add(int p, long long v, double sv, double cv) {
double Sumsin = Sin[p];
Sin[p] = Sin[p] * cv + Cos[p] * sv;
Cos[p] = Cos[p] * cv - Sumsin * sv;
tag[p] += v;
}
void pushdown(int p) {
if(tag[p] != 0) {
add(lson, tag[p], sin(tag[p]), cos(tag[p]));
add(rson, tag[p], sin(tag[p]), cos(tag[p]));
tag[p] = 0;
}
}
void modify(int p, int l, int r, int L, int R, long long v) {
if(l > R || r < L) return;
if(l >= L && R >= r) {
add(p, v, sinx, cosx);
return;
}
pushdown(p);
modify(lson, l, mid, L, R, v);
modify(rson, mid + 1, r, L, R, v);
Sin[p] = Sin[lson] + Sin[rson];
Cos[p] = Cos[lson] + Cos[rson];
}
double query(int p, int l, int r, int L, int R) {
if(l > R || r < L) return 0.0;
if(l >= L && R >= r) return Sin[p];
pushdown(p);
return query(lson, l, mid, L, R) + query(rson, mid + 1, r, L, R);
}

int main() {
n = read();
for(int i = 1; i <= n; i++) {
int x = read();
a[i] = sin(x), b[i] = cos(x);
}
build(1, n, 1);
m=read();
while(m--) {
int opt, l, r, v;
opt = read();
if(opt == 1) {
l = read(); r = read(); v = read();
sinx=sin(v),cosx=cos(v);
modify(1, 1, n, l, r, (long long)(v));
} else {
l = read(); r = read();
printf("%.1lf\n",query(1, 1, n, l, r));
}
}
return 0;
}

题目链接:LOJ 2143

好像是循环矩阵一样的东西。
今天早上才做过一道用矩阵的[题目][https://www.luogu.com.cn/problem/P2886]

按照题目去算显然是会超时的, 可以按\(\bmod k\)的余数分类。
\(f[i][j]\)表示从\(i\)个数中选若干个数,余数为\(j\)的方案数。
转移是\(f[i][j]=f[i][j]+f[x][j-1]*f[y][j-2]\),\(((j-1+j-2)\bmod k=j)\).
因为只和\(j\)有关, 所以可以用矩阵快速幂优化。
注意\(k\)可能等于1,初始化的时候注意一下。
时间复杂度\(\mathjaxcal{O}(k^{2\log {nk}})\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <bits/stdc++.h>
using namespace std;

int n, k, r, p;

struct Martix {
int a[55];
Martix operator * (const Martix& x) const {
Martix tmp;
memset(tmp.a, 0, sizeof tmp.a);
for (int i = 0; i < k; i++)
for (int j = 0; j < k; j++) {
tmp.a[(i + j) % k] = (tmp.a[(i + j) % k] + a[i] * 1ll * x.a[j] % p) % p;
}
return tmp;
}
} ans;
Martix fpow(Martix a, long long b) {
Martix ans;
memset(ans.a, 0, sizeof ans.a);
ans.a[0] = 1;
for (; b; b >>= 1, a = a * a) if (b & 1) ans = a * ans;
return ans;
}

int main() {
scanf("%d %d %d %d", &n, &p, &k, &r);
ans.a[1 % k]++, ans.a[0]++;
ans = fpow(ans, n * 1ll * k);
printf("%d\n", ans.a[r]);
return 0;
}

感觉这东西也不算是数据结构... 模板题见CF896C 随机数据的复杂度证明见知乎专栏

虽然构造数据只能拿来骗分,有时候作为辅助工具是比较方便的。
只用讲基本的操作。
大概是把相同的元素合并成一个块以减少时间复杂度。

块信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
struct Node-t {
int l, r;
mutable int v;
Node-t(const int &il, const int &ir, const int &iv) : l(il), r(ir), v(iv) {}
inline bool operator<(const Node-t &o) const { return l < o.l; }
};
```
mutable 的意思是“可变的”,让我们可以在后面的操作中修改 v 的值。在 C++ 中,mutable 是为了突破 const 的限制而设置的。被 mutable 修饰的变量(mutable 只能用于修饰类中的非静态数据成员),将永远处于可变的状态,即使在一个 const 函数中。

## split函数
```cpp
#define IT set<node>::iterator
IT split(int pos) {
IT it = s.lower-bound(node(pos));
if (it->l == pos && it != s.end())
return it;
--it;
int v = it->v, l = it->l, r = it->r;
s.erase(it);
s.insert(node(l, pos - 1, v));
return s.insert(node(pos, r, v)).first;
}

注意 set 自带的 lower-bound 和 upper-bound 的时间复杂度为\(\mathjaxcal{O}(log n)\)。 但使用 algorithm 库中的 lower-bound 和 upper-bound 函数对 set 中的元素进行查询,时间复杂度为 \(\mathjaxcal{O}(n)\)。似乎是 set 不支持随机访问的原因。
s.insert(node(pos, r, v)).first 返回的是插入元素的地址。

assign函数

1
2
3
4
5
void assign(int l, int r, int v) {
IT itr = split(r + 1), itl = split(l);
s.erase(itl, itr);
s.insert(node(l, r, v));
}

作用是推平一段区间,也是\(ODT\)时间复杂度的保证。
注意如果先 split 左端点再 split 右端点就会 RE,原因是 l 和 r 在一个 node 上时,split(r) 会 erase 这个 node 再重新插入,导致 split(l) 的迭代器失效。

没被卡的习题比较少... - 「SHOI2015」脑洞治疗仪 - 「Luogu 2787」理理思维 - 「Luogu 4979」矿洞:坍塌

放一下正经的题
- LOJ 2005 「TJOI / HEOI2016」排序 - LOJ 2958 「COCI 2009.10」ALADIN

阅读全文 »

鸽了好多天,今天终于开始写了233...

分块思想

实际上分块并不能算一种数据结构?
分块的基本思想是,将原来的数据经过适当的划分(分成一个个块的样子)。
每次修改和询问时,都把一个块内元素当作整体处理,而边角直接暴力。
莫队就是基于分块思想实现的。

分块的复杂度主要取决于块长,根据均值不等式,块长为\(\mathjaxcal{O}(\sqrt n)\)时最优。当然不能一概而论。详细分析可以阅读\(2017\)年国家集训队论文中徐明宽的《非常规大小分块算法初探》。 所以分块的时间复杂度一般都是带根号的...

分块入门

基本操作

块大小:

1
2
3
4
5
size = sqrt(n - 1) + 1
```
块个数:
```cpp
block = (n - 1) / size + 1
预处理每个块的区间和每个元素所在块编号:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
for (int i = 1; i <= block; i++) {
L[i] = R[i - 1] + 1;
R[i] = i * blo;
}
R[block] = n;
for (int i = 1; i <= block; i++) {
for (int j = L[i]; j <= R[i]; j++) bel[j] = i;
}
```
### 入门例题
先看看两道入门题...
#### [LOJ 6280 入门分块4](https://loj.ac/problem/6280)
若$opt=0$,表示将位于$[l,r]$的之间的数字都加$c$。
若$opt=1$,表示询问位于$[l,r]$的所有数字的和$\bmod (c+1)$。

这道题看起来是线段树的模板题,~~实际上就是~~。
做法不难想到,分好块以后,给每个块记录一个$tag$和$sum$。
$tag$表示这个块的所有元素都要加多少,相当于线段树里面的懒标记。
每次操作,$[l,r]$中完整块都可以$\mathjaxcal{O}(1)$做,而边角的元素一个一个去加或者统计就可以了。
因为只有$\mathjaxcal{O}(\sqrt n)$个块,边角的元素个数也是$\mathjaxcal{O}(\sqrt n)$。
所以总的时间复杂度是$\mathjaxcal{O}(\sqrt n)$。

为了方便理解,放一下修改的代码。
```cpp
void upd(int l, int r, int v) {
if (bel[l] == bel[r]) {
for (int i = l; i <= r; i++) a[i] += v, sum[bel[i]] += v;;
} else {
for (int i = l; i <= R[bel[l]]; i++) a[i] += v, sum[bel[i]] += v;
for (int i = L[bel[r]]; i <= r; i++) a[i] += v, sum[bel[i]] += v;
for (int i = bel[l] + 1; i < bel[r]; i++) tag[i] += v;
}
}
阅读全文 »

其实很早就想学一下这类问题了,不过因为各种原因鸽到现在...
links:
普通版 加强版 加强版的加强版

普通版

看大佬的博客学习了一下.
有一个很好懂的\(\mathjaxcal{O}(n^3)\)\(dp\)方法.
\(f-i\)表示大小为\(i\)的无标号树的组成方式.
儿子的子树大小为\(i-1\),假设有三个儿子大小为\(a,b,c\),\(0\leq a\leq b\leq c\leq i-1\).
分类讨论一下转移就可以了.因为无标号,所以不能简单的乘法原理.
1. \(a=b=c\),\(f-i+=\binom{f-a+3-1}{3}\)
2. \(a=b\), \(f-i+=\binom{f-a+2-1}{2}\times f-c\),\(b=c\)时也是同理.
3. \(a < b < c\), \(f-i+=f-a\times f-b \times f-c\).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include<bits/stdc++.h>
using namespace std;

const int MOD = 1e9 + 7;
int n, f[405];

inline int fpow(int a, int b) {
int ans = 1;
for (; b; b >>= 1, a = a * 1ll * a % MOD) if (b & 1) ans = ans * 1ll * a % MOD;
return ans;
}
int inv2 = fpow(2, MOD - 2), inv6 = fpow(6, MOD - 2);
int C(int f, int d) {
if (d == 2) {
return (f + 1) * 1ll * f % MOD * inv2 % MOD;
} else {
return (f + 1) * 1ll * (f + 2) % MOD * 1ll * f % MOD * inv6 % MOD;
}
}

int main() {
scanf("%d", &n); f[0] = 1;
for (int k = 1; k <= n; k++)
for (int a = 0; a <= k; a++)
for (int b = a; b <= k; b++) {
int c = k - a - b - 1;
if (c < b || b < a) break;
if (a == b && b == c) f[k] = (f[k] * 1ll + C(f[a], 3)) % MOD;
else if (a == b) f[k] = (f[k] * 1ll + (C(f[a], 2) % MOD * 1ll * f[c] % MOD)) % MOD;
else if (b == c) f[k] = (f[k] * 1ll + (C(f[b], 2) % MOD * 1ll * f[a] % MOD)) % MOD;
else f[k] = (f[k] * 1ll + f[a] * 1ll * f[b] % MOD * f[c] % MOD) % MOD;
}
printf("%d\n", f[n]);
return 0;
}

加强版

因为\(n\leq 5000\),所以朴素的\(dp\)是过不了的.
考虑一下怎么优化.
普通的\(dp\)要去枚举所有子树的大小,那么有没有办法把大小一样的子树一起考虑,像树形\(dp\)一样去计算贡献.
\(f[s][i][j]\)表示大小为\(i\)的树,根节点度数为\(j\),儿子的子树大小不超过\(s\)时的方案数.
考虑怎么转移, 假设大小为\(s\)的子树有\(k\)个.(\(k\times s<i\)\(k\leq j\)).
\(k\)个子树的形态的方案数是\(\binom{(\sum{f_{s-1,s,j}})+k-1}{k}\).
在乘上\(f_{s-1,i-s\times k,j-k}\)就是答案了.
用背包的方式来\(dp\),可以省去第一维空间.

石锤我不会写背包

code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 5005, MOD = 1e9 + 7;
ll f[N][4], n; // 1 -> 大小 2 -> 度数

inline ll fpow(ll a, ll b) {
int ans = 1;
for (; b; b >>= 1, a = a * a % MOD) if (b & 1) ans = ans * a % MOD;
return ans;
}

ll inv2 = fpow(2, MOD - 2), inv6 = fpow(6, MOD - 2);

inline ll C(int now, int k) {
if (k == 0) return 1;
if (k == 1) return now % MOD;
if (k == 2) return now * (now + 1ll) % MOD * inv2 % MOD;
else return now * (now + 1ll) % MOD * (now + 2ll) % MOD * inv6 % MOD;
}

int main() {
scanf("%lld", &n);
f[1][0] = 1; ll now = 0;
for (int s = 1; s <= n; s++) { // s -> 子树大小限制
now = 0;
for (int i = 0; i <= 3; i++) now = (now + f[s][i]) % MOD;
if (n == s) break;
// cout << now << endl;
for (int i = n; i > 0; i--) {
for (int j = 1; j <= 3; j++) {
for (int k = 1; k <= j && k * s < i; k++) {
f[i][j] = (f[i][j] + (f[i - s * k][j - k] * C(now, k) % MOD)) % MOD;
}
}
}
}
printf("%lld\n", now);
return 0;
}

加强版的加强版

阅读全文 »

题目

给定一张\(N\)个点\(M\)条边的无向图,每条边要染一个编号在\(1\)\(K\)的颜色。
你可以对一张染色了的图进行若干次操作,每次操作形如,在图中选择一个简单环(即不经过相同点的环),并且将其颜色逆(顺)时针旋转一个单位。
两种染色方案被认为是本质相同的,当且仅当其中一种染色后的图经过若干次操作后可以变成另一种染色后的图。
问有多少本质不同的染色方案,输出对\(10^9+7\)取模。 arc062f-1.png

题解

首先考虑\(n\)个点的简单环怎么做。
根据\(Burnside\)引理,染色方案有\(\frac{1}{n}\sum\limits_{i=0}^{n-1}k^{(n,i)}\)种。 那么可以将图的所有点双联通分量单独处理。
\(BCC\)缩点后,边分为三种情况。1. 单边 2. 单环 3. 复合环。
单边就直接算就可以了。
单环之前考虑过了,其实复合环也很类似。
arc062f-2.png 可以发现复合环是可以不改变其他边换掉其中两条边的。
所以不同的染色方案不同颜色的数量是不同的。
若每种颜色有\(p\)\(,p-1+p-2+...+p-k=m\)的划分方法就是所有的染色方案。
用插板法就可解决,方案数是\(\binom{n+k-1}{k-1}\)
最后问题就解决了!时间复杂度是\(O(n+2* m)\)

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 105, MOD = 1e9 + 7;
int n, m, k, fac[N << 1], ifac[N << 1];

int fpow(int a, int m) {
int ans = 1;
for(; m; m >>= 1) {
if(m & 1) ans = ans * 1ll * a % MOD;
a = a * 1ll * a % MOD;
}
return ans;
}

struct Edge {
int nxt, v;
}e[N << 1];
int bccno[N], bcc-cnt,siz-e[N], siz-p[N], dfs-clock, low[N], pre[N], head[N], cnt, top;
pair<int, int> stk[N];

void add(int u, int v) {
e[++cnt] = (Edge){head[u], v}, head[u] = cnt;
e[++cnt] = (Edge){head[v], u}, head[v] = cnt;
}
void dfs(int u, int fa) {
low[u] = pre[u] = ++dfs-clock;
for(int i = head[u]; ~i; i = e[i].nxt) {
int v = e[i].v;
if(v == fa) continue;
if(!pre[v]) {
stk[++top] = make-pair(u, v);
dfs(v, u);
low[u] = min(low[u], low[v]);
if(low[v] >= pre[u]) {
bcc-cnt++;
while(523) {
int x = stk[top].first, y = stk[top].second;
top--;
siz-e[bcc-cnt]++;
if(bccno[x] != bcc-cnt) {bccno[x] = bcc-cnt; siz-p[bcc-cnt]++;}
if(bccno[y] != bcc-cnt) {bccno[y] = bcc-cnt; siz-p[bcc-cnt]++;}
if(x == u && y == v) break;
}
}
}
else if(pre[v] < pre[u]) {stk[++top] = make-pair(u, v); low[u] = min(low[u], pre[v]);}
}
}
int C(int n, int m) {
return fac[n] * 1ll * ifac[m] % MOD * ifac[n - m] % MOD;
}
int gcd(int a, int b) {
return b == 0 ? a : gcd(b, a % b);
}

int main() {
memset(head, -1, sizeof head);
scanf("%d %d %d", &n, &m, &k);
fac[0] = ifac[0] = ifac[1] = 1;
for (int i = 1; i <= m + k; ++i) fac[i] = fac[i - 1] * 1ll * i % MOD;
for (int i = 2; i <= m + k; ++i) ifac[i] = (MOD - MOD / i) * 1ll * ifac[MOD % i] % MOD;
for (int i = 2; i <= m + k; ++i) ifac[i] = ifac[i - 1] * 1ll * ifac[i] % MOD;
for(int i = 1; i <= m; i++) {
int u, v;
scanf("%d %d", &u, &v);
add(u, v);
}
for(int i = 1; i <= n; i++) {
if(!pre[i]) dfs(i, 0);
}
int ans1 = 1, ans2 = 1, ans3 = 1;
for(int i = 1; i <= bcc-cnt; i++) {
if(siz-e[i] == 1) ans1 = ans1 * 1ll * k % MOD;
else if(siz-e[i] == siz-p[i]) {
int sum = 0;
for(int j = 0; j < siz-e[i]; j++) {
sum += fpow(k, gcd(siz-p[i], j));
if(sum >= MOD) sum -= MOD;
}
ans2 = ans2 * 1ll * sum % MOD * fpow(siz-e[i], MOD - 2) % MOD;
} else ans3 = ans3 * 1ll * C(siz-e[i] + k - 1, k - 1) % MOD;
}
printf("%lld\n", ans1 * 1ll * ans2 % MOD * ans3 % MOD);
return 0;
}

Problem A. Alternating Paths

还没改啊,先鸽着吧...
# Problem B. Dispatch Money
假设区间\([l,r]\)的逆序对数是\(f_{l,r}\).
显然\(f_{l,r}=f_{l+1,r}+f_{l,r-1}-f_{l+1,r-1}+[P-l>P-r]\).
\(f_{l,r} + f_{l+1,r-1} \geq f_{l+1,r}+f_{l,r-1}\),所以决策具有单调性.
那么可以用想到CDQ分治来优化这个\(1D\)\(DP\)方程.
主要是快速算出区间的逆序对,感觉像莫队一样啊.
还是比较套路,不过这个\(\mathjaxcal{O}(nlog^3n)\)真的能过?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 300005;
int n, x, p[N], t[N];
vector<int> b; ll f[N];

void update(int x, int v) {for(; x <= n; x += x & -x) t[x] += v;}
int query(int x) {int res = 0; for(; x; x -= x & -x) res += t[x]; return res;}
int query(int l, int r) {return query(r) - query(l - 1);}

ll inverse(int L, int R) { //求区间逆序对,类似于莫队?
static int l = 1, r = 0;
//printf("%d %d\n", L, R);
static ll res = 0;
while(l > L) l--, res += query(1, p[l]), update(p[l], 1);
while(r < R) r++, res += query(p[r], n), update(p[r], 1);
while(l < L) update(p[l], -1), res -= query(1, p[l]), l++;
while(r > R) update(p[r], -1), res -= query(p[r], n), r--;
//cout << res << endl;
return res;
}

void CDQ(int tl, int tr, int l, int r) { // 从[tl,tr] 转移到 [l,r]
if(l > r) return;
int mid = (l + r) >> 1, pos = 0; ll res = 1e17;
for(int i = tl; i <= tr; i++) { //枚举转移点
ll v = inverse(i + 1, mid) + f[i] + x;
if(v < res) res = v, pos = i;
}
f[mid] = min(f[mid], res); CDQ(tl, pos, l, mid - 1), CDQ(pos, tr, mid + 1, r);
}

void CDQ(int l, int r) {
if(l >= r) return;
int mid = (l + r) >> 1;
CDQ(l, mid); CDQ(l, mid, mid + 1, r); CDQ(mid + 1, r);
}

int main() {
//freopen("text.in", "r", stdin);
memset(f, 0x3f, sizeof f);
scanf("%d %d", &n, &x);
for(int i = 1; i <= n; i++) scanf("%d", &p[i]), b.push-back(p[i]);
sort(b.begin(), b.end());
b.erase(unique(b.begin(), b.end()), b.end());
for(int i = 1; i <= n; i++) p[i] = lower-bound(b.begin(), b.end(), p[i]) - b.begin() + 1;
f[0] = 0; CDQ(0, n);printf("%lld\n", f[n]);
return 0;
}
```

# Problem C. Exerci
这个很容易想到$hash$来搞吧.
最开始想到可能会把式子变形什么的,发现好像不太可行?
然后又想到了两个乱搞的方法:
1. 根据末尾的数字来判断,不过好像不太可行.
2. 用扩展欧几里得来解出一组特解,然后通解是可知并且有限的.如果可能的通解太多,就可以扔到一个序列里面,最后$\mathjaxcal{O}(n^2)$暴力考虑这些序列的元素有没有可能有解.否则直接用$hash$表来判断每对解是否合法.

其实解法$2$确实是可行的,但感觉不太对一直没有写...
实际上解比较少,这种方法跑得还挺快?

话说直接$\mathjaxcal{O}(n^2)$,$6s$为什么可以跑$50000$的数据啊?
机房的同学还一直用魔法卡常,我直接写的$for$循环反而很快过了$subtask1$?
因为存$hash$表时要用点小技巧,还得开$int128$才行.

```cpp
#include<bits/stdc++.h>
using namespace std;

#define int __int128
const int N = 200005;
int n, k, x[N], y[N];
vector<int> v;

int read() {
int w = 0, f = 1; char ch = getchar();
while(ch > '9' || ch < '0') {
if(ch == '-') f = -1;
ch = getchar();
}
while(ch >= '0' && ch <= '9') {
w = w * 10 + ch - 48;
ch = getchar();
}
return w * f;
}
void print(int x) {
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9) print(x / 10);
putchar(x % 10 + 48);
}
void exgcd(int a, int b, int &d, int &x, int &y) {
if(b == 0) {x = 1, d = a, y = 0; return;}
exgcd(b, a % b, d, y, x); y -= x * (a / b);
}

namespace H {
const int M = 19260817;
int hd[M] = {}, tot = 0;
struct E{int nxt, a, b;}e[N];
int f(int a, int b) {return (a * 1000000000 % M + b) % M;}
void ins(int a, int b) {int t = f(a, b); e[++tot] = E{hd[t], a, b};hd[t] = tot;}
int qry(int a, int b) {for(int i = hd[f(a, b)]; i; i = e[i].nxt) if(e[i].a == a && e[i].b == b) return i;return 0;}
}

signed main() {
//freopen("text.in","r",stdin);
n = read(), k = read();
for(int i = 1; i <= n; i++) {
x[i] = read(), y[i] = read();
H::ins(x[i], y[i]);
}
for(int i = 1; i <= n; i++) {
int a, b, d;
exgcd(x[i], y[i], d, a, b);
if(k % d != 0) continue;
a *= k / d, b *= k / d;
int xx = x[i] / d, yy = y[i] / d;
if(a < 0) {int kk = (-a) / yy + 1; a += yy * kk; b -= xx * kk;}
int kk = a / yy; a -= kk * yy; b += xx * kk;
int root = b / xx;
if(root > 92) {v.push-back(i); continue;}
for(root += 1; root >= 0; root--) {
//print(root);puts("");
if(a < 0 || b < 0) continue;
//print(a), putchar(' '),print(b);puts("");
int p = H::qry(a, b);
if(p != 0) {(print(i), putchar(' '), print(p));return 0;}
a += yy, b -= xx;
}
}
for(auto i : v)
for(auto j : v) {
if(x[i] * x[j] + y[i] * y[j] == k) {print(i),putchar(' '),print(j);return 0;}
}
puts("114514");
return 0;
}

Problem A. Good Subsegments

\[2^{a-l}+2^{a_{l+1}}+...+2^{a-r}=2^x\] 可以发现,\(x\leq max(a-l,a_{l+1}...a-r)+log-2^{r-l+1}\).
所以固定一个端点时,枚举\(x\)的值去验证有没有等于\(2^x\)的区间是可行的.
但是\(2^x\)可能很大,高精度很麻烦也会\(TLE\),那怎么办呢?
可以选一个大质数,然后把\(2^{a-l}+2^{a_{l+1}}+...+2^{a-r}=2^x\)分别模上它也是相等的. 可以分治来做比较简单,每次找到当前区间的最大的\(a-i\),然后枚举\(i\)左边的区间或者右边的区间(取决于区间长度).
端点固定后,就枚举\(x\),然后去哈希表验证一下有没有这样的另一个端点,如果存在这样的区间,那么就是合法的.
注意一下细节: 1. 在哈希表中要插入\((key=0,val=0)\),因为区间\([1,r]\)可能是合法的,而对应区间的值是\(sum_r-sum_0\).
2. 要选一个尽量大的质数,不然出错的概率很大.哈希表也要选择一个尽量大的,比如\(114514\).
3. 另外要用快速乘,因为模数是大于\(2^{31}-1\)的.龟速乘可能会超时吧.
时间复杂度\(\mathjaxcal{O}(nlog^2n)\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include<bits/stdc++.h>
using namespace std;

typedef long long ll;
#define PI pair<ll, int>
const int N = 200005;
const ll P = 4398042316712111199;
int n, a[N], lg[N];
ll sum[N], h[N], ans;
PI mx[N][20];

namespace H {
const int M = 19260817;
int hd[M] = {}, tot = 0;
struct E{int nxt, o; ll v;}e[N];
void insert(ll h, int o) {int t = ((h % M + M) % M + M) % M, mx = max(t, mx); e[++tot] = E{hd[t], o, h}; hd[t] = tot;}
int query(ll h) {for(int i = hd[((h % M + M) % M + M) % M]; i; i = e[i].nxt) if(e[i].v == h) return e[i].o; return -1;}
}

ll mul(ll a, ll b) {
ll c = a * b - (ll)((long double)a * b / P + 0.5) * P;
return c < 0 ? c + P : c;
}
ll fpow(ll a, ll b) {
ll ans = 1;
for(; b; b >>= 1, a = mul(a, a)) if(b & 1) ans = mul(ans, a);
return ans;
}
void solve(int l, int r) {
if(l == r) {
ans += 1;
} if(l >= r) return;
int len = lg[r - l + 1], m = max(mx[l][len], mx[r - (1 << len) + 1][len]).second;
if(r + l >= m + m) {
for(int i = l; i <= m; i++) {
ll t = h[m];
for(int j = 1; j <= 20; j++) {
ll q = t + sum[i - 1]; if(q >= P) q -= P;
int p = H::query(q);
if(p >= m && p <= r) ans++; t *= 2; if(t >= P) t -= P;
}
}
} else {
for(int i = m; i <= r; i++) {
ll t = h[m];
for(int j = 1; j <= 20; j++) {
ll q = sum[i] - t; if(q < 0) q += P;
int p = H::query(q) + 1;
if(p <= m && p >= l) ans++; t *= 2; if(t >= P) t -= P;
}
}
}
solve(l, m - 1); solve(m + 1, r);
}

int main() {
scanf("%d", &n);H::insert(0, 0);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
for(int i = 2; i <= n; i++) lg[i] = lg[i >> 1] + 1;
for(int i = 1; i <= n; i++) {
h[i] = fpow(2, a[i]);
sum[i] = (sum[i - 1] + h[i]) % P;
H::insert(sum[i], i);
mx[i][0] = make-pair(a[i], i);
}
for(int j = 1; j <= 19; j++)
for(int i = 1; i + (1 << j) - 1 <= n; i++) mx[i][j] = max(mx[i][j - 1], mx[i + (1 << j - 1)][j - 1]);
solve(1, n);
printf("%lld\n", ans);
return 0;
}

Problem B. Easy Sum

还没改这道题啊,暂时鸽了.QωQ.

Problem C. Funny Cost

据说是最简单的一道...
题目不太好懂啊,翻译一下就是,给你\(N\)个元素的序列. Funny Cost
这个序列每个排列的代价是\(n+1\)顶点的完美匹配边权和(顶点\(u,v\)之间的边权是\(max_{i=u}^{v-1}a-i\)),问你最大代价.

可以暴搜发现这样匹配是最优的.
匹配方式 怎么计算每个元素的贡献是多少呢?
\(n\)是顶点数,考虑每个长度为\(\frac{n}{2}\)的区间.
假设现在选出了最大值是第\(i\)个,那么另外\(\frac{n}{2}-1\)个顶点要选比它小的值,方案有\(\tbinom{i-1}{\frac{n}{2}-1}\)种.
而这个顶点在数组中有\(\frac{n}{2}\)种放法,区间里的顶点可以任意排列,答案乘上\(\frac{n}{2}!\).
而区间外的顶点也是,有\((\frac{n}{2}-1)!\)种排列方法.
最后再乘上权值.
答案是\(\sum\limits_{i=\frac{n}{2}}^{n}a-i\times \tbinom{i-1}{\frac{n}{2}-1}\times \frac{n}{2}\times \frac{n}{2}! \times (\frac{n}{2}-1)!\)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include<bits/stdc++.h>
using namespace std;

const int N = 100005, P = 998244353;
int n, a[N], fac[N], ifac[N];

int C(int n, int m) {
return fac[n] * 1ll * ifac[m] % P * ifac[n - m] % P;
}

int main() {
ios::sync-with-stdio(false);
cin >> n;
for(int i = 1; i <= n; i++) cin >> a[i];
fac[0] = ifac[0] = ifac[1] = 1;
for (int i = 1; i <= n; i++) fac[i] = fac[i - 1] * 1ll * i % P;
for (int i = 2; i <= n; i++) ifac[i] = (P - P / i) * 1ll * ifac[P % i] % P;
for (int i = 2; i <= n; i++) ifac[i] = ifac[i - 1] * 1ll * ifac[i] % P;
sort(a + 1, a + n + 1);
int m = n / 2 + 1, ans = 0;
for(int i = m; i <= n; i++) {
ans = (ans + 1ll * a[i] * C(i - 1, m - 1) % P * m % P * fac[m] % P * fac[m - 1]) % P;
}
cout << ans << endl;
return 0;
}

写完一次就 AC 了是我没想到的...
cjcjcj.jpg

可以假设增加了\(c\)亮度.
即求\(\sum\limits_{i=1}^{n}(x-i-y-i+c)^2\)的最小值.
把式子展开, \(\sum\limits_{i=1}^{n}x-i^2+\sum\limits_{i=1}^{n}{y-i}^2+nc^2+2c( \sum\limits_{i=1}^{n}(x-i-y-i))-2 \sum\limits_{i=1}^{n}x-iy-i\)
可以发现前面两项是固定的.
因为c比较小, \(|c|\leq m\), 中间两项可以直接枚举\(c\).
所以只需要求出\(\sum\limits_{i=1}^{n}x-iy-i\)的最大值.
因为可以旋转, 等价于\(\sum\limits_{i=1}^{n}x_{n-i+1}y-i\).
可以看出这就是卷积的形式.
断环成链, 即求\(\sum\limits_{i=1}^{n}x_{n-i+1+k}y-i\), \(0 \leq k\leq n-1\).
所以把\(x\)翻转后,复制成两份做 fft.
\(n+1\)\(2n\)的系数就对应了旋转后的\(\sum\limits_{i=1}^{n}x-iy-i\).
取一个最大值就做完了.
注意数组要开大一些, 因为还要把\(x\)复制一份.
\(c\)要在\(-m\), \(m\)之间枚举, 因为展开后的式子可能是\(x-i-y-i\), 也可能是\(y-i-x-i\).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
typedef complex<double> cp;
const int N = 6e5 + 5;
const double pi = acos(-1.0);
int n, m, len = 1, l, rev[N], x[N], y[N];
cp a[N * 2], b[N];

void fft(cp *a, int n, int inv) {
for (int i = 0; i < n; i++) if (rev[i] < i) swap(a[i], a[rev[i]]);
for (int k = 1; k < n; k <<= 1) {
cp wn(cos(pi / k), inv * sin(pi / k));
for (int i = 0; i < n; i += k * 2) {
cp w(1, 0);
for (int j = 0; j < k; j++, w *= wn) {
cp x = a[i + j], y = a[i + j + k] * w;
a[i + j] = x + y, a[i + j + k] = x - y;
}
}
}
if (inv < 0) for (int i = 0; i < len; i++) a[i] /= n;
}

int main() {
ll res = 0, dis = 0, mx = -998244353, ans = 998244353;
scanf("%d %d", &n, &m);
for (int i = 1; i <= n; i++) scanf("%d", &x[i]), res = res + x[i] * 1ll * x[i];
for (int i = 1; i <= n; i++) scanf("%d", &y[i]), res = res + y[i] * 1ll * y[i], dis += x[i] - y[i];
while (len <= n * 2) len <<= 1, l++;
for (int i = 0; i < len; i++) rev[i] = (rev[i >> 1] >> 1) | ((1 & i) << (l - 1));
for (int i = 1; i <= n; i++) a[i].real((double)(x[n - i + 1])), b[i].real((double)(y[i]));
for (int i = 1; i <= n; i++) a[i + n].real(a[i].real());
fft(a, len, 1), fft(b, len, 1);
for (int i = 0; i < len; i++) a[i] *= b[i];
fft(a, len, -1);
for (int i = n + 1; i <= 2 * n; i++) mx = max(mx, (ll)(a[i].real() + 0.5));
mx *= -2;
for (int c = -m; c <= m; c++) ans = min(ans, n * 1ll * c * c + 2 * c * 1ll * dis);
printf("%lld\n", res + mx + ans);
return 0;
}
0%