石黑一雄虽从小在英国长大,但这篇作品,尤其后三章真相的揭露,字里行间流露着日本的“物哀”美学,一种无法言说的悲伤,无可慰藉的苦痛。小说中的人物只能在既定框架下面对早已注定的命运,没有反抗,甚至连逃避都没有,不免让人“恼火”。可事实上现实不也如此,在社会这个大浪潮下,你又能改变些什么呢?有时我在想,上帝宏观不掷骰子,微观掷骰子。你的努力只是微观层面上的扰动,改变不了整体宏观层面上早已注定的命运,甚至连你的努力都是确定性的。这么一想不免悲从中来,但正因如此,我拼了命也要改变些什么,留下些什么……

CF103D Time to Raid Cowavans

题意

给一个序列 \(a\)\(m\) 次询问,每次询问给出 \(t,k\)。求 \(a_t+a_{t+k}+a_{t+2k}+\dots+a_{t+pk}\) 的值,其中 \(t+pk<=n\)\(t+(p+1)k> n\)

\(n,m\le 300000,a_i\le 10^9\)

思路

如果对每个间隔 \(k\) 预处理前缀和的话,因为 \(1\le k\le n\),所以时间复杂度为 \(O(n^2)\)。但进一步想,对于比较大的间隔,暴力其实已经很快了。每次暴力扫描所有位置的时间复杂度是 \(O(\frac{n}{k})\)

\(k\ge\sqrt{n}\) 时,\(\frac{n}{k}\le\sqrt{n}\),每次询问时间复杂度为 \(O(\sqrt{n})\) 所以总的时间复杂度为 \(n\sqrt{n}\)

\(k<\sqrt{n}\) 时,\(O(n)\) 预处理前缀和,每次 \(O(1)\) 查询,所以总的时间复杂度为 \(O(n\sqrt{n})\)​。

另外这个题空间给的比较少,需要离线处理。

代码

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
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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 3e5 + 10;
const int S = 547;

void solve() {
int n;
cin >> n;
vector<int> a(n + 1);
vector<LL> pre(n + 1);
for(int i = 1; i <= n; i++) {
cin >> a[i];
}

int q;
cin >> q;
vector<array<int, 3> > ques(q);
for(int i = 0; i < q; i++) {
auto &[l, b, id] = ques[i];
cin >> l >> b;
id = i + 1;
}
sort(ques.begin(), ques.end(), [&](array<int, 3> x, array<int, 3> y) {
return x[1] < y[1];
});
vector<LL> ans(q + 1);
for(int i = 0; i < q; i++) {
auto [l, b, id] = ques[i];
if(b > S) {
for(int j = l; j <= n; j += b) {
ans[id] += a[j];
}
continue;
}
if(!i || b != ques[i - 1][1]) {
for(int j = 1; j <= n; j++) {
pre[j] = pre[max(0, j - b)] + a[j];
}
}
ans[id] = pre[(n - l) / b * b + l] - pre[max(0, l - b)];
}
for(int i = 1; i <= q; i++) {
cout << ans[i] << endl;
}
}

// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif

int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

FlippingBitsDiv1 - TopCoder 12728

题意

给一个长度为 \(N(N<300)\)\(01\) 字符串,再给一个正整数 \(M\)。每次操作,你可以将一个位置取反,或者将一个长度为 \(M\) 的倍数的前缀取反。问最少需要多少次操作,才能使字符串成为一个循环节为 \(M\) 的循环串。

循环节长度为 \(M\) 定义为,对于任意 \(i\),如果 \(i+M\) 这个位置存在,那么 \(i\)\(i+M\)​​ 上的字符应该相等。

思路

循环节的长度和循环节的个数相互制约,总有一个不超过 \(\sqrt{n}\)

对于 \(m\le\sqrt{n}\),循环节的长度不超过 \(\sqrt{300}\approx 17\),并不长。我们直接枚举最后的循环节,时间复杂度为 \(2^{17}\)。然后每一段必须和答案相等或者相反即可,这里可以通过动态规划。设 \(dp[i][0/1]\),第一维为这是第 \(i\) 段,第二位 \(0/1\) 为这一段是否反转,那么可以得到如下转移方程。 \[ \begin{align} dp[i][0] &= min(dp[i - 1][0], dp[i - 1][1]) + notEqu \\ dp[i][1] &= min(dp[i - 1][0] + equ + 2, dp[i - 1][1] + equ) \end{align} \] 其中 \(equ\) 为这一段与循环串相同的个数,\(notEqu\) 即为不同的个数。

