题目描述
最近市区的交通拥挤不堪,交通局长如果再不能采取措施改善这种糟糕的状况,他就不可避免地要被免职了。市区的道路已经修得够多了,总共n个站点,已经修了n∗(n−1)/2n*(n-1)/2n∗(n−1)/2条道路,也就是任意两个站点都有一条道路连接。但因为道路都很窄,也无法再加宽,所以所有的道路都是单向的。现在,交通局长认为导致交通拥堵的原因之一是存在环路。他决定改变一些道路的方向,使得不存在任何环路。但是,如果改动数量太多,市民们又要打电话投诉了。现在,请你帮帮他,尽量改动最少的道路的方向,使得整个交通网中没有环路。
输入格式
给出一个整数nnn,表示有nnn个点。接下来有一个nnn行nnn列的矩阵,如果第iii行第jjj列为1,表示有一条从iii到jjj的单向道路,如果为0,表示没有从iii到jjj的单向道路。保证所有的数据合法。
输出格式
一个整数,表示最少需要改变的道路条数。
输入样例
4
0 0 0 0
1 0 1 0
1 0 0 1
1 1 0 0
输出样例
1
数据范围
对于所有数据,n≤20n\leq 20n≤20
题解
依题意,总共有n∗(n−1)/2n*(n-1)/2n∗(n−1)/2条单向边,而且任意两点之间不超过一条边,那么也就是说任意两点之间都有一条单向边。
有一个性质,在上述的有向图中不存在环的充要条件是:n个点的出度或入度互不相同,0到n-1在这些点的出度或入度中都有出现。
证明
首先必须要有一个点出度为0,因为如果所有点的出度不为0,则从任意一个点出发,可以一直走下去,这样必定会形成环。也不能有多个点出度为0,否则这两点之间就没有边,与上述内容不符。所以必须刚好有一个点出度为0,根据上文任意两点之间都有一条单向边,这个点的入度就是n−1n-1n−1。删掉这个点以及它的n−1n-1n−1条入边,我们就可以得到一个有n−1n-1n−1个点、(n−1)∗(n−2)/2(n-1)*(n-2)/2(n−1)∗(n−2)/2条边的有向图。根据同样的分析方法,新的图中只有一个点出度为0,那么这个点原来出度就为1。以此类推,一直这样下去,我们可以知道,出度为2、3、4…的点都只有一个。只有这样才能保证图中没有环。
做法
看到n≤20n\leq 20n≤20,我们很容易想到状压DP。设f(s)f(s)f(s)表示达到sss这个状态需改变边数量的最小值。sss二进制中为1的位表示这个点的入度已经达到最大值,为0的位表示该点还未考虑。比如sss二进制位中有3位为1,则为一的这三个点入度分别为n−1,n−2,n−3n-1,n-2,n-3n−1,n−2,n−3。sss并没有规定每个点入度具体为多少,只表示了这个状态中有哪几个点已经达到最大值。
被标记为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;
}