【NOIP2017模拟】道路设计

题目描述

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

输入格式

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

输出格式

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

输入样例

4
0 0 0 0 
1 0 1 0 
1 0 0 1 
1 1 0 0

输出样例

1

数据范围

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

题解

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

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

证明

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

做法

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

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

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

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

总时间复杂度为 O ( n 2 n ) 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;
}

相关文章

显卡天梯图2024最新版,显卡是电脑进行图形处理的重要设备,...
初始化电脑时出现问题怎么办,可以使用win系统的安装介质,连...
todesk远程开机怎么设置,两台电脑要在同一局域网内,然后需...
油猴谷歌插件怎么安装,可以通过谷歌应用商店进行安装,需要...
虚拟内存这个名词想必很多人都听说过,我们在使用电脑的时候...
win11本地账户怎么改名?win11很多操作都变了样,用户如果想要...