对于 \(m>\sqrt{n}\),循环节的个数不是很多,我们枚举每一段是否反转,对于答案串的每一位,如果所有循环节在这一位的 \(0\) 比较多,就是 \(0\)。否则就是 \(1\)

所以总的时间复杂度为 \(O(n\times 2^{\sqrt{n}})\)

代码

TopCoder 交不明白,此代码没有经过测试。

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
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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 2e5 + 10;
const int pivot = 17;

struct FlippingBitsDiv1 {
int getmin(vector<string> S, int m) {
string s{};
for(auto &i : S) {
s += i;
}
// debug(s);
int n = s.length(), cnt = (n - 1) / m + 1;
vector<string> ss(cnt);
// debug(cnt);
for(int i = 0; i < cnt; i++) {
for(int j = 0; i * m + j < n && j < m; j++) {
ss[i] += s[i * m + j];
}
}

int ans = s.length();
if(m <= pivot) {
for(int i = 0; i < (1 << m); i++) {
string t{};
for(int j = 0; j < m; j++) {
t += char(((i >> j) & 1) + '0');
}
// debug(t);
vector<array<int, 2> > dp(cnt);
for(int i = 0; i < cnt; i++) {
int equ = 0, not_equ = 0;
for(int j = 0; j < ss[i].length(); j++) {
if(ss[i][j] == t[j]) equ++;
else not_equ++;
}
// debug2(i, ss[i]);
// debug2(equ, not_equ);
if(!i) {
dp[i][0] = not_equ;
dp[i][1] = equ + 1;
}
else {
dp[i][0] = min(dp[i - 1][0], dp[i - 1][1]) + not_equ;
dp[i][1] = min(dp[i - 1][0] + equ + 2, dp[i - 1][1] + equ);
}
}
ans = min({ans, dp[cnt - 1][0], dp[cnt - 1][1]});
}
}
else {
for(int i = 0; i < (1 << cnt); i++) {
vector<bool> re(cnt);
for(int j = cnt - 1; ~j; j--) {
if(j == cnt - 1) re[j] = (i >> j) & 1;
else re[j] = re[j + 1] ^ ((i >> j) & 1);
}
int tot = 0;
vector<int> sum[2];
sum[0].resize(m);
sum[1].resize(m);
for(int i = 0; i < cnt; i++) {
for(int j = 0; j < ss[i].length(); j++) {
sum[(ss[i][j] - '0') ^ re[i]][j]++;
}
}
for(int i = 0; i < m; i++) {
tot += min(sum[0][i], sum[1][i]);
}
ans = min(ans, tot);
}
}
return ans;
}
} FBD1;

IOI2009 Regions

题意

\(N\) 个节点的树,\(R\) 种属性。每个点属于一种属性。有 \(Q\) 次询问,每次询问 \((r_1,r_2)\),问有多少对 \((e_1,e_2)\) 满足 \(e_1\) 的属性是 \(r_1\), \(e_2\) 属性是 \(r_2\), \(e_1\)\(e_2\) 的祖先。

\(1\le N,Q\le 2\times 10^5,\quad 1\le R\le 2.5\times 10^4\)

思路

不妨设询问中 \(r_1\) 属性的点的个数为 \(A,\ r_2\) 属性的点有 \(B\) 个。

属性的点的个数超过 \(\sqrt{n}\) 的属性不超过 \(\sqrt{n}\) 个,我们先求出这棵树的 dfs 序。

对于 \(A\ge\sqrt{n}\) 的情况,我们可以以 \(O(n)\) 的时间复杂度预处理 \(r_1\) 对所有 \(r2\) 的答案。所以总的时间复杂度为 \(O(n\sqrt{n})\)。 维护一个差分数组 \(d\),对于所有属性为 \(r_1\) 的点,设其入栈时间为 \(x\),最后一个孩子入栈时间为 \(y\)。让 d[x]++, d[y + 1]--,然后求 \(d\) 的前缀和。那么对于所有其它属性,其祖先为 \(r_1\) 的对数为 d[x]

对于 \(B\ge\sqrt{n}\),同理我们让 d[x]++,求 \(d\) 的前缀和,那么对于所有其它属性,其孩子为 \(r_2\) 的对数为 d[y] - d[x - 1]。时间复杂度也是 \(O(n\sqrt{n})\)

对于 \(A,B\le\sqrt{n}\) 的情况,我们采用扫描线的方法,相当于求多少线段过这个点。一次询问为 \(O(n)\),有 \(Q\) 组询问,时间复杂度为 \(O(Q\sqrt{n})\)\(Q\)\(n\) 同一个量级。

