相当于重点练了一下最小割的知识
------------------------------------------------
2229: [Zjoi2011]最小割
首先有个结论是这些点的最小割最多有n-1个(并不会证明)
然后就可以分治+最小割解决了
分治过程运用递归,对于每一层选择头(s)尾(t)跑最大流得出最小割,然后根据割集分出S集和T集继续分治即可
#include<cstdio>
#include<vector>
#include<cstring>
#include<queue>
#include<algorithm>
#define N 155
#define M 100005
#define INF 1e9
using namespace std;
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
struct edge{int to,f;}e[M];
vector<int>G[N];
int n,m,s,t,cnt;
int a[N],d[N],cur[N],mark[N],pr[N],ans[N][N];
void add(int x,int y,int v)
{
e[cnt].to=y,e[cnt].f=v;
G[x].push_back(cnt++);
}
bool bfs()
{
queue<int>Q;
memset(d,-1,sizeof(d));
d[s]=0,Q.push(s);
while(!Q.empty())
{
int x=Q.front();Q.pop();
if(x==t) return 1;
for(int i=0;i<G[x].size();++i)
{
edge tmp=e[G[x][i]];
if(d[tmp.to]==-1&&tmp.f)
{
Q.push(tmp.to);
d[tmp.to]=d[x]+1;
}
}
}
return 0;
}
int dfs(int x,int a)
{
if(x==t) return a;
int flow=0,f;
for(int i=0;i<G[x].size();++i)
{
int y=G[x][i];
if(e[y].f&&d[e[y].to]==d[x]+1)
{
f=dfs(e[y].to,min(a,e[y].f));
e[y].f-=f;
e[y^1].f+=f;
flow+=f;
a-=f;
if(a==0) break;
}
}
if(!flow) d[x]=-1;
return flow;
}
void search(int x)
{
mark[x]=1;
for(int i=0;i<G[x].size();++i)
if(e[G[x][i]].f&&!mark[e[G[x][i]].to]) search(e[G[x][i]].to);
}
void solve(int l,int r)
{
if(l==r) return;
for(int i=0;i<cnt;i+=2) e[i].f=e[i^1].f=(e[i].f+e[i^1].f)/2;
s=a[l],t=a[r];
int cs=0;
while(bfs()){cs+=dfs(s,INF);}
memset(mark,0,sizeof(mark));
search(s);
for(int i=1;i<=n;++i)
for(int j=1;j<=n&&mark[i];++j)
if(!mark[j]) ans[i][j]=ans[j][i]=min(cs,ans[i][j]);
int L=l,R=r;
for(int i=l;i<=r;++i)
if(mark[a[i]]) pr[L++]=a[i];
else pr[R--]=a[i];
for(int i=l;i<=r;++i) a[i]=pr[i];
solve(l,L-1),solve(R+1,r);
}
int main(void)
{
int test=read();
while(test--)
{
n=read(),m=read();
memset(ans,127,sizeof(ans));
memset(e,0,sizeof(e));
cnt=0;
for(int i=1;i<=n;++i) a[i]=i,G[i].clear();
for(int i=1,x,y,z;i<=m;++i)
{
x=read(),y=read(),z=read();
add(x,y,z);
add(y,x,z);
}
solve(1,n);
int que=read(),res;
while(que--)
{
res=0;
int x=read();
for(int i=1;i<=n;++i)
for(int j=i+1;j<=n;++j) if(ans[i][j]<=x) ++res;
printf("%d\n",res);
}
printf("\n");
}
return 0;
}
2521: [Shoi2010]最小生成树
题目中的操作其实等价于将选择一条边并将它的边权加上1
根据克鲁斯卡尔算法求解最小生成树的过程与思想,想到如果将比lab边权小的边全部用放在一个并查集里,如果此时lab边两个端点如果不连通才一定会把lab边加入最小生成树
所以一种可行的做法是把lab边两端分别作为源点和汇点
将边按权值排序后比lab边小的加入图:连接容量为a[lab].v-a[i].v+1的边(记得连两边,即两边分别连或连双向边)
跑最大流得出最小割
#include<cstdio>
#include<algorithm>
#include<queue>
#include<cstring>
#define INF 1e9
#define N 501
#define M 805
#define RG register
using namespace std;
struct Edge{int x,y,z;}a[M];
struct edge{int next,to,v;}e[M*4];
int n,m,id,cnt,S,T;
int head[N],d[N];
int read()
{
int a=0,f=1;char c=getchar();
while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();}
while(c>='0'&&c<='9'){a=a*10+c-'0';c=getchar();}
return a*f;
}
void insert(int x,int y,int v)
{
e[++cnt].next=head[x],e[cnt].to=y,e[cnt].v=v;
head[x]=cnt;
}
int bfs()
{
memset(d,-1,sizeof(d));
queue<int>q;
q.push(S),d[S]=0;
while(!q.empty())
{
int x=q.front();q.pop();
if(x==T) return 1;
for(RG int i=head[x];i;i=e[i].next)
if(e[i].v&&d[e[i].to]==-1)
{
d[e[i].to]=d[x]+1;
q.push(e[i].to);
}
}
return 0;
}
int dfs(int x,int a)
{
if(x==T) return a;
int flow=0,f;
for(RG int i=head[x];i;i=e[i].next)
if(e[i].v&&d[e[i].to]==d[x]+1)
{
f=dfs(e[i].to,min(a,e[i].v));
e[i].v-=f;
e[i^1].v+=f;
flow+=f;
a-=f;
if(!a) break;
}
if(!flow) d[x]=-1;
return flow;
}
int dinic()
{
int res=0;
while(bfs())
res+=dfs(S,INF);
return res;
}
int main(void)
{
n=read(),m=read(),id=read();
for(RG int i=1,x,y,z;i<=m;++i)
{
a[i].x=read(),a[i].y=read(),a[i].z=read();
if(i==id) S=a[i].x,T=a[i].y;
}
cnt=1;
for(RG int i=1;i<=m;++i)
if(i!=id&&a[i].z<=a[id].z)
{
insert(a[i].x,a[i].y,a[id].z-a[i].z+1);
insert(a[i].y,a[i].x,a[id].z-a[i].z+1);
}
printf("%d",dinic());
return 0;
}