Hdu 4240 Route Redundancy
题意:
给定一张n个点m条边的有向图,边具有流量限制,给定起点与终点
问整张图从起点到终点的“最大总流量”与“单条路径的最大流量”的比值是多少
思路:
先根据要求建图跑一遍最大流,最大总流量便能求出
“单条路径的最大流量”也就是此时网络中从起点到终点的最长路,搜索即可
用dfs来搜索能减少时间复杂度(别问,问就是用 bfs TLE了)
当前边的流量,就是起点 u -> 终点 v 这条边的反向边的容量,再在搜索过程中更新每个节点即可
int n,m,id,s,t;
struct edge{
ll to,cap,rev;
};
vector<edge>mp[maxn];
ll level[maxn];//顶点到源点的距离标号
ll iter[maxn]; //当前弧,在其之前的边已经没有用了
void add(int from,int to,ll cap){
mp[from].push_back({to,cap,mp[to].size()});
mp[to].push_back({from,0,mp[from].size()-1});
}
void bfs(int s){
memset(level,-1,sizeof(level));
queue<int>q;
level[s]=0;
q.push(s);
while(!q.empty()){
int v=q.front();q.pop();
for(int i=0;i<mp[v].size();i++){
edge &e=mp[v][i];
if(e.cap>0&&level[e.to]<0){
level[e.to]=level[v]+1;
q.push(e.to);
}
}
}
}
int dfs(int v,int t,ll f){
if(v==t)return f;
for(ll &i=iter[v];i<mp[v].size();i++){
edge &e=mp[v][i];
if(e.cap>0&&level[v]<level[e.to]){
int d=dfs(e.to,t,min(f,e.cap));
if(d>0){
e.cap-=d;
mp[e.to][e.rev].cap+=d;
return d;
}
}
}
return 0;
}
ll max_flow(int s,int t){
ll flow=0;
while(1){
bfs(s);
if(level[t]<0)return flow;
memset(iter,0,sizeof(iter));
ll f;
while((f=dfs(s,t,INF))>0)flow+=f;
}
return flow;
}
ll vis[1005];
void dfss(int u){
for(int i=0;i<mp[u].size();i++){
int v=mp[u][i].to;
int flow=min(mp[v][mp[u][i].rev].cap,vis[u]);
if(vis[v]<flow){
vis[v]=flow;
dfss(v);
}
}
}
int main()
{
scanf("%d",&t);
while(t--){
int tt;
scanf("%d%d%d%d%d",&id,&n,&m,&s,&tt);
for(int i=1;i<=m;i++){
int a,b,c;
scanf("%d%d%d",&a,&b,&c);
add(a+1,b+1,c);
}
int ans=max_flow(s+1,tt+1);
memset(vis,0,sizeof(vis));
vis[s+1]=INF;
dfss(s+1);
double res=1.0*vis[tt+1];
res=1.0*ans/res;
printf("%d %.3lf\n",id,res);
for(int i=1;i<=n;i++)mp[i].clear();
}
return 0;
}
Hdu 6582 Path
题意:
给定n个点m条边的一张有向图
Jerry每次都会沿着最短路从1号点走到n号点
现在Tom想断掉某些路,使得Jerry必须走更长的路,断掉一条路径的花费即该路的长度
问Tom的最小花费
思路:
题目只问从起点到终点需要割多少边,那么我们可以先用最短路算法跑出可能的最短路径。
再用跑出来的路线创建网络,再求最小割即可。
重点:
在 s - t 流中,要将 s 和 t 分为两个不同集合 的问题,此时最小割 == 最大流。
小 tip:在复原路径时,我们只要遍历原来的所有边,如果两个端点 a , b之间的距离差 ==那么就说明这条边在最短路上,建边即可。
ll dist[maxn];
void dijkstra(int st){
priority_queue<P> pq;
memset(dist,INF,sizeof dist);
dist[st]=0;pq.push(P(0,st));
while(!pq.empty()){
int v=pq.top().second;pq.pop();
for(auto pd:vv[v])if(dist[v]+pd.cap<dist[pd.to])
dist[pd.to]=dist[v]+pd.cap,pq.push(P(-dist[pd.to],pd.to));
}
}
int main()
{
scanf("%d",&t);
while(t--){
int tt;
scanf("%d%d",&n,&m);
for(int i=1;i<=m;i++){
int a,b,c;scanf("%d%d%d",&a,&b,&c);
add1(a,b,c);
}
dijkstra(1);
for(int i=1;i<=n;i++){
for(int j=0;j<vv[i].size();j++){
edge e=vv[i][j];
if(dist[e.to]==dist[i]+e.cap){ //这里用来建立新的网络
add(i,e.to,e.cap);
}
}
}
printf("%d\n",max_flow(1,n));
for(int i=1;i<=n;i++)mp[i].clear(),vv[i].clear();
}
return 0;
}
Special Fish
题意:
给你n条鱼(n<=100),每条鱼可以攻击其他鱼,每条鱼有一个价值。一次攻击产生的价值 是两条鱼价值的异或。每条鱼只能攻击一次,并且只能被攻击一次。问攻击后产生的最大总价值。
思路:
很简单的建图,边权取负去跑最小费用最大流。
然后我就一直wa了。
!!问题在于,最小费用最大流的前提是——最大流!!!
这道题我们只想最大化价值,如果强行去跑最大流,可能会使价值更小。那么我们需要从出点 i1 指向 终点T 代表这条鱼可以不攻击。
typedef pair<ll,ll> P;
struct edge{
int to;
ll cap,cost;
int rev;
};
vector<edge>mp[maxn];
int V;
ll h[maxn];
ll dist[maxn];
int prevv[maxn],preve[maxn];
void add(int from,int to,ll cap,ll cost){
mp[from].push_back({to,cap,cost,mp[to].size()});
mp[to].push_back({from,0,-cost,mp[from].size()-1});
}
ll MCMF(int s,int t,ll &f){
ll res=0;
V++;
fill(h,h+V,0);
while(f>0){
priority_queue<P,vector<P>,greater<P> >q;
fill(dist,dist+V,INF);
dist[s]=0;
q.push({P(0,s)});
while(!q.empty()){
P p=q.top();q.pop();
int v=p.second;
if(dist[v]<p.first)continue;
for(int i=0;i<mp[v].size();++i){
edge &e=mp[v][i];
if(e.cap>0&&dist[e.to]>dist[v]+e.cost+h[v]- h[e.to]){
dist[e.to]=dist[v]+e.cost+h[v]-h[e.to];
prevv[e.to]=v;
preve[e.to]=i;
q.push(P(dist[e.to],e.to));
}
}
}
if(dist[t]==INF){
f=INF-f;
return res;
}
for(int v=1;v<=V;++v)h[v]+=dist[v];
ll d=f;
for(int v=t;v!=s;v=prevv[v])
d=min(d,mp[prevv[v]][preve[v]].cap);
f-=d;
res+=d*h[t];
for(int v=t;v!=s;v=prevv[v]){
edge &e=mp[prevv[v]][preve[v]];
e.cap-=d;
mp[v][e.rev].cap+=d;
}
}
return res;
}
int n,m;
int s[maxn];
char ss[maxn];
int main()
{
while(~scanf("%d",&n)){
if(!n)break;
for(int i=1;i<=n;i++)scanf("%d",&s[i]);
int S=2*n+1,T=2*n+2;
for(int i=1;i<=n;i++){
scanf("%s",ss);
for(int j=0;j<n;j++){
if(ss[j]=='1'){
add(i,n+j+1,1,-(s[i]^s[j+1]));
}
}
}
for(int i=1;i<=n;i++){
add(S,i,1,0);add(n+i,T,1,0);
add(i,T,1,0);
}
ll ans,f=INF;
V=T;
ans=MCMF(S,T,f);
printf("%lld\n",-ans);
for(int i=1;i<=T;i++)mp[i].clear();
}
return 0;
}
Cordon Bleu
题意:
有n个货物在(xi,yi)的位置,有m个运输员在(xi,yi),餐厅在(x,y)上。一次运输指的是,一个快递员从出发点到货物的位置再到餐厅;一个运输员也可以在到餐厅之后,再出发去货物地点再回到餐厅。
问运输所有货物的最少运输距离(运输过程的 曼哈顿 距离和)
思路:
首先,快递员和货物肯定是属于两个集合,并且集合内不会连边,那么这就是一张二分图。
问题就在于货物有可能是从餐厅出发的。
想法就是我们在快递员的集合中加入(n - 1)个点代表餐厅,那么就保证了至少有一个快递员是从自己这里出发的;
然后去跑一个二分图最大权匹配即可。(边权取负)
ps:
之前都没怎么好好学km,觉得费用流能解决最大权匹配的问题,但是这道题T了。
发现先建费用图,再建二分图会有一定的启发性。
KM板子中使用矩阵存边的,一个代表一个集合,所有直接n,m即可。
const int INF=0x3f3f3f3f;
const int N=4e3;
int n,m,match[N],pre[N];
bool vis[N];
int mp[2004][2004];
int val1[N],val2[N],slack[N];
void bfs(int p)
{
memset(pre,0,sizeof pre);
memset(slack,INF,sizeof slack);
match[0]=p;
int x=0,nex=0;
do{
vis[x]=true;
int y=match[x],d=INF;
for(int i=1;i<=m;i++)
{
if(!vis[i])
{
if(slack[i]>val1[y]+val2[i]-mp[y][i])
{
slack[i]=val1[y]+val2[i]-mp[y][i];
pre[i]=x;
}
if(slack[i]<d)
{
d=slack[i];
nex=i;
}
}
}
for(int i=0;i<=m;i++)
{
if(vis[i])
val1[match[i]]-=d,val2[i]+=d;
else
slack[i]-=d;
}
x=nex;
}while(match[x]);
while(x)
{
match[x]=match[pre[x]];
x=pre[x];
}
}
int KM()
{
memset(match,0,sizeof match);
memset(val1,0,sizeof val1);
memset(val2,0,sizeof val2);
for(int i=1;i<=n;i++)
{
memset(vis,false,sizeof vis);
bfs(i);
}
int res=0;
for(int i=1;i<=m;i++)
res+=mp[match[i]][i];
return res;
}
int x1[maxn],y1[maxn];
int x2[maxn],y2[maxn];
int x,y;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++)
scanf("%d%d",&x1[i],&y1[i]);
for(int i=1;i<=m;i++)
scanf("%d%d",&x2[i],&y2[i]);
scanf("%d%d",&x,&y);
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
ll dis=abs(x2[i]-x1[j])+abs(y2[i]-y1[j])+
abs(x-x1[j])+abs(y-y1[j]);
mp[j][i]=-dis;
}
}
for(int i=1;i<n;i++){
for(int j=1;j<=n;j++){
ll dis=abs(x-x1[j])+abs(y-y1[j]);
mp[j][m+i]=-2*dis;
}
}
m=n+m-1;
printf("%lld\n",-KM());
return 0;
}