所以总的时间复杂度为 \(O(n\sqrt{n})\)

代码

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
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
#include<bits/stdc++.h>
// #define int long long
// #define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 2e5 + 10;
const int maxr = 25e3 + 10;
const int pivot = 447;

vector<int> e[maxn], hid[maxr];
vector<pii> seg[maxr];
int h[maxn], dfn, ans1[450][maxr], ans2[450][maxr], d[maxn], num[maxn];
pii lr[maxn];

void dfs(int u) {
lr[u].first = ++dfn;
for(auto v : e[u]) {
dfs(v);
}
lr[u].second = dfn;
}

void solve() {
int n, r, q;
cin >> n >> r >> q >> h[1];
hid[h[1]].push_back(1);
for(int i = 2; i <= n; i++) {
int fa;
cin >> fa >> h[i];
e[fa].push_back(i);
hid[h[i]].push_back(i);
}
dfs(1);
int cnt = 0;
for(int i = 1; i <= r; i++) {
if(hid[i].size() >= pivot) {
num[i] = ++cnt;
memset(d, 0, sizeof(d));
for(auto id : hid[i]) {
d[lr[id].first]++;
d[lr[id].second + 1]--;
}
for(int j = 2; j <= n; j++)
d[j] += d[j - 1];
for(int j = 1; j <= n; j++)
if(h[j] != i) {
ans1[cnt][h[j]] += d[lr[j].first];
}

memset(d, 0, sizeof(d));
for(auto id : hid[i]) {
d[lr[id].first]++;
}
for(int j = 2; j <= n; j++)
d[j] += d[j - 1];
for(int j = 1; j <= n; j++)
if(h[j] != i) {
ans2[cnt][h[j]] += d[lr[j].second] - d[lr[j].first - 1];
}
}
else {
sort(hid[i].begin(), hid[i].end(), [&](int a, int b) {
return lr[a].first < lr[b].first;
}); //
for(auto id : hid[i]) {
seg[i].push_back({lr[id].first, 1});
seg[i].push_back({lr[id].second + 1, -1});
}
sort(seg[i].begin(), seg[i].end());
}
}
for(int i = 1; i <= q; i++) {
int r1, r2;
cin >> r1 >> r2;
if(hid[r1].size() >= pivot) {
cout << ans1[num[r1]][r2] << endl;
}
else if(hid[r2].size() >= pivot) {
cout << ans2[num[r2]][r1] << endl;
}
else {
int tot = 0, ans = 0, now = 0;
for(auto id : hid[r2]) {
while(now < seg[r1].size() && seg[r1][now].first <= lr[id].first) {
tot += seg[r1][now].second;
now++;
}
ans += tot;
}
cout << ans << endl;
}
}
}

// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif

int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

CF101E Candies and Stones

思路

递推式很明显: \[ dp[i][j] = max\{dp[i-1][j],dp[i][j-1]\}+(x[i]+y[i])\bmod n \] \(n\)\(20000\)\(O(n^2)\) 显然可行,但注意到空间只有 \(45MB\),而用 \(bitset\) 记录转移信息需要 \(20000*20000/8/1024/1024=47.68MB\)

我们可以对 \(dp[i][j]\) 的第一维进行分块,块长为 \(B\)。先跑一遍的 \(dp\),处理出每个块首的 \(dp\) 信息和最后的结果。然后倒序处理每个块。对每个块正着跑一遍记录转移信息,倒着跑一遍输出具体方案。

空间上,开大小为 \(\frac{n}{B}\times B\) 的数组记录每个块块首的状态,开 \(\frac{Bn}{w}\) 的数组记录每个块内的转移信息。平衡一下,解的 \(B=\sqrt{nw}\)​。

\(\text{P.S.}\) 其实 \(47.68\)\(45\) 比差距不大,只分成两块就可以通过

代码

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
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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 2e4 + 10;
const int pivot = 800;

int x[maxn], y[maxn], dp_block[26][maxn], dp[maxn];
bitset<maxn> f[801], b[26];


// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif

