【Srm590】Fox And City jzoj 3749最小割

17 篇文章 0 订阅
13 篇文章 0 订阅

题目

A long time ago, 有一个国家有n 座从0 到n-1编号的城市。城市0 是首都。国家道路网络形成了一个无向连通图。换句话说:某些对城市被双向通行的道路所连接。

对于每座城市,可以从城市出发经过一系列连续的道路到达首都。(当两条道路需要在城市外相交时,相交处总是会有一座桥梁,因此城市外并没有路口。)

你会获得一个用于描述道路网络的字符矩阵linked。对于每个i 和j,当城市i 和城市j 已由一条道路直接连通时linked[i][j] 为‘Y’ ,否则为‘N’ 。

两个城市间的距离为从一个城市到达另一个城市所需通过的道路的最小数目。居住在首都以外的市民通常都对他们与首都之间的距离不爽。因此你会获得一个n 个元素的数组want。对于每个i,want[i] 是城市i 与首都之间的市民

期望距离。

福克斯· 夏尔正在负责建造新的道路。每个新道路必须双向且连接两座城市。一旦所有的新道路落成,市民们会计算他们对最终道路网络的不爽值:

对于每个i:令real[i] 为从城市i 到达首都的新距离。那么城市i 的市民增加的对国家的不爽值为(want[i]-real[i])^2。

计算并回答夏尔建造一些(可能是零)新道路之后新增不爽值之和的最小值。

分析

因为看到n只有40,所以一开始就在想如何利用网络流(万物皆可网络流)……

正解在此(太长懒的打,copy自题解):

令 distance[i]为给出的无向图中节点 i 到节点 0 的最短路的长度。显然有:
1,distance[0]=0。
2,对于任意一条无向边(i,j),有|distance[i]-distance[j]|<=1。
3,对于任意一节点点 i(i≠0),至少存在一个节点 j 使得 distance[i]=distance[j]+1。
不难发现,这三条是 distance 合法的充分必要条件。根据这一点,可以发现,一个
distance 数组只要满足以下三个条件,就可以以在原图的基础上条件若干条无向边得到:
1,有且仅有一个节点 0 的 distance 值为 0。
2,对于原图的任意一条无向边(i,j),有|distance[i]-distance[j]|<=1。
3,对于任意整数 1≤d≤max(distance[i]),存在至少一个节点 i 使得 distance[i]=d。
这三个条件的必要性显然,下面证明它们的充分性。根据 distance,可以按照如下过程
构造出无向图。
从节点 0 开始,令 distance[0]=0。然后令 p 从 1 到 max(distance[i]),对于每个
distance[i]=p-1 的节点 i,向任意一个满足 distance[j]=p-1 的节点 j 连一条无向边。最后将原
图的边加入。这个构造法的正确性显然,不做证明。
更进一步,可以发现,只要满足三个条件的前两个条件,就必然满足第三个条件(过程
见原题解)。因此一个合法的 distance 序列只要满足以下两个条件:
1,有且仅有一个节点 0 的 distance 值为 0。
2,对于原图的任意一条无向边(i,j),有|distance[i]-distance[j]|<=1。
在满足两个条件的序列中,找到∑(distance[i]-want[i])^2 最小的序列即为所求。
求这个序列,可以使用最小割算法。
建立源点S,汇点T。将原图中每个节点i拆成n个点,point(i,k)(0≤k≤n-1),令节点point(i,k)
表示 distance[i]<=k。若最小割中,节点 point(i,k)在 S 集中,代表 distance[i]<=k 为 false,
否则为 true。构图方法如下:
1,从 S 向 point(i,0)(1≤i≤n-1)连一条容量为正无穷的边。除节点 0 外的所有节点权值均
不可能为 0。
2,从每个 point(i,n-1)(0≤i≤n-1)向 T 连一条容量为正无穷的边。任意节点的 distance 值
均不超过 n-1。
3,从 point(i,k)向 point(i,k+1)一条容量为((k+1)-want[i])^2(若 i=0,这条边的容量为正无
穷)。可以用这条边容量的代价将 point(i,k)和 point(i,k+1)分到两个不同的集合。
4,显然,若 point(i,k)为 false,point(i,k-1)必然也为 false,因此从每个 point(i,k)向
point(i,k-1)连一条权值为正无穷的边。
5,对于原图中的每条边(i,j),因为|distance[i]-distance[j]|为 0。所以若 point(i,k)为 false,
必然有 point(j,k-1)为 false。所以从每个 point(i,k)向 point(j,k-1)连一条容量为正无穷的边,
从每个 point(j,k)向 point(i,k-1)连一条容量为正无穷的边。
最后求出最小割即为答案。
这样构图的点数是 O(n^2),边数为 O(n^3)。如果使用 dinic 求最小割,时间复杂度为
O(n^7),但实际上远达不到这样的复杂度,因此可以通过本题。

