【NOIP2017模拟】道路设计

题目描述

最近市区的交通拥挤不堪,交通局长如果再不能采取措施改善这种糟糕的状况,他就不可避免地要被免职了。市区的道路已经修得够多了,总共n个站点,已经修了n∗(n−1)/2n*(n-1)/2n(n1)/2条道路,也就是任意两个站点都有一条道路连接。但因为道路都很窄,也无法再加宽,所以所有的道路都是单向的。现在,交通局长认为导致交通拥堵的原因之一是存在环路。他决定改变一些道路的方向,使得不存在任何环路。但是,如果改动数量太多,市民们又要打电话投诉了。现在,请你帮帮他,尽量改动最少的道路的方向,使得整个交通网中没有环路。

输入格式

给出一个整数nnn,表示有nnn个点。接下来有一个nnnnnn列的矩阵,如果第iii行第jjj列为1,表示有一条从iiijjj的单向道路,如果为0,表示没有从iiijjj的单向道路。保证所有的数据合法。

输出格式

一个整数,表示最少需要改变的道路条数。

输入样例
4
0 0 0 0 
1 0 1 0 
1 0 0 1 
1 1 0 0
输出样例
1
数据范围

对于所有数据,n≤20n\leq 20n20

题解

依题意,总共有n∗(n−1)/2n*(n-1)/2n(n1)/2条单向边,而且任意两点之间不超过一条边,那么也就是说任意两点之间都有一条单向边。

有一个性质,在上述的有向图中不存在环的充要条件是:n个点的出度或入度互不相同,0到n-1在这些点的出度或入度中都有出现。

证明

首先必须要有一个点出度为0,因为如果所有点的出度不为0,则从任意一个点出发,可以一直走下去,这样必定会形成环。也不能有多个点出度为0,否则这两点之间就没有边,与上述内容不符。所以必须刚好有一个点出度为0,根据上文任意两点之间都有一条单向边,这个点的入度就是n−1n-1n1。删掉这个点以及它的n−1n-1n1条入边,我们就可以得到一个有n−1n-1n1个点、(n−1)∗(n−2)/2(n-1)*(n-2)/2(n1)(n2)/2条边的有向图。根据同样的分析方法,新的图中只有一个点出度为0,那么这个点原来出度就为1。以此类推,一直这样下去,我们可以知道,出度为2、3、4…的点都只有一个。只有这样才能保证图中没有环。

做法

看到n≤20n\leq 20n20,我们很容易想到状压DP。设f(s)f(s)f(s)表示达到sss这个状态需改变边数量的最小值。sss二进制中为1的位表示这个点的入度已经达到最大值,为0的位表示该点还未考虑。比如sss二进制位中有3位为1,则为一的这三个点入度分别为n−1,n−2,n−3n-1,n-2,n-3n1,n2,n3sss并没有规定每个点入度具体为多少,只表示了这个状态中有哪几个点已经达到最大值。

被标记为1的点已经达到了入度的最大值,它们在接下来的图中都不再起作用了,我们可以认为它们已经被删除了。

此时,任选sss二进制中为0的位,设该位为iii,其他的为0的位都向它连有向边。

这里要预处理需要改变方向的边的数量,记为wiw_iwi,则f(s+2i)=min(f(s)+wi)f(s+2^i)=min(f(s)+w_i)f(s+2i)=min(f(s)+wi)

总时间复杂度为O(n2n)O(n2^n)O(n2n)

code

#include<bits/stdc++.h>
using namespace std;
int n,inf=1000000000,a[25][25],w[25][1<<20],f[1<<20];
int main()
{
    scanf("%d",&n);
    for(int i=1;i<=n;i++){
        for(int j=1;j<=n;j++){
            scanf("%d",&a[i][j]);
        }
        a[i][i]=1;
    }
    for(int i=1;i<=n;i++){
        for(int s=(1<<n)-2;s>=0;s--){
            for(int p=1;p<=n;p++){
                if(!((s>>(p-1))&1)){
                    w[i][s]=w[i][s|(1<<p-1)]+(!a[p][i]);
                    break;
                }
            }
        }
    }
    for(int s=1;s<(1<<n);s++){
        f[s]=inf;
        for(int i=1;i<=n;i++){
            if((s>>i-1)&1) f[s]=min(f[s],f[s^(1<<i-1)]+w[i][s^(1<<i-1)]);
        }
    }
    printf("%d",f[(1<<n)-1]);
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值