int n, m, p;
cin >> n >> m >> p;
for(int i = 1; i <= n; i++) {
cin >> x[i];
}
for(int i = 1; i <= m; i++) {
cin >> y[i];
}
for(int i = 1; i <= n; i++) {
for(int j = 1; j <= m; j++) {
bool choose_x = 0;
if(dp[j] >= dp[j - 1]) {
choose_x = 1;
}
else {
dp[j] = dp[j - 1];
}
dp[j] += (x[i] + y[j]) % p;
if(i % pivot == 1) {
int id = (i - 1) / pivot + 1;
dp_block[id][j] = dp[j];
b[id][j] = choose_x;
}
}
}
cout << dp[m] << endl;
stack<bool, vector<bool> > s;
int nowx = n, nowy = m;
for(int i = (n - 1) / pivot + 1; i; i--) {
memcpy(dp, dp_block[i], sizeof(dp));
f[1] = b[i];
int st = (i - 1) * pivot;
for(int j = 2; j <= pivot && st + j <= n; j++) {
for(int k = 1; k <= m; k++) {
if(dp[k] >= dp[k - 1]) {
f[j][k] = 1;
}
else {
f[j][k] = 0;
dp[k] = dp[k - 1];
}
dp[k] += (x[st + j] + y[k]) % p;
}
}
while(nowx > st && nowx > 1) {
if(f[nowx - st][nowy]) {
s.push(1);
nowx--;
}
else {
s.push(0);
nowy--;
}
}
}
while(nowy > 1) {
cout << 'S';
nowy--;
}
while(!s.empty()) {
cout << ((s.top() == 1) ? 'C' : 'S');
s.pop();
}
return 0;
}

题目链接

L3-032 关于深度优先搜索和逆序对的题应该不会很难吧这件事 - 团体程序设计天梯赛

题意

给定一棵 \(n\) 个节点的树,其中节点 \(r\) 为根。求该树所有可能的 DFS 序中逆序对数量之和。

思路

一开始分了两类,第一类为它的所有孩子,第二类为它父亲的除了它及它的孩子的所有孩子。所以少考虑了一些情况,写到最后样例没过才发现问题。第二类应为它的祖先的……

重新进行分类,不妨设它为 \(u\),第一类为 \(u\) 所有祖先,这一类贡献不会因为 DFS 改变而变。第二类为除了其祖先,子孙和 \(u\) 的所有结点,这一类会改变。

对于第二类结点,不妨设其中一个为 \(v\),对所有可能的 DFS序,有 \(\frac{1}{2}\) 的概率 \(u\)\(v\) 前面,\(\frac{1}{2}\) 的概率 \(u\)\(v\) 后面。\(u\) 与它的所有第二类结点都可能产生贡献,共计为 \[ \text{第二类结点数}\times\text{DFS 数量}\times\frac{1}{2}\times\frac{1}{2} \] 一个 \(\frac{1}{2}\) 为位置概率,另一个为一对 \((u,v)\) 其贡献会计算两次。

代码

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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
#define lowbit(i) ((i) & (-i))
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 1e9 + 7;
const int maxn = 3e5 + 10;

vector<int> e[maxn];
int c[maxn], n, dep[maxn], son[maxn], fac[maxn];
LL base = 1, ans = 0;

LL qpow(LL b, LL p, int k) {
b %= k;
LL ans = 1;
for(; p; p >>= 1, b = b * b % k)
if(p & 1) ans = ans * b % k;
return ans;
}

void add(int x, int val) {
for(; x <= n; x += lowbit(x)) c[x] += val;
}

int sum(int x) {
int res = 0;
for(; x; x -= lowbit(x)) res += c[x];
return res;
}

void dfs(int u, int fa) {
dep[u] = dep[fa] + 1;
ans = (ans + dep[u] - 1 - sum(u)) % mod;
add(u, 1);
int cnt = 0;
for(auto v : e[u])
if(v != fa) {
cnt++;
dfs(v, u);
son[u] += son[v] + 1;
}
if(cnt) base = base * fac[cnt] % mod;
add(u, -1);
}

void solve() {
int r;
cin >> n >> r;
fac[0] = 1;
for(int i = 1; i <= n; i++)
fac[i] = (LL)fac[i - 1] * i % mod;
for(int i = 1; i < n; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
e[v].push_back(u);
}
dfs(r, 0);
ans = ans * base % mod;
int inv2 = qpow(2, mod - 2, mod);
for(int i = 1; i <= n; i++) {
ans = (ans + (n - dep[i] - son[i]) * base % mod * inv2 % mod * inv2 % mod) % mod;
}
cout << ans << endl;
}

// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif

int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

题目链接

L2-047 锦标赛 - 团体程序设计天梯赛-练习集 (pintia.cn)

思路

​ 一开始一直想着从最后一轮开始倒序处理,但怎么都无法保证分配的合理性。遍历到前面的轮次可能会不满足。