ps:这道题的建模真TM神奇,可以好好记录,学习一波

code

#include<iostream>
#include<cstring>
#include<cstdio>
#include<cmath>
#include<queue>

using namespace std;

const int maxn=100;
const int INF=1e9;

struct arr{
    int x,y,w,next;
}edge[1000000];

int ls[maxn*maxn],cur[maxn*maxn];
int f[1000000];
int edge_m=1;
int n,m,s,t;
int ans,num=0;

void add(int x,int y,int w)
{
    edge[++edge_m]=(arr){x,y,w,ls[x]}; cur[y]=ls[x]=edge_m; f[edge_m]=w;
    edge[++edge_m]=(arr){y,x,w,ls[y]}; cur[y]=ls[y]=edge_m; f[edge_m]=0;
}

int dis[maxn*maxn];

bool bfs()
{
    memset(dis,-1,sizeof(dis));
    queue<int> q;
    q.push(s);
    dis[s]=0;
    do{
        int x=q.front();
        q.pop();
        for (int i=ls[x];i!=0;i=edge[i].next)
        {
            if ((f[i])&&(dis[edge[i].y]==-1))
            {
                dis[edge[i].y]=dis[x]+1;
                q.push(edge[i].y);
                if (edge[i].y==t) return true;
            }
        }
    }while (!q.empty());
    return false;
}

int find(int x,int min_)
{
    if (x==t) return min_;
    int rec=min_;
    for (int &i=cur[x];i!=0;i=edge[i].next)
    {
        if ((f[i])&&(dis[edge[i].y]==dis[x]+1))
        {
            int k=find(edge[i].y,min(min_,f[i]));
            f[i]-=k;
            f[i^1]+=k;
            rec-=k;
            if (rec==0) return min_;
        }
    }
    if (rec==min_) dis[x]=-1;
    return min_-rec;
}

int dinic()
{
    ans=0;
    while (bfs())
    {
        for (int i=0;i<=t;i++) cur[i]=ls[i];
        ans+=find(s,INF);
    }
}

int a[maxn][maxn],b[maxn];

int main()
{
    freopen("fox.in","r",stdin);
    freopen("fox.out","w",stdout); 
    while (scanf("%d",&n)!=EOF)
    {
        memset(edge,0,sizeof(edge));
        memset(ls,0,sizeof(ls));
        memset(f,0,sizeof(f));
        memset(a,0,sizeof(a));
        memset(b,0,sizeof(b));
        edge_m=1;
        for (int i=1;i<=n;i++)
            for (int j=1;j<=n;j++)
            {
                char x;
                cin>>x;
                if (x=='Y') a[i][j]=1;
            }
        s=0; t=n*n+2;

        for (int i=1;i<=n;i++)
            scanf("%d",&b[i]);
        add(s,1,0);
        for (int i=2;i<=n;i++)
            add(s,(i-1)*n+1,INF);
        for (int i=1;i<=n;i++)
            add(i*n,t,INF);
        for (int i=1;i<n;i++)
            add(i,i+1,INF);
        for (int i=2;i<=n;i++)
        {
            for (int j=1;j<n;j++)
                add((i-1)*n+j,(i-1)*n+j+1,(j-b[i])*(j-b[i]));
        }
        for (int i=1;i<=n;i++)
        {
            for (int j=1;j<=n;j++)
            {
                if (a[i][j]) 
                    for (int k=2;k<=n;k++)
                        add((i-1)*n+k,(j-1)*n+k-1,INF);
            }
        }
        dinic();
        printf("%d\n",ans);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值