首先,我们发现以卡车的路线为边,仓库为点,构成了一张图。
然后我们又看到需要花最小代价使公司和零售商不连通。
那么我们很容易得到此题的模型:
以公司为源点,零售商为汇点,求出图中的最小割大小、最小割边数。
第一问,直接上著名的定理:
最大流 = 最小割 \text{最大流}=\text{最小割} 最大流=最小割
任何学过网络流的人都应该知道吧
于是直接上 D i n i c Dinic Dinic秒掉第一问。
然而我们发现第二问有些棘手:似乎没有快速求最小割边集的算法?
别急,我们依然从那个著名的定理入手。
最大流 = 最小割 \text{最大流}=\text{最小割} 最大流=最小割
显而易见的是,当最小割中所有边权均为 1 1 1时,最小割的大小等于最小割的边数。
但此题边权并不是 1 1 1,所以感性理解一下,要想办法把 1 1 1在不影响第一问答案的情况下附加到边上。
而附加信息其实有套路:
当我们使用 p b _ d s pb\_ds pb_ds中的平衡树时,为了实现重复元素的功能,我们将数据存在高位,而低位存一个时间戳。
于是新技能
g
e
t
get
get:将原数据
x
x
x乘一个常数
P
P
P,再加上附加数据
y
y
y,就得到新数据
x
P
+
y
xP+y
xP+y。只要
P
>
y
P>y
P>y,原数据就与附加数据8848互不干扰。之后拿
P
P
P整除、对
P
P
P取模就可以分离信息。由于附加信息较小,不会对原来的答案造成影响。
于是此题第二问也可以秒掉了。。。
代码 (自我感觉写得挺好看的,喜欢可以拿去)
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#define int long long//注意边数乘边权会爆int
using std::queue;
const int N=1005,M=2005;
int head[N],level[N],cur[N];
struct Edge
{
int next,to,c;
};
Edge E[M<<1];
void __add(int u,int v,int c)
{
static int tot=-1;
E[++tot].next=head[u];
E[tot].to=v;
E[tot].c=c;
head[u]=tot;
}
bool bfs(int s,int t)
{
memset(level,0x00,sizeof(level));
queue<int> q;
q.push(s);
level[s]=1;
while(!q.empty())
{
int u=q.front();
q.pop();
for(int i=head[u];~i;i=E[i].next)
{
int v=E[i].to;
if(level[v]||E[i].c==0)continue;
level[v]=level[u]+1;
q.push(v);
}
}
return level[t];
}
int dfs(int u,int t,int flow)
{
if(u==t||flow==0)return flow;
int now=flow;
for(int i=cur[u];~i;cur[u]=i=E[i].next)
{
int v=E[i].to;
if(level[v]!=level[u]+1||E[i].c==0)continue;
int f=dfs(v,t,std::min(now,E[i].c));
now-=f;
E[i].c-=f;
E[i^1].c+=f;
if(now==0)break;
}
return flow-now;
}
int dinic(int s,int t)
{
int ans=0;
while(bfs(s,t))
{
memcpy(cur,head,sizeof(cur));
ans+=dfs(s,t,0x7fffffffffffffff);//无穷大别开小了,会死循环
}
return ans;
}
signed main()
{
memset(head,0xff,sizeof(head));
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=0;i<m;++i)
{
int u,v,c;
scanf("%lld%lld%lld",&u,&v,&c);
__add(u,v,c*(m*2+1)+1);
__add(v,u,0);
}
int res=dinic(1,n);
printf("%lld %lld",res/(m*2+1),res%(m*2+1));
}