​ 正确思路如下,先根据第一轮败者完善答案的一半。下标从 \(0\) 开始的话第 \(1\) 轮比赛的第 \(i\) 场败者,可以对应第 \(i\times 2\) 名选手。

​ 第 \(i\) 轮比赛的第 \(j\) 场的败者其可能与第 \(i -1\) 轮比赛的第 \(j \times 2\)\(j\times 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
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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 131080;

void solve() {
int k;
cin >> k;
vector<vector<int> > l(k), nxt(k);
for(int i = 0; i < k; i++) {
int n = (1 << (k - i - 1));
l[i].resize(n);
nxt[i].resize(n);
for(int j = 0; j < n; j++) {
cin >> l[i][j];
}
}
int w;
cin >> w;
if(l[k - 1][0] > w) {
cout << "No Solution\n";
return;
}
vector<int> ans(1 << k);
for(int i = 0; i < (1 << (k - 1)); i++) {
ans[i << 1] = l[0][i];
nxt[0][i] = (i << 1) + 1;
}
for(int i = 1; i < k; i++) {
for(int j = 0; j < (1 << (k - i - 1)); j++) {
if(l[i][j] < l[i - 1][j * 2] && l[i][j] < l[i - 1][j * 2 + 1]) {
cout << "No Solution\n";
return;
}
int maxl = max({l[i][j], l[i - 1][j * 2], l[i - 1][j * 2 + 1]});
if(l[i][j] >= l[i - 1][j * 2]) {
ans[nxt[i - 1][j * 2]] = l[i][j];
nxt[i][j] = nxt[i - 1][j * 2 + 1];
}
else {
ans[nxt[i - 1][j * 2 + 1]] = l[i][j];
nxt[i][j] = nxt[i - 1][j * 2];
}
l[i][j] = maxl;
}
}
// debug(nxt[k - 1][0]);
ans[nxt[k - 1][0]] = w;
for(int i = 0; i < (1 << k); i++)
cout << ans[i] << " \n"[i == (1 << k) - 1];
}

// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif

int T = 1;
// cin >> T;
while(T--) {
solve();
}
return 0;
}

题目链接

F-命运的抉择_2024牛客寒假算法基础集训营6 (nowcoder.com)

题意

给一个长度为 \(n(2\le n\le 10^5)\) 的数组 \(a(1\le a_i\le 10^6)\),对每个 \(a_i\) 将其添加进数组 \(b\) 或数组 \(c\)​。要求 \(b\)\(c\) 均非空,两个数组中各任取一个元素 \(b_i,c_j\),均满足 \(\gcd(b_i,c_j)=1\)

思路

先用筛法预处理出 \(10^6\) 范围内每个数的素因子。

