题目:这里
题意:软件在安装之后需要重启才能发挥作用,现在给你一堆软件(有的需要重启有的不需要)以及安装这个软件之前需要哪些软件发挥作用,求最少的重启次数。
解法:真拓扑好题,但是我不会。然后这个题有两种解法,解法1来自斌神:普通的拓扑排序用了一个队列,而现在用两个队列q1,q2分别来存 不需要重启的software 和 需要重启的software。根据题目输入建好图后,按照拓扑序规则,首先将入度的0的点加进队列,当然不需要重启的进q1,需要的进q2。然后处理q1中的所有节点(即要删除这些点),那么要让与他们相连的节点的入度减1,如果发现减完入度为0,再判断其是否需要重启,并加进q1 or q2。一旦发现q1为空,那么使答案加1(即重启一次),把q2中所有元素加入q1,q2清空。如此循环直到q1,q2均为空即可。
//HDU 5098 topsort
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1020;
map <string, int> mp;
int cnt, edgecnt, reboot[maxn], out[maxn], in[maxn];
int head[maxn], vis[maxn];
struct edge{
int v, nxt;
edge(){}
edge(int v, int nxt) : v(v), nxt(nxt) {}
}E[maxn*maxn];
void init(){
edgecnt = cnt = 0;
mp.clear();
memset(head, -1, sizeof(head));
memset(reboot, 0, sizeof(reboot));
memset(out, 0, sizeof(out));
memset(in, 0, sizeof(in));
}
void addedge(int u, int v){
E[edgecnt].v = v, E[edgecnt].nxt = head[u], head[u] = edgecnt++;
}
int topsort(){
queue <int> q1, q2;
for(int i = 1; i <= cnt; i++){
if(in[i] == 0){
if(reboot[i] == 0) q1.push(i);
else q2.push(i);
}
}
int ans = 0;
while(!q1.empty() || !q2.empty()){
if(q1.empty() && !q2.empty()){
ans++;
while(!q2.empty()){
q1.push(q2.front());
q2.pop();
}
}
while(!q1.empty()){
int u = q1.front(); q1.pop();
for(int i = head[u]; ~i; i = E[i].nxt){
int v = E[i].v;
in[v]--;
if(in[v] == 0){
if(reboot[v] == 0) q1.push(v);
else q2.push(v);
}
}
}
}
return ans;
}
int main()
{
string s;
char name[1010];
int T, ks = 0;
scanf("%d", &T);
getchar();
getchar();
while(T--){
init();
printf("Case %d: ", ++ks);
while(getline(cin, s)){
if(s[0] == '\0') break;
istringstream sin(s);
sin >> name;
int len = strlen(name);
int flag = 0;
if(name[len - 2] == '*'){
flag = 1;
name[len - 2] = '\0';
}
else{
name[len - 1] = '\0';
}
string a, b;
a = name;
if(mp.find(a) == mp.end()) mp[a] = ++cnt;
reboot[mp[a]] = flag;
while(sin >> b){
if(mp.find(b) == mp.end()) mp[b] = ++cnt;
addedge(mp[b], mp[a]);
out[mp[b]]++;
in[mp[a]]++;
}
}
int ans = topsort();
printf("%d\n", ans);
}
return 0;
}
解法2来自hdu鸟神:建图同上,在建完的DAG图中求最长路,然后需要重启的点权为1,不需要的为0。这样找出最长路的权和即可。易证:只有具有从属关系的重启需要分别进行,不然可以同时搞掉。这样需要对每个入度为0的点求一次这样的最长路,取最大值即可,具体方法用DP可解。真orz。
//HDU 5098 topsort
#include <bits/stdc++.h>
using namespace std;
const int maxn = 1020;
map <string, int> mp;
int cnt, edgecnt, reboot[maxn], out[maxn], in[maxn];
int head[maxn], vis[maxn], dp[maxn];
struct edge{
int v, nxt;
edge(){}
edge(int v, int nxt) : v(v), nxt(nxt) {}
}E[maxn*maxn];
void init(){
edgecnt = cnt = 0;
mp.clear();
memset(head, -1, sizeof(head));
memset(reboot, 0, sizeof(reboot));
memset(out, 0, sizeof(out));
memset(in, 0, sizeof(in));
}
void addedge(int u, int v){
E[edgecnt].v = v, E[edgecnt].nxt = head[u], head[u] = edgecnt++;
}
int dfs(int u){
if(dp[u]) return dp[u];
int ans = 0;
for(int i = head[u]; ~i; i = E[i].nxt){
int v = E[i].v;
ans = max(ans, dfs(v));
}
return dp[u] = ans + reboot[u];
}
int DAGdp(){
int ans = 0;
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= cnt; i++){
if(in[i] == 0){
ans = max(ans, dfs(i));
}
}
return ans;
}
int main()
{
string s;
char name[1010];
int T, ks = 0;
scanf("%d", &T);
getchar();
getchar();
while(T--){
init();
printf("Case %d: ", ++ks);
while(getline(cin, s)){
if(s[0] == '\0') break;
istringstream sin(s);
sin >> name;
int len = strlen(name);
int flag = 0;
if(name[len - 2] == '*'){
flag = 1;
name[len - 2] = '\0';
}
else{
name[len - 1] = '\0';
}
string a, b;
a = name;
if(mp.find(a) == mp.end()) mp[a] = ++cnt;
reboot[mp[a]] = flag;
while(sin >> b){
if(mp.find(b) == mp.end()) mp[b] = ++cnt;
addedge(mp[b], mp[a]);
out[mp[b]]++;
in[mp[a]]++;
}
}
int ans = DAGdp();
printf("%d\n", ans);
}
return 0;
}