July Summary
07.07
考试(DP 大赛)。
T1 是个 DP?T2 也是个 DP?T3 也是个 DP?T4 也是个 DP?
太可怕了,刚好不擅长 DP。
A subnp
\(dp_i\) 表示以含质因数 \(i\) 的数结尾满足条件的序列最长为多少。
如果一个数为 \(x\),对于所有它的质因数 \(p_i\) 找到最大 \(dp\) 值,让所有 \(dp_{p_i}\) 变为这个 \(dp\) 值 \(+ 1\) 。
B div
第一问还是很简单的。
设 \(dp_i\) 为大小为 \(i\) 的堆最多会有几次分到质数,\(dp_i = max\{dp_{i-j} + dp_j + !isnp_i\}(1 \le j < i)\),\(isnp_i\) 为 \(0\) 表示 \(i\) 为质数。
对于第二问,维护一个数组 \(f_i\) 表示是否在最大方案中有过 \(i\)。很好维护,不讲了。
欸这个时间复杂度好像过不了啊?\(n = 30000\) 怎么办?
但是我过了???神秘。
C exercise
\(dp_{i,j,1}\) 表示下一次向大找,反之向小找。前缀和优化一下,然后就随便写了,注意 long long 和取模。
D corn
恐怖 DP,不会。
07.08
又是 DP???
稍微熟练一下 DP,?好像什么都有,差不多都练了一下。
题很可爱,没写完。
07.09
又 是 D P ? ? ?
数位 DP,难的很难,简单的简单得离谱。
数位 DP 不就是高级的 DFS 吗???不知道怎么有蓝的。
07.10
思 维 训 练。
A P3619 魔法 - 洛谷
简单贪心(?)但还是没有场切。
如果想要完成任务 \(j\) 后不能完成 \(i\),完成任务 \(i\) 后还能完成 \(j\),那么 \(t_i+b_i>t_j+b_j\)。
排个序然后挨个判断就行了。
B Decoding Genome
DP + 矩阵加速。
很显然转移为配对的矩阵,转移次数为 \(n-1\) 然后 随 便 写。
C Jee, You See?
数位 DP。我 真 的 不 想 写 D P 了 。
实际上根本不会写。
D GCD Subtraction
发现暴力模拟会炸,注意到 \(\gcd(a,b) = 1\) 时耗费大量时间,考虑合并。
设 \(c\) 会减几次 \(1\)。则 \(\gcd(a - c, b - c) \ne 1\)。
然后乱搞一下,枚举 \(a - b\) 的因数 \(d\),求出最小 \(x=a \bmod d\)。
E Words on Tree
2-SAT。然后我就不会了。
07.11
博弈论。
ε=ε=ε=ε=ε=┌(; ̄◇ ̄)┘
结果发现好像非常()。(填空,\(4\) 分)。
四大博弈模型,看懂了但不会转化。
07.12
CSP-S 模拟赛,恐怖。
A [USACO09OPEN] Cow Digit Game S
博弈论。建个博弈图,直接硬跑,查询时直接由跑出来的数组判断。
B [ZJOI2010] 数字计数
出简单数位 DP 的出题人才是好出题人。
非常简单,直接数位 DP,没什么好说的,好像还可以打表。
C P11774 [COTS 2013] 矩形覆盖 / BAKTERIJE - 洛谷
神秘。不会扫描线被秒了。
D Museums Tour
Tarjan 缩点 + DFS,注意处理缩点后的点权(在这里调了 60 min)。
场上写 BFS,下标没取模痛失 \(45 pts\)。
07.14
树的直径和重心。
树的重心的性质:
- 性质一:树的重心如果不唯一,则至多有两个,且这两个重心相邻。
- 性质二:以树的重心为根时,所有子树的大小都不超过整棵树大小的一半。
- 性质三:树中所有点到某个点的距离和中,到重心的距离和是最小的;如果有两个重心,那么到它们的距离和一样。
- 性质四:把两棵树通过一条边相连得到一棵新的树,那么新的树的重心在连接原来两棵树的重心的路径上。
- 性质五:在一棵树上添加或删除一个叶子,那么它的重心最多只移动一条边的距离。
CF685B Kay and Snowflake - 洛谷(树的重心模板)
当时__没有写(为 07.24 思维训练__埋下伏笔)。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n, m, f[300005], sz[300005], ans[300005];
vector<int> vc[300005];
void dfs(int x){
int mx = 0;
sz[x] = 1;
ans[x] = x;
for(int i = 0; i < vc[x].size(); i++){
int v = vc[x][i];
dfs(v);
sz[x] += sz[v];
if(sz[mx] < sz[v]) mx = v;
}
if(sz[mx] * 2 > sz[x]){
int v = ans[mx];
while((sz[x] - sz[v]) * 2 > sz[x]) v = f[v];
ans[x] = v;
}
}
int main(){
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for(int i = 2; i <= n; i++){
int v;
cin >> v;
vc[v].push_back(i);
f[i] = v;
}
dfs(1);
while(m--){
int v;
cin >> v;
cout << ans[v] << '\n';
}
return 0;
}
树的直径的性质:
-
性质一:树的直径可能有多条,但这些直径必然有重复的一段或一点。
-
性质二:树上任意一点,距离它最远的节点是直径两个端点中的其中一个。
B4016 树的直径 - 洛谷
根据性质二进行两次 dfs 就行了。实在不知道我以前为什么不会。
#include <bits/stdc++.h>
using namespace std;
int n, mx, dis[100005];
vector<int> vc[100005];
void dfs(int x, int fa){
if(fa) dis[x] = dis[fa] + 1;
for(int i = 0; i < vc[x].size(); i++){
int v = vc[x][i];
if(v == fa) continue;
dfs(v, x);
if(dis[v] > dis[mx]) mx = v;
}
}
int main(){
cin >> n;
for(int i = 1; i <= n - 1; i++){
int u, v;
cin >> u >> v;
vc[u].push_back(v);
vc[v].push_back(u);
}
dfs(1, 0);
memset(dis, 0, sizeof dis);
dfs(mx, 0);
cout << dis[mx];
return 0;
}
07.15
字符串基础算法(KMP、01Trie、Manacher 等)。
P4551 最长异或路径 - 洛谷(01Trie 模板)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
struct p{
ll v, w;
};
vector<p> vc[2000005];
ll n, c = 1, xr[6000005], zotrie[6000005][2];
void dfs(int x, int fa){
for(int i = 0; i < vc[x].size(); i++){
int v = vc[x][i].v;
ll w = vc[x][i].w;
if(v == fa) continue;
xr[v] = xr[x] ^ w;
dfs(v, x);
}
}
void insert(ll s){
ll u = 0;
for(int i = 30; i >= 0; i--){
bool x = (s >> i) & 1;
if(!zotrie[u][x]){
c++;
zotrie[u][x] = c;
}
u = zotrie[u][x];
}
}
ll query(ll s){
ll ans = 0, u = 0;
for(int i = 30; i >= 0; i--){
bool x = (s >> i) & 1;
if(zotrie[u][!x]){
ans += (1 << i);
u = zotrie[u][!x];
}
else u = zotrie[u][x];
}
return ans;
}
int main(){
cin >> n;
for(int i = 1; i <= n - 1; i++){
int u, v, w;
cin >> u >> v >> w;
vc[u].push_back({v, w});
vc[v].push_back({u, w});
}
dfs(1, 0);
for(int i = 1; i <= n; i++) insert(xr[i]);
ll mx = 0;
for(int i = 1; i <= n; i++) mx = max(mx, query(xr[i]));
cout << mx;
return 0;
}
P3375 【模板】KMP - 洛谷
#include <bits/stdc++.h>
using namespace std;
int nxt[1000005];
string s1, s2;
int main(){
cin >> s1 >> s2;
int n = s2.size(), m = s1.size();
s2 = ' ' + s2;
s1 = ' ' + s1;
nxt[1] = 0;
for(int i = 2, j = 0; i <= n; i++){
while(j && s2[i] != s2[j + 1]) j = nxt[j];
if(s2[i] == s2[j + 1]) j++;
nxt[i] = j;
}
for(int i = 1, j = 0; i <= m; i++){
while(j && s1[i] != s2[j + 1]) j = nxt[j];
if(s1[i] == s2[j + 1]) j++;
if(j == n){
cout << i - j + 1 << '\n';
j = nxt[j];
}
}
for(int i = 1; i <= n; i++) cout << nxt[i] << ' ';
return 0;
}
P3805 【模板】manacher - 洛谷
#include <bits/stdc++.h>
using namespace std;
string S;
int ans, mxl[22000005];
int main(){
cin >> S;
string s = "!-";
for(int i = 0; i < S.size(); i++){
s += S[i];
s += "-";
}
s += '@';
int n = s.size() - 1, r = 0, mid = 0;
for(int i = 1; i <= n; i++){
int u = max(0, mid * 2 - i);
int len = min(mxl[u], r - i);
if(i > r) len = 1;
while(s[i + len] == s[i - len]) len++;
mxl[i] = len;
len--;
if(i + len > r){
r = i + len;
mid = i;
}
ans = max(ans, mxl[i]);
}
cout << ans - 1;
return 0;
}
07.16
线段树进阶。
一 个 都 不 会。
其实会两个 板子 。
练习题:
(板子)SP2713 GSS4 - Can you answer these queries IV - 洛谷
(板子)T168212 DYZ AK IOI 1 加强版 - 洛谷
P5298 [PKUWC2018] Minimax - 洛谷
P4556 [Vani有约会] 雨天的尾巴 /【模板】线段树合并 - 洛谷
CF1672I PermutationForces - 洛谷
07.17
查漏补缺。补了两个题。
07.18
思维训练。
A Power Strings
KMP,但是暴力可过。
B Progressions Covering
贪心地从后往前加,没有满足条件就一直 \(k\) 加到满足为止。
C Sausage Maximization
观察题目,看到异或和最大,自然想到了 01Trie。
前后缀不相交的条件其实不用管,反正一样的数异或起来是 \(0\),不会产生影响。
那么直接用一个 01Trie 维护前缀异或和最大值,然后对于每个后缀异或和找最大就行了。
D P1937 [USACO10MAR] Barn Allocation G - 洛谷
贪心 + 线段树。
按照右端点从小到大排序,一个一个填区间就行了。
E Different Arrays
不会。
07.19
补题,不记得补了几道了,好像至少有一道。
07.21
LCA 和树上差分。
LCA 没什么好说的,树上差分也挺好理解的。
P3379 【模板】最近公共祖先(LCA) - 洛谷
#include <bits/stdc++.h>
using namespace std;
int n, m, s, dep[500005], pa[500005][20];
vector<int> vc[500005];
void dfs(int x, int fa){
pa[x][0] = fa;
dep[x] = dep[fa] + 1;
for(int i = 0; i < vc[x].size(); i++)
if(vc[x][i] != fa)
dfs(vc[x][i], x);
}
void painit(){
for(int i = 1; i <= 19; i++)
for(int j = 1; j <= n; j++)
pa[j][i] = pa[pa[j][i - 1]][i - 1];
}
int fly(int x, int d){
for(int i = 0; i <= 19; i++)
if((d >> i) & 1)
x = pa[x][i];
return x;
}
int LCA(int x, int y){
if(dep[x] > dep[y]) swap(x, y);
y = fly(y, dep[y] - dep[x]);
if(x == y) return x;
for(int i = 19; i >= 0; i--){
if(pa[x][i] != pa[y][i]){
x = pa[x][i];
y = pa[y][i];
}
}
return pa[x][0];
}
int main(){
ios::sync_with_stdio(0);
cout.tie(0);
cin.tie(0);
cin >> n >> m >> s;
for(int i = 1; i <= n - 1; i++){
int x, y;
cin >> x >> y;
vc[x].push_back(y);
vc[y].push_back(x);
}
dfs(s, 0);
painit();
while(m--){
int x, y;
cin >> x >> y;
cout << LCA(x, y) << '\n';
}
return 0;
}
P3128 [USACO15DEC] Max Flow P(树上差分)
#include <bits/stdc++.h>
using namespace std;
int n, k, maxn, nw, s[50005], sz[50005], id[50005], dep[50005], ans[50005], pa[50005][16];
vector<int> vc[50005];
void dfs(int x, int fa){
nw++;
sz[x] = 1;
id[x] = nw;
dep[x] = dep[fa] + 1;
pa[x][0] = fa;
for(int i = 0; i < vc[x].size(); i++){
if(vc[x][i] != fa){
dfs(vc[x][i], x);
sz[x] += sz[vc[x][i]];
}
}
}
void painit(){
for(int i = 1; i <= 15; i++)
for(int j = 1; j <= n; j++)
pa[j][i] = pa[pa[j][i - 1]][i - 1];
}
int fly(int x, int d){
for(int i = 15; i >= 0; i--){
if((d >> i) & 1) x = pa[x][i];
}
return x;
}
int LCA(int x, int y){
if(dep[x] > dep[y]) swap(x, y);
y = fly(y, dep[y] - dep[x]);
if(x == y) return x;
for(int i = 15; i >= 0; i--){
if(pa[x][i] != pa[y][i]){
x = pa[x][i];
y = pa[y][i];
}
}
return pa[x][0];
}
int dfans(int x, int fa){
int sum = s[x];
for(int i = 0; i < vc[x].size(); i++){
if(vc[x][i] != fa){
sum += dfans(vc[x][i], x);
}
}
maxn = max(sum, maxn);
return sum;
}
int main(){
cin >> n >> k;
for(int i = 1; i <= n - 1; i++){
int x, y;
cin >> x >> y;
vc[x].push_back(y);
vc[y].push_back(x);
}
dfs(1, 0);
painit();
while(k--){
int x, y;
cin >> x >> y;
int lca = LCA(x, y);
s[x]++;
s[y]++;
s[lca]--;
s[pa[lca][0]]--;
}
dfans(1, 0);
cout << maxn;
return 0;
}
练习题:
P5836 [USACO19DEC] Milk Visits S
P3038 [USACO11DEC] Grass Planting G
都比较简单。
07.22
树剖。
怎么所有人都做过练习题啊,怎么只有我没学过啊。
写了 \(4\) 题,拼尽全力无法战胜。
P3384 【模板】重链剖分/树链剖分
重链剖分的性质(部分):
-
树上每个节点都属于且仅属于一条重链。
-
所有的重链将整棵树 完全剖分。
-
在剖分时 重边优先遍历,最后树的 DFS 序上,重链内的 DFS 序是连续的。按 DFN 排序后的序列即为剖分后的链。
相当于把树分为一堆重链,然后利用重链的性质维护问题。
// sz[i]:以 i 为根的子树大小 hs[i]: i 的重儿子 fa[i]: i 的父亲节点 tp[i]: i 所在重链的链顶节点
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
int n, m, r, p, cnt, sz[100005], hs[100005], fa[100005], tp[100005], id[100005], dep[100005];
vector<int> vc[100005];
ll a[100005], tr[400005], lazy[400005], A[100005];
void down(int x, ll k, int len){
tr[x] += k * len;
tr[x] %= p;
lazy[x] += k;
lazy[x] %= p;
}
void pushdown(int x, int l, int r){
int mid = (l + r) / 2;
down(x * 2, lazy[x], mid - l + 1);
down(x * 2 + 1, lazy[x], r - mid);
lazy[x] = 0;
}
void add(int a, int x, int l, int r, int L, int R){
if(r < L || l > R) return;
if(L <= l && r <= R){
tr[a] += x * (r - l + 1) % p;
tr[a] %= p;
lazy[a] += x;
lazy[a] %= p;
return;
}
pushdown(a, l, r);
int mid = (l + r) / 2;
add(a * 2, x, l, mid, L, R);
add(a * 2 + 1, x, mid + 1, r, L, R);
tr[a] = (tr[a * 2] + tr[a * 2 + 1]) % p;
}
ll query(int a, int l, int r, int L, int R){
if(r < L || l > R) return 0;
if(L <= l && r <= R){
return tr[a] % p;
}
pushdown(a, l, r);
int mid = (l + r) / 2;
ll s = 0;
s += query(a * 2, l, mid, L, R);
s += query(a * 2 + 1, mid + 1, r, L, R);
return s % p;
}
void build(int x, int l, int r){
if(l == r){
tr[x] = A[l];
return;
}
int mid = (l + r) / 2;
build(x * 2, l, mid);
build(x * 2 + 1, mid + 1, r);
tr[x] = (tr[x * 2] + tr[x * 2 + 1]) % p;
}
void dfs1(int x, int f){
fa[x] = f;
sz[x] = 1;
dep[x] = dep[f] + 1;
for(int i = 0; i < vc[x].size(); i++){
int v = vc[x][i];
if(v == f) continue;
dfs1(v, x);
sz[x] += sz[v];
if(sz[v] > sz[hs[x]]) hs[x] = v;
}
}
void dfs2(int x, int t){
tp[x] = t;
id[x] = ++cnt;
A[cnt] = a[x];
if(!hs[x]) return;
dfs2(hs[x], t);
for(int i = 0; i < vc[x].size(); i++){
int v = vc[x][i];
if(v == hs[x] || v == fa[x]) continue;
dfs2(v, v);
}
}
void addp(int x, int y, int z){
while(tp[x] != tp[y]){
if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
add(1, z, 1, n, id[tp[x]], id[x]);
x = fa[tp[x]];
}
if(dep[x] > dep[y]) swap(x, y);
add(1, z, 1, n, id[x], id[y]);
}
ll qryp(int x, int y){
ll s = 0;
while(tp[x] != tp[y]){
if(dep[tp[x]] < dep[tp[y]]) swap(x, y);
s += query(1, 1, n, id[tp[x]], id[x]);
s %= p;
x = fa[tp[x]];
}
if(dep[x] > dep[y]) swap(x, y);
s = (s + query(1, 1, n, id[x], id[y])) % p;
return s;
}
int main(){
cin >> n >> m >> r >> p;
for(int i = 1; i <= n; i++) cin >> a[i];
for(int i = 1; i <= n - 1; i++){
int x, y;
cin >> x >> y;
vc[x].push_back(y);
vc[y].push_back(x);
}
dfs1(r, 0);
dfs2(r, r);
build(1, 1, n);
while(m--){
int op, x;
cin >> op >> x;
if(op == 1){
int y, z;
cin >> y >> z;
z %= p;
addp(x, y, z);
}
else if(op == 2){
int y;
cin >> y;
cout << qryp(x, y) <<'\n';
}
else if(op == 3){
int z;
cin >> z;
z %= p;
add(1, z, 1, n, id[x], id[x] + sz[x] - 1);
}
else{
cout << query(1, 1, n, id[x], id[x] + sz[x] - 1) << '\n';
}
}
return 0;
}
[!NOTE]
要先调用 dfs1 和 dfs2,再调用 build 函数初始化线段树,不然会错。
用新的数组保存映射后的原数组,不要直接覆盖原数组。
07.23
补题,下午回家了。
轻重边没调出来。
07.24
思 维 训 练 。
A Minimum spanning tree for each edge
求出最小生成树并记下树边。对于每条边,作如下判断:
- 如果是最小生成树的树边,答案即为原本的最小边权和。
- 如果不是,在原本的最小生成树上加上此边会形成环,删去环上除了此边最大的边即为答案。倍增,稍微有点麻烦。
B A Wide, Wide Graph
很显然,连通块个数为离直径一端最远距离 \(> k\) 的点 \(+1\)。
直接求出数的直径的两个端点,在此过程中可以顺便求出每个点与直径端点的距离。
然后就随便做了。
C White Lines
形似二维前缀和。一路 \(O(n^2)\) 枚举左上顶点推过去即可。
实现非常麻烦,所以没有实现。
D Kay and Snowflake
原题,刚好没写。(是的就是 CF685B Kay and Snowflake - 洛谷(树的重心模板),伏笔回收)。
肯定是要预处理什么的。
- 树的重心的性质之一:将两颗树合并,新的重心在原来两个重心的简单路径上。
一个以 \(x\) 为根的树的重心可以由它的子树合并得来。很显然的是,以 \(x\) 为根的树的重心不可能比它的子树的重心还要低,所以在合并的时候直接从它子树的重心往上跳,满足就记录答案。
叶子节点的重心肯定是它自己。然后就可以做了。
\(O(n)\) 预处理,\(O(1)\) 查询。
E Kuro and GCD and XOR and SUM
01Trie。
听说是 \(10^5\) 个 01Trie???恐怖如斯。
居然不会炸。
07.25
组合计数类问题选讲。
递推求逆元 & \(O(1)\) 求组合数
ll jc[305], inv[305], dp[305][305];
int fpow(ll a, int b){
ll mul = 1;
while(b){
if(b & 1) mul = a * mul % MOD;
a = a * a % MOD;
b >>= 1;
}
return mul;
}
void init(){
jc[0] = 1;
for(int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i % MOD;
inv[n] = fpow(jc[n], MOD - 2);
for(int i = n; i >= 1; i--) inv[i - 1] = inv[i] * i % MOD;
}
ll C(int x, int y){
return jc[x] * inv[y] % MOD * inv[x - y] % MOD;
}
递推求组合数 \(O(n^2)\)
for(int i = 0; i <= 2000; i++) dp[i][0] = dp[i][i] = 1;
for(int i = 1; i <= 2000; i++)
for(int j = 1; j < i; j++)
dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % MOD;
Lucas 定理
卢卡斯定理如下:
ll n, m, p, jc[300005], inv[300005];
ll fastpow(ll a, ll b){
ll mul = 1;
while(b){
if(b & 1) mul = a * mul % p;
a = a * a % p;
b >>= 1;
}
return mul;
}
void init(){
jc[0] = 1;
for(ll i = 1; i <= p - 1; i++) jc[i] = jc[i - 1] * i % p;
inv[p - 1] = fastpow(jc[p - 1], p - 2);
for(ll i = p - 1; i >= 1; i--) inv[i - 1] = inv[i] * i % p;
}
ll C(ll x, ll y){
return jc[x] * fastpow(jc[y], p - 2) % p * fastpow(jc[x - y], p - 2) % p;
}
ll Lucas(ll x, ll y){
if(!y) return 1ll;
return (Lucas(x / p, y / p) * C(x % p, y % p)) % p;
}
Catalan 数 \(O(n)\)
Cat[0] = Cat[1] = 1;
for(int i = 2; i <= n; i++) Cat[i] = Cat[i - 1] * (4 * i - 2) / (i + 1);
盒子与球问题

- 鸽巢原理
将 \(n\) 个物体划分为 \(k\) 组,那么至少存在一个分组,含有大于或等于 \(\left \lceil \dfrac{n}{k} \right \rceil\) 个物品。
-
组合数的基本求解以及用法(非常重要)
-
网格模型
-
普通网格问题
-
多组合数和
-
卡特兰数 - 与树计数
\[H_n = \begin{cases} \sum_{i=1}^{n} H_{i-1} H_{n-i} & n \geq 2, n \in \mathbf{N_{+}}\\ 1 & n = 0, 1 \end{cases} \]\[H_n = \dfrac{H_{n-1} (4n-2)}{n+1} \]\[H_n = \binom{2n}{n} - \binom{2n}{n-1} \]
-
-
插板问题
-
放球问题
- 有符号
- 无符号
-
可重集组合数
-
二项式定理
-
-
容斥
- 补集
- 其他容斥
本文来自博客园,作者:KukCair,转载请注明原文链接:https://www.cnblogs.com/KukCair/p/19003989

浙公网安备 33010602011771号