然后采用并查集,如果两个数包含同一个素因子,那么它们就必须在同一个集合内。最终如果集合数小于 \(2\),则无解。对于可行解,可以将一个集合放入数组 \(b\),其余集合放入数组 \(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
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
#include<bits/stdc++.h>
// #define int long long
#define endl '\n'
#define debug(x) cout << #x << " = " << x << endl
#define debug2(x,y) cout << #x << " = " << x << " " << #y << " = "<< y << endl
using namespace std;

typedef long long LL;
typedef unsigned long long uLL;
typedef pair<int, int> pii;
const double eps = 1e-9;
const int mod = 998244353;
const int maxn = 1e5 + 10;
const int maxa = 1e6;

int a[maxn], f[maxn];
bool vis[maxn];
vector<int> fac[1'000'001];

int find(int x) {
return f[x] == x ? x : f[x] = find(f[x]);
}

void solve() {
int n;
cin >> n;
for(int i = 1; i <= n; i++) {
cin >> a[i];
f[i] = i;
vis[i] = false;
}
map<int, int> mp;
for(int i = 1; i <= n; i++) {
for(auto x : fac[a[i]]) {
if(mp.find(x) != mp.end()) {
f[find(i)] = find(mp[x]);
}
else mp[x] = i;
}
}

int fir = 0, sec = 0;
for(int i = 1; i <= n; i++) {
int f = find(i);
if(!vis[f]) {
vis[f] = true;
if(fir == 0) fir = f;
else sec = f;
}
}
if(!sec) cout << "-1 -1\n";
else {
int lb = 0, lc = 0;
for(int i = 1; i <= n; i++) {
if(find(i) == fir) lb++;
else lc++;
}
cout << lb << " " << lc << endl;
for(int i = 1; i <= n; i++) {
if(find(i) == fir) cout << a[i] << " ";
}
cout << endl;
for(int i = 1; i <= n; i++) {
if(find(i) != fir) cout << a[i] << " ";
}
cout << endl;
}
}

// #define sunset
signed main() {
cin.tie(nullptr)->sync_with_stdio(false);

#ifdef sunset
freopen("in.txt", "r", stdin);
freopen("out.txt", "w", stdout);
#endif


for(int i = 2; i <= maxa; i++) if(fac[i].empty())
for(int j = i; j <= maxa; j += i) fac[j].push_back(i);

int T = 1;
cin >> T;
while(T--) {
solve();
}
return 0;
}

辗转相除法

等式 1 如果 \(a\mid b\)\(b\mid a\),那么 \(a=b\)

等式 2 如果 \(d\mid a\)\(d\mid b\) 那么 \(d\mid(ax + by);x,y \in Z\)

等式 3 \(a\bmod\ n = a - n\left \lfloor\frac{a}{n}\right \rfloor;a\in Z,n\in N^{*}\)

推论 1 对任意整数 \(a\), \(b\),如果 \(d\mid a\)\(d\mid b\)\(d\mid\gcd(a, b)\)

如果我们想要获得结论 \(\gcd(a,b) = \gcd(b,a\bmod\ b)\)

那么我们只需要证明 \(\gcd(a,b)\mid\gcd(b,a\bmod b)\)\(\gcd(b,a\bmod b)\mid\gcd(a,b)\) 就可以来证明它俩相等了。

证明 \(\gcd(a,b)\mid\gcd(b,a\bmod b)\)

\(d = \gcd(a, b)\) \(\therefore d\mid a\)\(d\mid b\)
等式 3 可知:\(a\pmod b = a - qb,q = \left \lfloor\frac{a}{b}\right \rfloor\)
\(\therefore a\bmod\ b\) 是 a 与 b 的线性组合
等式 2 可知:\(d\mid a\pmod b\)
\(\because d\mid b\)\(d\mid a\pmod b\)
\(\therefore\)推论 1 可知 \(d\mid\gcd(b, a\bmod\ b)\)
等价结论: \(\gcd(a, b)\mid\gcd(b, a\bmod\ b)\)

证明 \(\gcd(b,a\bmod\ b)\mid\gcd(a,b)\)

\(c = \gcd(b, a\bmod\ b)\)
\(\therefore c\mid b\)\(c\mid a\pmod b\)
\(\because a = qb + r,r = a\bmod\ b,q = \left \lfloor\frac{a}{b}\right \rfloor\)
\(\therefore a\)\(b\)\(a\bmod\ b\)线性组合
等式 2 可知:\(c\mid a\)
\(\because c\mid a\)\(c\mid b\)
推论 1 可知:\(c\mid\gcd(a, b)\)
等价结论: \(\gcd(b, a\bmod\ b)\mid\gcd(a, b)\)

证明 \(\gcd(a,b) = \gcd(b, a\bmod b)\)

由 上述两个结论 可知: \[ \begin{aligned} \gcd(a, b)\mid\gcd(b, a\bmod\ b)\\ \gcd(b, a\bmod\ b)\mid\gcd(a, b) \end{aligned} \]等式 1 可知:\(\gcd(a, b) = \gcd(b, a\bmod\ b)\)

扩展欧几里得算法

\(ax+by=gcd(a,b)\)

\[ \begin{aligned} ax_1+by_1&=\gcd(a,b) \\ bx_2+(a\bmod b)y_2&=\gcd(b,a\bmod b) \end{aligned} \]

由欧几里得定理可知:\(\gcd(a,b)=\gcd(b,a\bmod b)\)

所以 \(ax_1+by_1=bx_2+(a\bmod b)y_2\)

又因为 \(a\bmod b=a-\lfloor\frac{a}{b}\rfloor\times b\)

所以 \[ ax_1+by_1=bx_2+(a-\lfloor\frac{a}{b}\rfloor\times b)y_2 \] \(ax_1+by_1=ay_2+bx_2-\lfloor\frac{a}{b}\rfloor\times by_2=ay_2+b(x_2-\lfloor\frac{a}{b}\rfloor y_2)\)

\(a,b\) 相同,所以 \(x_1=y_2,y_1=x_2-\lfloor\frac{a}{b}\rfloor y_2\)

\(x_2,y_2\) 不断代入递归求解直至 \(\gcd\)(最大公约数,下同)为 0 递归 x=1,y=0 回去求解。

code

1
2
3
4
5
6
7
8
9
10
11
12
int exgcd(int a, int b, int &x, int &y) {
if (!b) {
x = 1;
y = 0;
return a;
}
int d = exgcd(b, a % b, x, y);
int t = x;
x = y;
y = t - (a / b) * y;
return d;
}

同余方程

求关于 $ x$ 的同余方程 $ a x b$ 的最小正整数解。

对于 \(100\%\) 的数据,\(2\le a, b\le 2,000,000,000\)

solution

\(ax\bmod\ b=1\) 实质是 \(ax+by=1\)\(y\) 是我们引入的某个整数,这里似乎是个负数。

扩展欧几里得求 \(ax+by=\gcd(a,b)\),所以这里 \(\gcd(a,b)=1\),即 \(a\)\(b\) 互质。所以 \(a\) 有模 \(b\) 下的乘法逆元的条件为 \(a\)\(b\) 互质。

我们将要求出来的 \(x\)\(y\) 仅仅保证满足 \(ax+by=1\),而 \(x\) 不一定是最小正整数解。有可能太大,也有可能是负数。\(x\) 批量的减去或加上 \(b\),能保证 \(ax+by=1\)。因为: \[ a(x+kb)+b(y-ka)=1 \] 这里我们可以这么写

1
x = (x % b + b) % b

二元一次不定方程

P5656 【模板】二元一次不定方程 (exgcd) - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)

