JZOJ 3749. 【Srm590】Fox And City(fox)

本文介绍了一个关于构建最优道路网络的问题,旨在通过调整城市间的道路连接,使得各城市居民与首都的距离达到期望值,从而减少不满情绪。文章详细阐述了解决方案,包括如何构造满足特定条件的distance数组,以及如何利用最小割算法寻找最佳方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Description

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。

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

Input

多组数据,读入到文件结束。

每组数据第一行,一个整数n——夏尔的国家内的城市数量。

接下来n 行,描述字符矩阵linked。第i 行第j 列的字符为linked[i][j]。

字符矩阵之后还有单独一行的n 个整数,为期望距离数组want。

Output

对于每组数据,输出单独一行一个整数——夏尔的最优方案中新道路都落成之后市民们新增的不爽值的最小值。

Sample Input

3

NYN

YNY

NYN

0 1 1

4

NYNN

YNYN

NYNY

NNYN

0 3 3 3

6

NYNNNY

YNYNNN

NYNYNN

NNYNYN

NNNYNY

YNNNYN

0 2 2 2 2 2

3

NYY

YNN

YNN

0 0 0

6

NYNNNN

YNYNNN

NYNYYY

NNYNYY

NNYYNY

NNYYYN

0 1 2 3 0 3

6

NYNNNN

YNYNNN

NYNYYY

NNYNYY

NNYYNY

NNYYYN

0 1 2 4 0 4

11

NYNYYYYYYYY

YNYNNYYNYYY

NYNNNYYNYYN

YNNNYYYYYYY

YNNYNYYYNYY

YYYYYNNYYNY

YYYYYNNNYYY

YNNYYYNNNYY

YYYYNYYNNNY

YYYYYNYYNNY

YYNYYYYYYYN

0 1 2 0 0 5 1 3 0 2 3

Sample Output

0

5

2

2

3

6

28

【样例解释】

第一组数据:夏尔可以建造一条连接城市0 和城市2 的道路。然后有real[1] = 1; real[2] = 1,并且总不爽值为零。

第二组数据:最优方案是不建造新道路。然后总不爽值将会为(3-1)^2+(3-2)^2+ (3-3)^2 = 5。

第三组数据:最优方案中的一种是,在城市1 和城市3 之间建造一条新道路。

Data Constraint

对于30% 的数据,能够新增的不同的道路的数量不超过16 条,数据组数<=10。

对于100% 的数据满足:

   数据组<=15。

    2<= n <=40; 0 <=want[i] < n;want[0] = 0。

    对于所有的i 和j,有linked[i][j] = linked[j][i]

    对于所有的i,有linked[i][i] =’ N’

分析

令 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),但实际上远达不到这样的复杂度,因此可以通过本题。
【时空复杂度】 时间复杂度:O(n^7) 空间复杂度:O(n^3)

代码

#include <bits/stdc++.h>

#define N 30000
#define M 500000

#define sqr(x) ((x) * (x))

const int INF = 1e9;

struct NOTE
{
    int to,c,next;
}e[M];

int n;
int want[N];

int ans;

int dis[100][100];

void init()
{
    for (int i = 0; i <= M; i++)
        e[i].next = 0;
    ans = 0;
    std::string way;
    for (int i = 1; i <= n; i++)
    {
        std::cin>>way;
        for (int j = 0; j < n; j++)
            if (way[j] == 'Y')
                dis[i][j + 1] = 1;
            else dis[i][j + 1] = INF;
    }
    for (int i = 0; i < n; i++)
        scanf("%d",&want[i]);
}

void floyed()
{
    for (int k = 1; k <= n; k++)
        for (int i = 1; i <= n; i++)
            for (int j = 1; j <= n; j++)
                dis[i][j] = std::min(dis[i][j],dis[i][k] + dis[k][j]);
}

int cnt;
int s,t;
int next[M];

void add(int x,int y,int c = INF)
{
    e[++cnt].to = y;
    e[cnt].next = next[x];
    e[cnt].c = c;
    next[x] = cnt;

    e[++cnt].to = x;
    e[cnt].next = next[y];
    e[cnt].c = 0;
    next[y] = cnt;
}

int disf[N];
int vis[N];

bool bfs()
{
    memset(disf,60,sizeof(disf));
    memset(vis,0,sizeof(vis));
    std::queue<int> Q;
    Q.push(s);
    disf[s] = 0;
    while (!Q.empty())
    {
        int u = Q.front();
        Q.pop();
        for (int i = next[u]; i; i = e[i].next)
        {
            if (disf[e[i].to] > INF && e[i].c)
            {
                disf[e[i].to] = disf[u] + 1;
                if (e[i].to == t)
                    return 1;
                Q.push(e[i].to);
            }
        }
    }
    return 0;
}

int cur[M];

int dfs(int x,int maxf)
{
    if (x == t || !maxf)
        return maxf;
    int ret = 0;
    for (int &i = cur[x]; i; i = e[i].next)
    {
        if (e[i].c && disf[e[i].to] == disf[x] + 1)
        {
            int f = dfs(e[i].to,std::min(e[i].c,maxf - ret));
            ret += f;
            e[i].c -= f;
            e[i ^ 1].c += f;
            if (ret == maxf)
                break;
        }
    }
    return ret;
}

void dinic()
{
    while (bfs())
    {
        for (int i = s; i <= t; i++)
            cur[i] = next[i];
        ans += dfs(s,INF);
    }
}

int ml[N];

int getId(int x,int y)
{
    return (x - 1) * (n + 1) + y;
}

void build()
{
    for (int i = 2; i <= n; i++)
        ml[i] = dis[1][i];
    cnt = 1;
    s = 0;
    t = N - 1;
    memset(next,0,sizeof(next));
    memset(cur,0,sizeof(cur));
    for (int i = 2; i <= n; i++)
    {
        add(s,getId(i,1));
        add(getId(i,ml[i] + 1),t);
        for (int j = 1; j <= ml[i]; j++)
        {
            add(getId(i,j),getId(i,j + 1),sqr(want[i - 1] - j));
            for (int k = 2; k <= n; k++)
            {
                if (i != k && dis[i][k] == 1 && j > 1)
                {
                    add(getId(i,j),getId(k,j - 1));
                }
            }
        }
    }
}

void work()
{
    dinic();
}

void print()
{
    printf("%d\n",ans);
}

int main()
{
    freopen("fox.in","r",stdin);
    freopen("fox.out","w",stdout); 
    while (~scanf("%d",&n))
    {
        init();
        floyed();
        build();
        work();
        print();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值