最小割
Description
小白在图论课上学到了一个新的概念——最小割,下课后小白在笔记本上写下了如下这段话: “对于一个图,某个对图中结点的划分将图中所有结点分成两个部分,如果结点s,t不在同一个部分中,则称这个划分是关于s,t的割。 对于带权图来说,将所有顶点处在不同部分的边的权值相加所得到的值定义为这个割的容量,而s,t的最小割指的是在关于s,t的割中容量最小的割” 现给定一张无向图,小白有若干个形如“图中有多少对点它们的最小割的容量不超过x呢”的疑问,小蓝虽然很想回答这些问题,但小蓝最近忙着挖木块,于是作为仍然是小蓝的好友,你又有任务了。
Input
输入文件第一行有且只有一个正整数T,表示测试数据的组数。 对于每组测试数据, 第一行包含两个整数n,m,表示图的点数和边数。 下面m行,每行3个正整数u,v,c(1<=u,v<=n,0<=c<=106),表示有一条权为c的无向边(u,v) 接下来一行,包含一个整数q,表示询问的个数 下面q行,每行一个整数x,其含义同题目描述。
Output
对于每组测试数据,输出应包括q行,第i行表示第i个问题的答案。对于点对(p,q)和(q,p),只统计一次(见样例)。
两组测试数据之间用空行隔开。
Sample Input
1
5 0
1
0
Sample Output
10
【数据范围】
对于100%的数据 T<=10,n<=150,m<=3000,q<=30,x在32位有符号整数类型范围内。
图中两个点之间可能有多条边
HINT
Source
Day1
以为很难写实际上写起来十分简单的一个东西……
思路:
考虑最小割的性质。
可以发现,对于任意一对源汇(
s,t
s
,
t
)之间的最小割,如果这个割将图分成了
S
S
、两个点集,那么以任意
(u∈S,v∈T)
(
u
∈
S
,
v
∈
T
)
的点对
(u,v)
(
u
,
v
)
作为源和汇,得到的最小割小于等于
(s,t)
(
s
,
t
)
之间的最小割。
证明很简单,
(s,t)
(
s
,
t
)
的最小割绝对是
(u,v)
(
u
,
v
)
的一个合法的割,只是不一定最小。
那么设
mincut(s,t)
m
i
n
c
u
t
(
s
,
t
)
为以
(s,t)
(
s
,
t
)
为源和汇时的最小割。
任取三个点
a,b,c
a
,
b
,
c
,设
min(mincut(a,b),mincut(b,c),mincut(c,a))=mincut(a,b)
min
(
m
i
n
c
u
t
(
a
,
b
)
,
m
i
n
c
u
t
(
b
,
c
)
,
m
i
n
c
u
t
(
c
,
a
)
)
=
m
i
n
c
u
t
(
a
,
b
)
。
那么假设
c
c
在所在的
T
T
集合内,根据上面的定理有。
又根据定义有
mincut(a,b)≤mincut(a,c)
m
i
n
c
u
t
(
a
,
b
)
≤
m
i
n
c
u
t
(
a
,
c
)
,则
mincut(a,b)=mincut(a,c)
m
i
n
c
u
t
(
a
,
b
)
=
m
i
n
c
u
t
(
a
,
c
)
。
于是得到结论:
任取三个点互相做最小割,其中必有一个点到另外两个点的最小割大小相等,剩下的那个的值则会大于另两个值。
于是就可以分治了!
对于一个点集,随机一对源和汇,对整张图跑一遍最小割。
此时可以令
S
S
和之间的节点对的答案对当前最小割取
min
min
值。
然后将当前点集分成在
S
S
集内的和在集内的两个部分,分别递归下去重复上述过程更新即可~
虽然这题并不需要建出最小割树,然而还是提一下:
最小割树,就是将刚才分治过程中每次对递归出去的两个点集之间连一条边所得到的树。
树上任意两点
(u,v)
(
u
,
v
)
的最小割等于树上
u
u
到之间的路径。
// luogu-judger-enable-o2
#include<cstdio>
#include<cstring>
#include<cstdlib>
using namespace std;
inline int read()
{
int x=0;char ch=getchar();
while(ch<'0' || '9'<ch)ch=getchar();
while('0'<=ch && ch<='9')x=x*10+(ch^48),ch=getchar();
return x;
}
inline int min(int a,int b){return a<b?a:b;}
const int N=159;
const int M=3009;
const int Inf=1e9+7;
int n,m,s,t;
int o[N],p[N];
int ans[N][N];
namespace flow
{
int to[M<<1],nxt[M<<1],w[M<<1],beg[N],tot=1;
int dis[N],q[N];
inline void init()
{
tot=1;memset(beg,0,sizeof(beg));
}
inline void add(int u,int v,int c)
{
to[++tot]=v;
nxt[tot]=beg[u];
beg[u]=tot;
w[tot]=c;
}
inline bool bfs()
{
for(int i=1;i<=n;i++)
dis[i]=Inf;
dis[q[1]=s]=0;
for(int l=1,r=1,u=q[l];l<=r;u=q[++l])
for(int i=beg[u];i;i=nxt[i])
if(w[i]>0 && dis[to[i]]==Inf)
dis[to[i]]=dis[u]+1,q[++r]=to[i];
return dis[t]!=Inf;
}
inline int dfs(int u,int flow)
{
if(u==t || !flow)return flow;
int cost=0;
for(int i=beg[u],f;i;i=nxt[i])
if(w[i]>0 && dis[u]+1==dis[to[i]])
{
f=dfs(to[i],min(w[i],flow-cost));
w[i]-=f;w[i^1]+=f;cost+=f;
if(cost==flow)break;
}
if(!cost)dis[u]=-1;
return cost;
}
inline int dinic()
{
int ret=0;
while(bfs())
ret+=dfs(s,Inf);
return ret;
}
}
using namespace flow;
inline void work(int l,int r)
{
if(l==r)return;
for(int i=2;i<=tot;i+=2)
w[i]=w[i^1]=(w[i]+w[i^1])>>1;
s=o[l],t=o[r];
int f=dinic();
for(int i=1;i<=n;i++)
if(dis[i]<Inf)
for(int j=1;j<=n;j++)
if(dis[j]==Inf)
ans[i][j]=ans[j][i]=min(ans[i][j],f);
int lp=l-1,rp=r+1;
for(int i=l;i<=r;i++)
if(dis[o[i]]<Inf)
p[++lp]=o[i];
else
p[--rp]=o[i];
for(int i=l;i<=r;i++)
o[i]=p[i];
work(l,lp);work(rp,r);
}
int mina()
{
init();
n=read();m=read();
for(int i=1;i<=n;i++)
o[i]=i;
for(int i=1,u,v,c;i<=m;i++)
{
u=read();v=read();c=read();
add(u,v,c);add(v,u,c);
}
memset(ans,127,sizeof(ans));
work(1,n);
int q=read();
while(q--)
{
int x=read(),ret=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
if(ans[i][j]<=x)
ret++;
printf("%d\n",ret);
}
puts("");
return 0;
}
int main()
{
for(int T=read();T;T--)
mina();
return 0;
}