\[ ax+by=c \]

\(\gcd(a,b)\mid(ax+by)\),若 \(c\) 不是 \(\gcd(a,b)\) 的倍数直接无解

\(ax+by=\gcd(a,b)\) 的一组特解为 \(x_0\)\(y_0\)。则有:

\[ \begin{aligned} ax_0+by_0=\gcd(a,b) \\ a\frac{x_0c}{\gcd(a,b)}+b\frac{y_0c}{\gcd(a,b)}=c \end{aligned} \]

故我们已经找到原方程的一组整数特解,记为 \(x_1\)\(y_1\)

\[ \begin{aligned} x_1=\frac{x_0c}{\gcd(a,b)}, \quad y_1=\frac{y_0c}{\gcd(a,b)} \end{aligned} \]

那么我们考虑构造原方程整数通解形式

我们设任意 \(d\in Q\),那么必有 \[ a(x_1+db)+b(y_1-da)=c \] 显然对于最小的 \(db\)\(da\)\(d=\frac{1}{\gcd(a,b)}\)

然后像上面一样开始乱搞

1
2
3
4
5
6
7
8
9
10
11
12
13
int d = exgcd(a, b, x, y);
if(c % d) {
cout << -1 << endl;
return;
}
x = c * x / d, y = c * y / d;
int dx = b / d, dy = a / d;
int minx = (x % dx + dx) % dx;
if(minx == 0) minx = dx;
int maxy = (c - a * minx) / b;
int miny = (y % dy + dy) % dy;
if(miny == 0) miny = dy;
int maxx = (c - b * miny) / a;

中国剩余定理

模数不互质的情况

设两个方程分别是 \(x\equiv a_1\pmod m_1,x\equiv a_2\pmod m_2\)

将它们转化为不定方程:\(x=m_1p+a_1=m_2q+a_2\),其中 \(p, q\) 是整数,则有 \(m_1p-m_2q=a_2-a_1\)

由裴属定理,当 \(a_2-a_1\) 不能被 \(\gcd(m_1,m_2)\) 整除时,无解;

其他情况下,可以通过扩展欧几里得算法解出来一组可行解 \((p,q)\)

则原来的两方程组的模方程组的解为 \(x\equiv b\pmod M\),其中 \(b=m_1p+a_1\), \(M=\mathrm{lcm}(m_1,m_2)\)

Lucas 定理

对于质数 \(p\),有

\[ \binom{n}{m}\bmod p=\binom{\lfloor\frac{n}{p}\rfloor}{\lfloor\frac{m}{p}\rfloor}\cdot\binom{n\bmod p}{m\bmod p}\bmod p \]

观察上述表达式,可知 \(n\bmod p\)\(m\bmod p\) 一定是小于 \(p\) 的数,可以直接求解,\(\displaystyle\binom{\left\lfloor\frac{n}{p}\right\rfloor}{\left\lfloor\frac{m}{p}\right\rfloor}\) 可以继续用 Lucas 定理求解。这也就要求 \(p\) 的范围不能够太大,一般在 \(10^5\) 左右。边界条件:当 \(m=0\) 的时候,返回 \(1\)

证明

