2024.12 做题记录
这个月可能没做啥特别大的思维题。但学的东西比较多。基本没记录网络流作业里面的那些题。太水的且没有任何启发性的我也略过了。
网络流
网络流问题是一类解决从源点
最基本的思想是一类贪心,考虑从源点
我们考虑给贪心加上反悔操作。当我们减去一条边的流量时,给它的反边加上这个流量,也就是我们允许退流操作。这样再跑这种增广路算法,正确性就有保证了,这种方法统称为 Ford-Fulkerson 算法,而这种单路增广的算法被称为 Edmond-Karp (EK) 算法。但这种方法的效率并不高。
于是我们考虑我们能不能在正确性有保证的情况下进行 多路增广。怎样增广其实非常显然,我们只要阻止这一次增广造成的退流即可,于是我们考虑一个有向无环图。图上最经典的 DAG 图应该就是最短路图了,于是我们考虑每次增广完都跑最短路建出最短路图,在这个图上用 DFS 跑多路增广即可。这就是著名的 Dinic 算法。
当然还需要一个保证复杂度的优化,当然我也不知道为什么能优化。就是 DFS 的时候每条边最多流一次,流完下次再来就不流了。就是对每个点记个 cur
数组就行了。这就是当前弧优化。总时间复杂度为
001. P3376 【模板】网络最大流 - Dinic 版本
难度:提高+ / 省选-
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 205;
const LL MAXM = 10005;
const LL INF = 1e18 + 5;
LL n, m, s, t, tot = 1, V[MAXM], W[MAXM], head[MAXN], nxt[MAXM], cur[MAXN], ans = 0, Dist[MAXN];
void AddEdge(LL u, LL v, LL w)
{
tot ++;
V[tot] = v; W[tot] = w;
nxt[tot] = head[u]; head[u] = tot;
return;
}
bool BFS()
{
for(LL i = 1; i <= n; i ++) Dist[i] = 0;
queue<LL> Q; Q.push(s); Dist[s] = 1;
while(!Q.empty())
{
LL u = Q.front(); Q.pop();
for(LL i = head[u]; i; i = nxt[i])
{
LL v = V[i], w = W[i];
if(!w || Dist[v]) continue;
Q.push(v); Dist[v] = Dist[u] + 1;
if(v == t) return true;
}
}
return false;
}
LL DFS(LL u, LL Lim)
{
if(u == t) return Lim;
LL ress = Lim;
for(LL i = cur[u]; i && ress; i = nxt[i])
{
cur[u] = i;
LL v = V[i], w = W[i];
if(!w || Dist[v] != Dist[u] + 1) continue;
LL k = DFS(v, min(ress, w));
if(!k) Dist[v] = 0;
W[i] -= k; W[i ^ 1] += k;
ress -= k;
}
return Lim - ress;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m >> s >> t;
for(LL i = 1; i <= m; i ++)
{
LL u, v, w;
cin >> u >> v >> w;
AddEdge(u, v, w); AddEdge(v, u, 0);
}
LL nowflow = 0;
while(BFS())
{
for(LL i = 1; i <= n; i ++) cur[i] = head[i];
while(nowflow = DFS(s, INF)) ans += nowflow;
}
cout << ans << '\n';
return 0;
}
002. *P4311 士兵占领
难度:省选 / NOI-
于是答案就是没被 Ban 的格子总数减去最大流。
003. P10930 异象石
难度:省选 / NOI-
维护树上动态路径并的题目。
dfn
小于 dfn
的选择点,dfn
大于 dfn
的选择点。
考虑简单证明
, 与 在同一条链上,其实就是根本没有增加贡献,其实就是一加一减,把贡献全消了。 与 在同一条链上, 不在,那么与第一种情况是同理的,也是把贡献全消除了。 与 在同一条链上, 不在,与情况二对称。 , 与 在不同子树,那么就是容斥,两个贡献然后把算重的两段合成一段删去就是了。 与 在同一条链上, 不在,这种情况显然不存在。
这个过程可以用 set 维护,其实维护方式我认为没啥难的,注意处理不存在
004. P4318 完全平方数
难度:省选 / NOI-
这个做法应该完全没有这个难度。性质题。
LCM
等价于乘积。乘积的增长速度巨快,完全容斥不满,于是就跑的飞快。
005. P4451 [国家集训队] 整数的lqp拆分
难度:省选 / NOI-
发现这个式子很像卡特兰数的一种递推式子,可以考虑生成函数。但是我不会,于是打表找规律。
二项式反演
就是一个式子:
二项式反演主要用于刻画一类,至少选
006. *P4859 已经没有什么好害怕的了
难度:省选 / NOI-
我们首先把
恰好
设
我们先把
那么有显然的转移,
然后
动态 DP
动态 DP 是一种思想,将 DP 中的简单转移写作矩阵乘法的形式或
最后所需要的值一般是所有矩阵的乘积,也就是线段树根节点维护的值。
007. [ABC246Ex] 01? Queries
难度:省选 / NOI-
设
s[i] = '0'
s[i] = '1'
s[i] = '?'
将初始矩阵设为
三种方程的转移矩阵都是显然的。把它们放到线段树上。然后你要做的就是单点修改即可了。
008. P1453 城市环路
难度:提高+ / 省选-
主要不是想记题咋做。是记一类基环树的判环 Trick。很简单,就是用并查集维护要断的环的其中两个相邻节点。然后强制不选
*009. P8867 [NOIP2022] 建造军营
难度:省选 / NOI-
见我的 2022-2023 赛季选讲 PPT
010. P8820 [CSP-S 2022] 数据传输
难度:省选 / NOI-
见我的 2022-2023 赛季选讲 PPT
011. P8820 [CSP-S 2022] 星战
难度:省选 / NOI-
见我的 2022-2023 赛季选讲 PPT
012. P6190 [NOI Online #1 入门组] 魔法
难度:省选 / NOI-
简单题。考虑经典 Trick —— 将图转化为邻接矩阵的形式。设
转移矩阵就是将一条边免费。矩阵应为
矩阵乘法
因为
Matrix friend operator * (Matrix P, Matrix Q)
{
Matrix R; R.Clear();
for(LL k = 1; k <= n; k ++)
for(LL i = 1; i <= n; i ++)
for(LL j = 1; j <= n; j ++)
R.A[i][j] = min(R.A[i][j], P.A[i][k] + Q.A[k][j]);
return R;
}
然后你暴力转移就好了。没啥难的。
013. P3203 [HNOI2010] 弹飞绵羊
难度:省选 / NOI-
014. P3396 哈希冲突
难度:提高+ / 省选 -
*015. P1989 无向图三元环计数
难度:提高+ / 省选-
很难想到。图上三元环计数板子题。
三元环计数
考虑给每条边定向。度数小的向度数大的连边。你统计的就是
这样你直接暴力枚举复杂度就是正确的。
首先不难发现这个图是一个有向无环图。但其实也挺难发现。比如原图就是一个三元环,那么连出来的图也是三元环。为了保证原图的偏序性质,如果度数相等,那么比较编号大小即可。
接下来证明:
- 如果
点在原图上的度 ,那复杂度显然是对的。 - 反之,那么由于边只有
条,所以它往出连的点最多只有 个。然后复杂度依然是对的。然后做完了。
016. HH 的项链
难度:提高+ / 省选-
Bonus:这题用莫队做会简单到爆,但是貌似被卡了?不知道,没试过。
这题挺典的。考虑离线扫描线,把询问挂右端点上。
思考:一个点有贡献意味着什么?答案是与这个点同色的最靠近他的左边的点小于询问区间左端点。
这是好维护的,我们记每个位置的
Bonus2:这题相当于把贡献挂自己身上,还有一个题是把询问挂在自己
2 - SAT 问题
挺精妙的一类问题。巧妙地将变量间的适应性问题转化为了图论相关问题。2 - SAT 一般有两种条件。一个是与,也就是合取,那么对于这类条件我们只需要给相应的变量直接赋值就可以了。而真正的难点在于或,也就是析取,那么我们考虑建图连边。
边的意思:如果
举个例子,如果题目中说
再讨论一种。比如如果条件为
对于不合法的情况,很简单,就是若
对于构造解这种行为。那么其实我们有一种非常显然的贪心,就是我们要让这个值影响的越少越好。于是我们考虑刻画“越少越好”这一想法。其实就是在缩点后的拓扑序上,编号越大的越好。但是由于你用了 Tarjan 缩点,所以你先缩的强联通分量必然是靠近叶子的。于是你得到的恰好就接近于拓扑逆序。
于是我们只关注强连通分量的编号就好了。具体的,如果
017. 【模板】2-SAT
代码实现:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
vector<int> Adj[MAXN];
int n, m, dfn[MAXN], SCCNO[MAXN], SCCcnt = 0, sizSCC[MAXN], low[MAXN], Stk[MAXN], Top = 0, dfsTime = 0;
void Tarjan(int u)
{
Stk[++ Top] = u; dfn[u] = low[u] = ++ dfsTime;
for(int i = 0; i < Adj[u].size(); i ++)
{
int v = Adj[u][i];
if(!dfn[v])
{
Tarjan(v);
low[u] = min(low[u], low[v]);
}
else if(!SCCNO[v]) low[u] = min(low[u], dfn[v]);
}
if(dfn[u] == low[u])
{
sizSCC[++ SCCcnt] = 1; SCCNO[u] = SCCcnt;
while(Stk[Top] != u)
{
SCCNO[Stk[Top]] = SCCcnt;
sizSCC[SCCcnt] ++;
Top --;
}
Top --;
}
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= m; i ++)
{
int u, a, v, b;
cin >> u >> a >> v >> b;
Adj[u + (a ^ 1) * n].push_back(v + b * n);
Adj[v + (b ^ 1) * n].push_back(u + a * n);
}
for(int i = 1; i <= (n << 1); i ++)
{
if(dfn[i]) continue;
dfsTime = 0; Tarjan(i);
}
for(int i = 1; i <= n; i ++)
{
if(SCCNO[i] == SCCNO[i + n])
{
cout << "IMPOSSIBLE" << '\n';
return 0;
}
}
cout << "POSSIBLE" << '\n';
for(int i = 1; i <= n; i ++) cout << (SCCNO[i] > SCCNO[i + n]) << " ";
cout << '\n';
return 0;
}
莫队
莫队是一种优雅的暴力。真就是暴力。
考虑离线所有询问,进行一定排序后暴力从一个询问按格转移到下一个区间。如从
当然纯暴力是不行的。我们需要找一种让扩展次数尽量少的方法。其实是曼哈顿距离最小生成树。但由于求最小生成树的复杂度过高。莫队给了我们一种非常神奇的区间排序方法。
考虑将序列分块,每次比较两个区间的时候,优先比较他们左端点的所在块,如果块不同,那么块小的排在前面,如果相等,那么右端点大的排在前面。
证明你可以把一个区间
使用莫队算法的前提是你可以快速地扩展一个端点,可以快速的删除一个点。如果不能快速做到其中之一,那么可能需要一种莫队的变种——回滚莫队来解决这类问题。
019. P2709 小B的询问
难度:提高+ / 省选-
算是莫队的模板题了。
考虑扩展一个点:就是开一个桶统计数的出现次数。答案计算可以拆完全平方式:
考虑删除一个点:也是统计出现次数。答案计算也可以拆完全平方式:
代码实现:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 50005;
int n, m, k, A[MAXN], LenBlock, Blo[MAXN], cnt[MAXN], ans = 0, Ans[MAXN];
struct Qry
{
int L, R, id;
} Q[MAXN];
bool Cmp(Qry u, Qry v)
{
if(Blo[u.L] == Blo[v.L]) return u.R < v.R;
return Blo[u.L] < Blo[v.L];
}
void Add(int x)
{
cnt[A[x]] ++;
ans += 2 * cnt[A[x]] - 1;
return;
}
void Del(int x)
{
cnt[A[x]] --;
ans -= 2 * cnt[A[x]] + 1;
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m >> k; LenBlock = sqrt(n);
for(int i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
for(int i = 1; i <= n; i ++) cin >> A[i];
for(int i = 1; i <= m; i ++) { cin >> Q[i].L >> Q[i].R; Q[i].id = i; }
sort(Q + 1, Q + m + 1, Cmp);
int nowL = 1, nowR = 0;
for(int i = 1; i <= m; i ++)
{
while(nowL > Q[i].L) Add(-- nowL);
while(nowR < Q[i].R) Add(++ nowR);
while(nowL < Q[i].L) Del(nowL ++);
while(nowR > Q[i].R) Del(nowR --);
Ans[Q[i].id] = ans;
}
for(int i = 1; i <= m; i ++) cout << Ans[i] << '\n';
return 0;
}
019. P1494 [国家集训队] 小 Z 的袜子
难度:提高+ / 省选-
也是板子。先考虑概率转计数。每次选的总数是固定的,就是
维护方面也是维护一个桶统计出现次数。每种颜色的贡献是
020. P4462 [CQOI2018] 异或序列
难度:提高+ / 省选-
板子,继续开桶维护。考虑到异或可以前缀和,于是维护区间异或等价于维护前缀异或。然后做完了。
*021. P4137 Rmq Problem / mex
难度:省选 / NOI-
莫队+值域分块科技题。考虑修改,非常简单就是开桶维护。
考察 mex 的性质。就是从
考虑记另一个桶,记每一块有多少个数出现。询问时,如果这个块有的数个数等于块长,那么不可能存在 mex,直接跳过。否则块内暴力查找。
这样最多查
#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
LL n, m, A[MAXN], LenBlock, B = 500, Blo[MAXN], cntBlo[MAXN], cnt[MAXN], ans[MAXN];
struct Qry
{
LL L, R, id;
} Q[MAXN];
bool Cmp(Qry x, Qry y)
{
if(Blo[x.L] != Blo[y.L]) return Blo[x.L] < Blo[y.L];
else return x.R < y.R;
}
void Add(LL x)
{
cnt[A[x]] ++;
if(cnt[A[x]] == 1) cntBlo[A[x] / B] ++;
return;
}
void Del(LL x)
{
cnt[A[x]] --;
if(cnt[A[x]] == 0) cntBlo[A[x] / B] --;
return;
}
LL Query() // 值域分块
{
LL res = MAXN;
for(LL i = 1; i <= B; i ++)
{
if(cntBlo[i - 1] == B) continue;
for(LL j = (i - 1) * B; j < i * B; j ++)
if(!cnt[j]) { res = j; return res; }
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m; LenBlock = sqrt(n);
for(LL i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
for(LL i = 1; i <= n; i ++) { cin >> A[i]; }
for(LL i = 1; i <= m; i ++) { cin >> Q[i].L >> Q[i].R; Q[i].id = i; }
sort(Q + 1, Q + m + 1, Cmp);
LL nowL = 1, nowR = 0;
for(LL i = 1; i <= m; i ++)
{
while(nowL > Q[i].L) Add(-- nowL);
while(nowR < Q[i].R) Add(++ nowR);
while(nowL < Q[i].L) Del(nowL ++);
while(nowR > Q[i].R) Del(nowR --);
ans[Q[i].id] = Query();
}
for(LL i = 1; i <= m; i ++) cout << ans[i] << '\n';
return 0;
}
022. P4396 [AHOI2013] 作业
难度:省选 / NOI-
非常简单的题。也是莫队+加值域分块科技。唯一需要注意的就是询问区间长度小于
023. P3730 曼哈顿交易
难度:省选 / NOI-
另类值域分块。这个值域分块考虑维护热度。扩展和删除的时候稍显麻烦,不过也很套路就是了。
void Add(int x)
{
nowcnt ++;
-- cnt2[cnt[A[x]]];
-- cntBlo[cnt[A[x]] / B];
++ cnt2[++ cnt[A[x]]];
++ cntBlo[cnt[A[x]] / B];
return;
}
void Del(int x)
{
-- cnt2[cnt[A[x]]];
-- cntBlo[cnt[A[x]] / B];
++ cnt2[-- cnt[A[x]]];
++ cntBlo[cnt[A[x]] / B];
return;
}
支持修改的莫队(带修莫队)
就是高维莫队。考虑在每个询问上记录这个询问前面已经有多少次修改了。
然后在扩展左右端点的基础上,再加一个扩展修改位置即可。注意修改的时候判断修改点在询问区间里面才能修改。
024. P1903 [国家集训队] 数颜色 / 维护队列
难度:提高+ / 省选-
板子题:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2000005;
int n, m, A[MAXN], LenBlock, Blo[MAXN], cnt[MAXN], cntQ = 0, cntC = 0, anss = 0, ans[MAXN];
inline int read()
{
int x = 0, f = 1;
char ch = getchar();
while(ch < '0' || ch > '9') { if(ch == '-') f = -1; ch = getchar(); }
while(ch >= '0' && ch <= '9') { x = x * 10 + ch - 48; ch = getchar(); }
return x * f;
}
struct Qry
{
int L, R, tim, id;
} Q[MAXN];
struct Updat
{
int Pos, col;
} C[MAXN];
bool Cmp(Qry x, Qry y)
{
if(Blo[x.L] != Blo[y.L]) return Blo[x.L] < Blo[y.L];
if(Blo[x.R] != Blo[y.R]) return Blo[x.R] < Blo[y.R];
return x.tim < y.tim;
}
void Add(int x)
{
cnt[A[x]] ++;
if(cnt[A[x]] == 1) anss ++;
return;
}
void Del(int x)
{
cnt[A[x]] --;
if(cnt[A[x]] == 0) anss --;
return;
}
void Solve(int x, int i)
{
if(C[x].Pos >= Q[i].L && C[x].Pos <= Q[i].R)
{
cnt[A[C[x].Pos]] --; if(cnt[A[C[x].Pos]] == 0) anss --;
cnt[C[x].col] ++; if(cnt[C[x].col] == 1) anss ++;
}
swap(C[x].col, A[C[x].Pos]);
return;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0); cout.tie(0);
cin >> n >> m;
for(int i = 1; i <= n; i ++) cin >> A[i];
for(int i = 1; i <= m; i ++)
{
char Opt; cin >> Opt;
if(Opt == 'Q')
{
cntQ ++;
cin >> Q[cntQ].L >> Q[cntQ].R; Q[cntQ].id = cntQ; Q[cntQ].tim = cntC;
}
else
{
cntC ++;
cin >> C[cntC].Pos >> C[cntC].col;
}
}
LenBlock = pow(n, 0.66);
for(int i = 1; i <= n; i ++) Blo[i] = (i - 1) / LenBlock + 1;
sort(Q + 1, Q + cntQ + 1, Cmp);
int nowL = 1, nowR = 0, tt = 0;
for(int i = 1; i <= cntQ; i ++)
{
while(nowL > Q[i].L) Add(-- nowL);
while(nowR < Q[i].R) Add(++ nowR);
while(nowL < Q[i].L) Del(nowL ++);
while(nowR > Q[i].R) Del(nowR --);
while(tt < Q[i].tim) Solve(++ tt, i);
while(tt > Q[i].tim) Solve(tt --, i);
ans[Q[i].id] = anss;
}
for(int i = 1; i <= cntQ; i ++) cout << ans[i] << '\n';
return 0;
}
025. P2464 [SDOI2008] 郁闷的小 J
难度:提高+ / 省选-
一样的板子题,注意先离散化一下就好。
026. P4151 [WC2011] 最大XOR和路径
难度:省选 / NOI-
一个非常重要有启发性的结论是:图上路径可以拆成一棵 DFS 树套上很多环的形式。
然后我们从
最后我们的答案就是 DFS 树的答案异或上所有环的异或最大值。所以线性基求一下异或最大即可。然后做完了。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】