[练习记录]图论与网络流
网络流
[SHOI2002]舞会
二分图最大独立集 = 总点数 - 最小点覆盖 = 总点数 - 最大匹配
然后上dinic板板题。
[USACO05NOV]Asteroids G
若有\((x,y)\)则将行\(x\)与列\(y\)连接,答案等价于求该图最小割。
有最小割等同于最大流。
东方文花帖|【模板】有源汇上下界最大流
考虑把每个人和每天看做一个点。
天的拍照限制相当于源点连至该点\([0,d]\)的边。
每天的人则相当于天向人连了\([l,r]\)的边。
人的照片限制相当于人向汇点连了\([g,\infty]\)。
所以变成了有源汇上下界最大流。
代码考完试补上。
发现自己写了一辈子的dinic的当前弧优化是错的。
假题解的代码害人。
东方文花帖|【模板】有源汇上下界最大流
#include<bits/stdc++.h>
#define ll long long
#define inf 5000005
#define N 10005
#define M 500005
struct P{
int to,next,v;
}E[M];
int cnt = 1;
int head[N];
int n,m;
int s,t;
int sa,ta;
int in[N],out[N];
inline void clear(){
for(int i = 1;i <= n + m + 10;++i)
head[i] = in[i] = out[i] = 0;
for(int i = 1;i <= cnt;++i)
E[i].to = E[i].next = E[i].v = 0;
cnt = 1;
s = n + m + 1,t = n + m + 2,sa = n + m + 3,ta = n + m + 4;
}
inline void add(int x,int y,int v){
// std::cout<<"("<<x<<"->"<<y<<" in "<<v<<")"<<std::endl;
E[++cnt].to = y;
E[cnt].next = head[x];
E[cnt].v = v;
head[x] = cnt;
}
int dis[N];
int vis[N];
using std::list;
std::list<int>Q;
inline bool spfa(int S,int T){
for(int i = 1;i <= n + m + 10;++i)
dis[i] = 0x3f3f3f3f,vis[i] = 0;
dis[S] = 0;
Q.push_back(S);
while(Q.size()){
int u = Q.front();
Q.pop_front();
vis[u] = 0;
for(int i = head[u];i;i = E[i].next){
int v = E[i].to;
if(dis[v] > dis[u] + 1 && E[i].v){
dis[v] = dis[u] + 1;
if(!vis[v])
vis[v] = 1,Q.push_back(v);
}
}
}
return dis[T] != 0x3f3f3f3f;
}
inline int dfs(int u,int In,int S,int T){
if(u == T)
return In;
int res = In;
for(int i = head[u];i;i = E[i].next){
int v = E[i].to;
if(E[i].v && dis[v] == dis[u] + 1){
int to = dfs(v,std::min(E[i].v,res),S,T);
E[i].v -= to;
E[i ^ 1].v += to;
res -= to;
if(!to)dis[v] = 0;
if(!res)return In;
}
}
return In - res;
}
inline int dinic(int S,int T){
int ans = 0;
while(spfa(S,T))
ans += dfs(S,0x3f3f3f3f,S,T);
return ans;
}
int main(){
while(scanf("%d%d",&n,&m) != EOF){
clear();
for(int i = 1;i <= m;++i){
int g;
scanf("%d",&g);
add(n + i,t,inf - g);
in[t] += g,out[n + i] += g;
add(t,n + i,0);
}
for(int i = 1;i <= n;++i){
int c,d;
scanf("%d%d",&c,&d);
add(s,i,d);
add(i,s,0);
while(c -- ){
int ti,l,r;
scanf("%d%d%d",&ti,&l,&r);
ti ++ ;
add(i,n + ti,r - l);
out[i] += l,in[n + ti] += l;
add(n + ti,i,0);
}
}
for(int i = 1;i <= n + m + 2;++i){
int Mi = in[i] - out[i];
// std::cout<<i<<" "<<Mi<<std::endl;
if(Mi < 0)
add(i,ta,abs(Mi)),add(ta,i,0);
if(Mi > 0)
add(sa,i,abs(Mi)),add(i,sa,0),out[sa] += abs(Mi) ;
}
add(t,s,inf);
add(s,t,0);
int ans = dinic(sa,ta);
// std::cout<<ans<<" "<<out[sa]<<std::endl;
if(ans != out[sa]){
puts("-1\n");
continue;
}
E[cnt].v = E[cnt ^ 1].v = 0;
std::cout<<ans + dinic(s,t)<<std::endl<<std::endl;
}
}
清理雪道
\((s,i,0,inf)\)
\((i,t,0,inf)\)
\((i,j,1,inf)\)
有源汇上下界最小流
[SCOI2007]修车
考虑对每个修车员知其对全局答案的贡献为\(\sum i * K_i\)其中\(K_i\)是倒数第\(i\)的修车时间。
于是拆点,连边,最小费用流。
[NOI2008] 志愿者招募
考虑用费用流可以用差的形式做。
连上
\((0,1,inf,0)\)
\((i,i + 1,inf - a_i,0)\)
\((n,n + 1,inf,0)\)
\((l_i,r_i + 1,inf,c_i)\)
最小费用流即可。
[NOI2009] 植物大战僵尸
最大权闭合子图版版。
考虑对一个颗植物,对其所有保护其的点连边,和其右边的点连边。
把环去掉。
最长k可重区间集问题
对
\([l,r]\)连一条\((l,r + 1,flow:1,cost:-len)\)的边
对\(i\)连一条\((i,i+1,flow:k,cost:0)\)的边。
我们发现其区间被我们巧妙的通过流量把区间加性质变为了区间两端点的流。
教辅的组成
这么建就行了
[SDOI2009]晨跑
不难想到直接最小费用最大流,点的限制拆点就好了。
[SDOI2010]星际竞速
不妨把\(u \to v\)使用能力爆发看作在\(u\)结束操作,然后从\(S\)出发。
那么每个点只能结束一次。
\(s \to [1_x,n_x](flow:1,cost:0)\)
\(s \to [1_y,n_y](flow:1,cost:A_i)\)
\([1_y,n_y] \to t(flow:1,cost:0)\)
\(u_x \to v_y (flow:1,cost:w_i)\)
等价于一个二分图最大权匹配
[HNOI2013]切糕
考虑这类限制选取一个的最小问题可以使用最小割,其所附带的条件限制可以考虑构造图使不能选取的始终联通即将其边权设为\(\infty\)
转点成边,连\(((x,y,r),(x + d,y,r + 1),flow:1,cost:infty)\)以此类推。
文理分科
考虑分类问题可以使用最小割,把文科和理科分属两边,对全局贡献跑最小割。
太空飞行计划问题
最大权闭合子图。
圆桌问题
把桌子当作点连向\(T\),把单位当成点。
连\((s,i,flow:r_i),(i,d_j,flow:1),(d_j,T,flow:infty)\)
[SDOI2017]新生舞会
分数规划二分之后上一个费用流验证是否可以。
圆方树
[SDOI2018]战略游戏
考虑图上的连通性使用圆方树。
使用圆方树后考虑将求\(|S|\)关键点的极小生成树的黑点数量减去\(|S|\)。
考虑极小生成树的边和为\(\sum dis(a_1,a_2),....dis(a_{k - 1},dis(a_k)),dis(a_k,a_1)(dfn(a_i) < dfn(a_{i + 1}))\),然后除2.
考虑把点权推到边上即可。
[SDOI2018]战略游戏
// code by fhq_treap
#include<bits/stdc++.h>
#define ll long long
#define N 300005
inline ll read(){
char C=getchar();
ll A=0 , F=1;
while(('0' > C || C > '9') && (C != '-')) C=getchar();
if(C == '-') F=-1 , C=getchar();
while('0' <= C && C <= '9') A=(A << 1)+(A << 3)+(C - 48) , C=getchar();
return A*F;
}
template <typename T>
void write(T x)
{
if(x < 0) {
putchar('-');
x = -x;
}
if(x > 9)
write(x/10);
putchar(x % 10 + '0');
return;
}
int T;
int n,m;
using std::vector;
using std::stack;
vector<int>t[N];
vector<int>A[N];//tree
int cnt,dfn[N],low[N];
bool vis[N];
stack<int>Q;
int fcnt;//block point
inline void tarjan(int u){
dfn[u] = low[u] = ++cnt;
Q.push(u);
for(int i = 0;i < t[u].size();++i){
int v = t[u][i];
if(!dfn[v]){
tarjan(v);
low[u] = std::min(low[u],low[v]);
if(low[v] == dfn[u]){
++ fcnt;
int x;
while(x = Q.top()){
Q.pop();
A[fcnt].push_back(x);
A[x].push_back(fcnt);
std::cout<<"("<<x<<" "<<fcnt<<")"<<std::endl;
if(x == v)
break;
}
std::cout<<"("<<u<<" "<<fcnt<<")"<<std::endl;
A[fcnt].push_back(u);
A[u].push_back(fcnt);
}
}else
low[u] = std::min(low[u],dfn[v]);
}
}
int s[N];
int fa[N];
int tdfn[N],tcnt;
int dep[N];
int F[N][20];
inline void dfs(int u,int f){
std::cout<<u<<std::endl;
fa[u] = f;
tdfn[u] = ++tcnt;
dep[u] = dep[f] + 1;
s[u] = (u <= n) ? s[f] + 1 : s[f];
F[u][0] = f;
for(int i = 1;i <= 19;++i)
F[u][i] = F[F[u][i - 1]][i - 1];
for(int i = 0;i <= 19;++i)
std::cout<<F[u][i]<<" ";
puts("");
for(int i = 0;i < A[u].size();++i){
int v = A[u][i];
if(v == f)continue;
dfs(v,u);
}
}
inline int LCA(int x,int y){
std::cout<<x<<" "<<y<<std::endl;
if(dep[y] > dep[x])std::swap(x,y);
for(int i = 19;i >= 0;--i)
if(dep[F[x][i]] >= dep[y])
x = F[x][i];
if(x == y)return y;
if(dep[x] > dep[y])x = F[x][0];
for(int i = 19;i >= 0;--i){
if(F[x][i] != F[y][i])
x = F[x][i],y = F[y][i];
}
return F[x][0];
}
inline int dis(int x,int y){
int L = LCA(x,y);
std::cout<<"find_dis"<<x<<" "<<y<<" "<<L<<"="<<(s[x] + s[y] - s[L] - s[F[L][0]])<<std::endl;
return (s[x] + s[y] - s[L] - s[F[L][0]]);
}
inline bool cmp(int x,int y){
return tdfn[x] < tdfn[y];
}
inline void clear(){
for(int i = 1;i <= n;++i)
t[i].clear();
for(int i = 1;i <= 2 * n;++i)
A[i].clear(),s[i] = 0,fa[i] = 0,tdfn[i] = 0;
for(int i = 1;i <= n;++i)
dfn[i] = low[i] = 0,s[i] = 0;
fcnt = n,cnt = 0,tcnt = 0;
}
int S[N];
int main(){
scanf("%d",&T);
while(T -- ){
scanf("%d%d",&n,&m);
clear();
for(int i = 1;i <= m;++i){
int x,y;
scanf("%d%d",&x,&y);
t[x].push_back(y);
t[y].push_back(x);
}
for(int i = 1;i <= n;++i)
if(!dfn[i])
tarjan(i);
dfs(1,0);
int q;
scanf("%d",&q);
while(q -- ){
int Si;
scanf("%d",&Si);
for(int i = 1;i <= Si;++i)
scanf("%d",&S[i]);
std::sort(S + 1,S + Si + 1,cmp);
ll sum = 0;
for(int i = 2;i <= Si;++i)
sum += dis(S[i],S[i - 1]);
sum += dis(S[1],S[Si]);
std::cout<<(sum >> 1 - Si)<<std::endl;
}
}
}
最短路 及其 衍生算法
[国家集训队]墨墨的等式
同余最短路。
处理这种\(\sum a_ib_i\)此类问题,考虑线选取\(\min(a_i) = M\),对其在同余系下连边\((i \to (i + a_j)\bmod M)\),考虑对其使用\(spfa\)即可。
枚举\(i\),若其可达,那么其能取到的同余数量为\(\lfloor\frac{x - dis_i}{M}\rfloor + 1\)
生成树 及其 衍生算法
[JSOI2008]最小生成树计数
考虑对于所有的最小生成树的边权值集合相同。
且相同权值的连通块点集相同。
考虑枚举加边,用矩阵树计算当前缩点的生成树数量。
[BJWC2010]严格次小生成树
枚举不在生成树上的边。
考虑在链上找最大的若相同则删除严格次大值即可
[WC2008]游览计划
斯坦纳树版版。
CF888G Xor-MST
考虑使用Boruvka 算法。
实际上我们每次使用小边进行一个一度缩点。
我们发现这是一个可以进行分治的过程,而01tire就是其分治树。
其利用了\(LCP\)越长,异或值越小的特点。