考虑 \(\displaystyle\binom{p}{n} \bmod p\) 的取值,注意到 \(\displaystyle\binom{p}{n} = \frac{p!}{n!(p-n)!}\),分子的质因子分解中 \(p\) 的次数恰好为 \(1\),因此只有当 \(n = 0\)\(n = p\) 的时候 \(n!(p-n)!\) 的质因子分解中含有 \(p\),因此 \(\displaystyle\binom{p}{n} \bmod p = [n = 0 \vee n = p]\)。进而我们可以得出:

\[ \begin{align} (a+b)^p &= \sum_{n=0}^p \binom pn a^n b^{p-n}\\ &\equiv \sum_{n=0}^p [n=0\vee n=p] a^n b^{p-n}\\ &\equiv a^p + b^p \pmod p \end{align} \]

注意过程中没有用到费马小定理,因此这一推导不仅适用于整数,亦适用于多项式。因此我们可以考虑二项式 \(f^p(x)=(ax^n + bx^m)^p \bmod p\) 的结果

\[ \begin{align} (ax^n + bx^m)^p &\equiv a^p x^{pn} + b^p x^{pm} \\ &\equiv ax^{pn} + bx^{pm}\\ &\equiv f(x^p) \end{align} \]

note: \(a^{p-1}\equiv 1\pmod{p}\)

考虑二项式 \((1+x)^n \bmod p\),那么 \(\displaystyle\binom n m\) 就是求其在 \(x^m\) 次项的取值。使用上述引理,我们可以得到

\[ \begin{align} (1+x)^n &\equiv (1+x)^{p\lfloor n/p \rfloor} (1+x)^{n\bmod p}\\ &\equiv (1+x^p)^{\lfloor n/p \rfloor} (1+x)^{n\bmod p} \end{align} \]

注意前者只有在 \(p\) 的倍数位置才有取值,而后者最高次项为 \(n\bmod p \le p-1\),因此这两部分的卷积在任何一个位置只有最多一种方式贡献取值,即在前者部分取 \(p\) 的倍数次项,后者部分取剩余项,即

\[ \displaystyle\binom{n}{m}\bmod p = \binom{\left\lfloor n/p \right\rfloor}{\left\lfloor m/p\right\rfloor}\cdot\binom{n\bmod p}{m\bmod p}\bmod p \]

扩展 Lucas定理

\[ {\mathrm{C}}_n^m \bmod{p} \]

其中 \(p\le 10^6\)不保证 \(p\) 为质数。

证明

扩展Lucas定理 - HorizonWind 的洛谷博客

莫比乌斯反演

\(f\) 是算数函数,\(F\)\(f\) 的和函数,对任意正整数 \(n\),满足 \(F(n)=\sum_{d\mid n}f(d)\),则有 \(f(n)=\sum_{d\mid n}\mu(d)F(\frac{n}{d})\)

证明:

\(i\mid\frac{n}{d}\iff id\mid n\iff d\mid \frac{n}{i}\)

\[ \begin{align} \sum_{d\mid n}\mu(d)F(\frac{n}{d})&=\sum_{d\mid n}\mu(d)\sum_{i\mid\frac{n}{d}}f(i) \\ &=\sum_{i\mid n}f(i)\sum_{d\mid\frac{n}{i}}\mu(d) \end{align} \]

\(\frac{n}{i}\)\(1\) 的时候,\(\sum_{d\mid\frac{n}{i}}\mu(d)=1\),其它均为 \(0\)

所以

\[ \sum_{d\mid n}\mu(d)F(\frac{n}{d})=f(n) \]


\(F(n)=\sum_{n\mid d}f(d)\),则 \(f(n)=\sum_{n\mid d}\mu(\frac{d}{n})F(d)\)

证明: \[ \sum_{n\mid d}\mu(\frac{d}{n})F(d)=\sum_{n\mid d}\mu(\frac{d}{n})\sum_{d\mid i}f(i) \] \(n\mid d\),设 \(d'=\frac{d}{n}\)\(d=d'n\)

\(d\mid i\),则 \(d'n\mid i\),即 \(d'\mid \frac{i}{n}\) \[ \sum_{n\mid d}\mu(\frac{d}{n})\sum_{d\mid i}f(i)=\sum_{n\mid i}f(i)\sum_{d'\mid\frac{i}{n}}\mu(d') \]\(\frac{i}{n}\)\(1\),即 \(i=n\) 时,\(\sum_{d'\mid\frac{i}{n}}\mu(d')=1\),其余均为 \(0\)

所以

\[ \sum_{n\mid d}\mu(\frac{d}{n})F(d)=f(n) \]

反演结论

\[ d(ij)=\sum_{x\mid i}\sum_{y\mid j}[\gcd(x,y)=1] \]

0%