题目
题目描述
最近市区的交通拥挤不堪,交通局长如果再不能采取措施改善这种糟糕的状况,他就不可避免地要被免职了。市区的道路已经修得够多了,总共
n
n
n 个站点,已经修了
n
(
n
−
1
)
2
\frac{n(n-1)}{2}
2n(n−1) 条道路,也就是任意两个站点都有一条道路连接。但因为道路都很窄,也无法再加宽,所以所有的道路都是单向的。现在,交通局长认为导致交通拥堵的原因之一是存在环路。他决定改变一些道路的方向,使得不存在任何环路。但是,如果改动数量太多,市民们又要打电话投诉了。现在,请你帮帮他,尽量改动最少的道路的方向,使得整个交通网中没有环路。
输入格式
给出一个整数
n
n
n ,表示有
n
n
n 个点。
接下来有一个
n
n
n 行
n
n
n 列的矩阵,如果第
i
i
i 行第
j
j
j 列为
1
1
1 ,表示有一条从
i
i
i 到
j
j
j 的单向道路,如果为
0
0
0 ,表示没有从
i
i
i 到
j
j
j 的单向道路。
保证所有的数据合法。
输出格式
一个整数,表示最少需要改变的道路条数。
数据范围与约定
40
%
40\%
40% 的数据,
n
≤
10
n\le 10
n≤10 。
100
%
100\%
100% 的数据,
n
≤
20
n\le 20
n≤20 。
思路
有一个性质,即 n n n 个点, n ( n − 1 ) 2 \frac{n(n-1)}{2} 2n(n−1) 条边的有向图中不存在环的充要条件是: n n n 个点的出度或入度互不相同,出度或入度从 0 0 0 到 n − 1 n-1 n−1 都有。
首先必须有一个点出度为 0 0 0 ,否则从任意一个点出发,沿着有向边可以一直走下去。那自然会重复访问到某个重复点,这就表明有环了。
也不能有多于一个点出度为 0 0 0 。否则这两个点之间就没有边了。
所以,必须刚好一个点的出度为 0 0 0 。而它的入度刚好是 n − 1 n-1 n−1 。删掉这个点及它的 n − 1 n-1 n−1 条入边,我们得到一个 n − 1 n-1 n−1 点的图,刚好有 ( n − 1 ) ( n − 2 ) 2 \frac{(n-1)(n-2)}{2} 2(n−1)(n−2) 条边。同样的分析方法,我们知道,新图中只能有一个点出度为 0 0 0 。那意味着它原来的出度为 1 1 1 。依次类推,可以知道,出度为 2 2 2 、为 3 3 3 、……,为 n − 1 n-1 n−1 的点都只有唯一一个。
状压 d p \tt{dp} dp ,设 f ( x ) f(x) f(x) 表示达到 x x x 这个状态需改变的边数量的最小值。 x x x 的二进制中,为 1 1 1 的位,表示其代表的点入度已经达到最大值,为 0 0 0 的位,表示该点还没有考虑。
比如 x x x 中为 1 1 1 的位有 3 3 3个,则这三个点的入度是 n − 1 , n − 2 , n − 3 n-1,n-2,n-3 n−1,n−2,n−3 。这个 x x x 表示的是组合,即这三个点无论谁入度为 n − 1 n-1 n−1 ,谁入度为 n − 2 n-2 n−2 ,都可以更新 f ( x ) f(x) f(x) 。
这三个点都已经达到了入度的最大值,它们在未来的图中都不再起作用,可以认为他们被删掉了。
此时,任选 x x x 的二进制中为 0 0 0 的位,设该位为第 j j j 位,其他的 0 0 0 位都向它连有向边。
这里统计需要改变方向的边的数量,记为 w j w_j wj 。 则 f ( x ) = min [ f ( x + 2 j ) + w j ] f(x)=\min[f(x+2^j)+w_j] f(x)=min[f(x+2j)+wj] 。
w j w_j wj 可以预处理的。总复杂度 O ( n 2 n ) \mathcal O(n2^n) O(n2n)( w w w 的计算,均摊是 O ( n 2 n ) \mathcal O(n2^n) O(n2n) ,证明略)。
代码
#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;
const int MaxN = 20;
int g[MaxN][MaxN], n;
int dp[1<<MaxN], w[MaxN][1<<MaxN];
int main(){
scanf("%d",&n);
for(int i=0; i<n; ++i){
for(int j=0; j<n; ++j)
scanf("%d",&g[i][j]);
g[i][i] = 1;
}
for(int i=0; i<n; ++i){
w[i][(1<<n)-1] = 0;
for(int S=(1<<n)-2; ~S; --S){
int bit;
for(bit=0; bit<n; ++bit)
if(not(S>>bit&1)) break;
w[i][S] = w[i][S^(1<<bit)]+(g[bit][i]^1);
}
}
for(int S=1; S<(1<<n); ++S){
dp[S] = 1<<MaxN;
for(int i=0; i<n; ++i)
if(S>>i&1)
dp[S] = min(dp[S],dp[S^(1<<i)]+w[i][S^(1<<i)]);
}
printf("%d\n",dp[(1<<n)-1]);
return 0;
}