您的位置:首页 > 编程语言 > C语言/C++

BZOJ 2229 [Zjoi2011] 最小割

2017-05-04 14:56 453 查看

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

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

最小割+二分+思路~

神奇的性质:所有最小割都不是完全独立的,总共有n-1种最小割。

所以我们直接(类似)二分求解,每次求最小割,并由最小割将答案分为两个部分,两个部分之间的点互相之间的最小割由此更新,并分别在两边求最小割,重复上述过程,直至所有点之间的最小割都求解完毕,直接暴力回答询问即可。

注意:

1.区分a[i]和i,i只是用来区分标号,a[i]的值才是标号;

2.在更新中,i和j都必须从1到n,因为可能前面的b[i]==0而后面的b[i]==1;

3.区分i和1!

#include<cstdio>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int t,n,m,a[151],fi[151],w[60005],v[60005],ne[60005],cnt,x,y,val,dis[151],S,T,f[151][151],aa[151],ans;
bool b[151];

queue<int> q;

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<<1)+(x<<3)+ch-'0';ch=getchar();}
return x*f;
}

void add(int u,int vv,int val)
{
w[++cnt]=vv;ne[cnt]=fi[u];fi[u]=cnt;v[cnt]=val;
w[++cnt]=u;ne[cnt]=fi[vv];fi[vv]=cnt;v[cnt]=val;
}

bool bfs()
{
memset(dis,-1,sizeof(dis));
q.push(S);dis[S]=0;
while(!q.empty())
{
int k=q.front();q.pop();
for(int i=fi[k];i;i=ne[i])
if(dis[w[i]]==-1 && v[i])
{
dis[w[i]]=dis[k]+1;
q.push(w[i]);
}
}
return dis[T]!=-1;
}

int findd(int u,int vv)
{
if(u==T) return vv;
int kkz,tot=0;
for(int i=fi[u];i;i=ne[i])
if(dis[w[i]]==dis[u]+1 && v[i] && (kkz=findd(w[i],min(v[i],vv-tot))))
{
tot+=kkz;v[i]-=kkz;v[i^1]+=kkz;
if(tot==vv) return tot;
}
if(!tot) dis[u]=-1;
return tot;
}

void dfs(int u)
{
b[u]=1;
for(int i=fi[u];i;i=ne[i])
if(!b[w[i]] && v[i]) dfs(w[i]);
}

void sol(int l,int r)
{
if(l==r) return;
for(int i=2;i<=cnt;i+=2) v[i]=v[i^1]=v[i]+v[i^1]>>1;
int k=0;S=a[l];T=a[r];
while(bfs()) k+=findd(S,999999999);
memset(b,0,sizeof(b));
dfs(S);
for(int i=1;i<=n;i++)
if(b[i])
for(int j=1;j<=n;j++)
if(!b[j]) f[i][j]=f[j][i]=min(f[i][j],k);
int L=l,R=r;
for(int i=l;i<=r;i++)
if(b[a[i]]) aa[L++]=a[i];
else aa[R--]=a[i];
for(int i=l;i<=r;i++) a[i]=aa[i];
sol(l,L-1);sol(R+1,r);
}

int main()
{
t=read();
while(t--)
{
n=read();m=read();cnt=1;
memset(fi,0,sizeof(fi));
memset(f,127,sizeof(f));
for(int i=1;i<=n;i++) a[i]=i;
while(m--) x=read(),y=read(),val=read(),add(x,y,val);
sol(1,n);
m=read();
while(m--)
{
x=read();ans=0;
for(int i=1;i<n;i++)
for(int j=i+1;j<=n;j++)
if(f[i][j]<=x) ans++;
printf("%d\n",ans);
}
puts("");
}
return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ 最小割 二分 思路