Task:
孩子们有
N
(1<=
给定一个
vector
<
string
>
spellChart
,其中
spellChart[i][j]
表示如果为
′Y′
则音标
i
必须在
Solution:
这个题目可以运用构图的思想:
给出一个有N个点的有向图,其中可能含有环。我们可以有以下两个操作:选择两个没有直接相连的点u,v连接,或者选择直接相连的点,删除边 (u,v) 。求出最小要改变的次数,使得新图中没有环。
再继续分析题目,可以发现的是:永远都不需要添加边,因为添加边的操作永远不可能会减少当前图中环的个数。那么题目也就变为如何删除最少的边,使图转化为DAG?那么我们就会想到DAG的性质:拓扑序。(然而我想到了Toposort)
对于一段拓扑序 T={t[0],t[1],⋯,t[i],⋯,t[j],⋯,t[n−1]} ,必然不包含有向边 (t[j],t[i]) ,否则 t[j] 就不会排在 t[i] 的后面。所以对于任何可能的拓扑序 T ,我们必须删除掉的反向边的最小个数就是所求的答案。
如果考虑枚举这个拓扑序,
Set 内元素是不会发生改变的,导致 O(N!) 的复杂度的是Set内点进行了全排列 Acntcnt 。这个全排列是不必要的,因为我们只需要当状态为 Set 的最大值。根据这句话,我们可以采用状压dp求解:
定义 dp[Set] 为当前点集为Set的最大值,则
显然从 O(N!) 优化到了 O(2N) ,那么 N<=20 的范围也可以过了。
还要提一下的是这个题目运用了很多的二进制操作去优化中间的代码与时间复杂度。
class OrderOfTheHats {
public:
static const int inf=1<<30;
int All_mark,n;
int vis[20],dp[1<<20];
inline void Min(int &a,int b){if(a>b)a=b;}
int calc(int m){
int cnt=0;
while(m){m-=m&(-m);++cnt;}
return cnt;
}//计算目前状态去学下一个音标产生矛盾的个数。
int rec(int mark){
if(mark==All_mark)return 0;
int &ans=dp[mark];
if(~ans)return ans;
ans=inf;
for(int i=0;i<n;i++)
if(!(mark>>i&1)){
int nxt=mark|(1<<i);
Min(ans,calc(nxt&vis[i])+rec(nxt));
}
return ans;
}
int minChanged(vector<string> str){
n=str.size();
All_mark=(1<<n)-1;
for(int i=0;i<n;i++){
vis[i]=0;
for(int j=0;j<n;j++)
if(str[i][j]=='Y')vis[i]|=1<<j;
}
memset(dp,-1,sizeof(dp));
return rec(0);
}
};