Description:
上帝手中有着 N 种被称作“世界元素”的东西,现在他要把它们中的一部分投放到一个新的空间中去以建造世界。每
种世界元素都可以限制另外一种世界元素,所以说上帝希望所有被投放的世界元素都有至少一个没有被投放的世界元素
能够限制它,这样上帝就可以保持对世界的控制。由于那个著名的有关于上帝能不能制造一块连自己都不能举起的大石头的二律背反命题,我们知道上帝不是万能的,而且不
但不是万能的,他甚至有事情需要找你帮忙——上帝希望知道他最多可以投放多少种世界元素,但是他只会 O(2N) 级别的算法。虽然上帝拥有无限多的时间,但是他也是个急性子。
你需要帮助上帝解决这个问题。
Input:
第一行是一个整数 N,表示世界元素的数目。
第二行有 N 个整数 A1, A2, …, AN。Ai 表示第 i 个世界元素能够限制的世界元素的编号。
Output:
一个整数,表示最多可以投放的世界元素的数目。
思路:先找环,跑两次树形DP,一次断开,一次重连
然后本机上用光盘里的数据+手动扩栈过了……codevs上因为不能扩栈结果爆栈了……还是我树形DP写得太差了
#include<iostream> #include<cstring> #include<vector> #include<cstdio> #include<cstdlib> using namespace std; const int N = 1e6 + 10; int n, sz, a[N], dp[N][2], dp2[N][2], deg[N], fa[N], pre[N], dfn[N], c[N]; vector<int> cir; bool vis[N]; int head[N], now = 1; struct edges{ int to, next, f; }edge[N<<1]; void add(int u, int v){ edge[++now].to = v, edge[now].next = head[u], edge[now].f = 1; head[u] = now; edge[++now].to = u, edge[now].next = head[v], edge[now].f = 0; head[v] = now; } int get(int x){ if(x != fa[x]) return fa[x] = get(fa[x]); return x; } void Merge(int x,int y){ x = get(x), y = get(y); if(x != y) fa[y] = x; } void fcur(int x){ dfn[x] = ++sz; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(v == pre[x]) continue; if(dfn[v]){ if(dfn[v] < dfn[x]) continue; cir.push_back(x); c[x] = 1; for(; x != v; v = pre[v]) c[v] = 1, cir.push_back(v); }else pre[v] = x, fcur(v); } return ; } void dfs2(int x){ dp[x][0] = 0; bool flag = 0; int pos = 0, mn = 1e9; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(edge[i].f) continue; flag = 1; dfs2(v); dp[x][0] += max(dp[v][1], dp[v][0]); if(dp[v][1] - dp[v][0] < mn){ mn = dp[v][1] - dp[v][0]; pos = v; } } if(flag) dp[x][1]++; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(edge[i].f) continue; if(v == pos) dp[x][1] += dp[v][0]; else dp[x][1] += max(dp[v][1], dp[v][0]); } return ; } void dfs3(int x){ dp2[x][0] = 0; bool flag = 0; int pos = 0, mn = 1e9; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(edge[i].f) continue; flag = 1; dfs3(v); dp2[x][0] += max(dp2[v][1], dp2[v][0]); if(dp2[v][1] - dp2[v][0] < mn){ mn = dp2[v][1] - dp2[v][0]; pos = v; } } if(x == a[cir[0]]){ //如果找到能强制重连的点 dp2[x][1]++; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(edge[i].f) continue; dp2[x][1] += max(dp2[v][1], dp2[v][0]); } return ; } if(flag) dp2[x][1]++; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(edge[i].f) continue; if(v == pos) dp2[x][1] += dp2[v][0]; else dp2[x][1] += max(dp2[v][1], dp2[v][0]); } return ; } void DP(){ for(int i = head[cir[0]]; i; i = edge[i].next) if(c[edge[i].to] && edge[i].f){ //先断开环上两个点的关系 edge[i ^ 1].f = 1; break; } dfs2(cir[0]); dfs3(cir[0]); //带上强制重连的点DP dp[cir[0]][0] = max(dp[cir[0]][0], dp2[cir[0]][0]); //更新关系 } int main(){ int size = 32 << 20; char*p=(char*)malloc(size) + size; __asm__("movl %0, %%esp\n" :: "r"(p) ); // freopen("gen.in","r",stdin); // freopen("gen.out","w",stdout); scanf("%d", &n); for(int i = 1; i <= n; i++) fa[i] = i; for(int i = 1; i <= n; i++){ scanf("%d", &a[i]); add(i, a[i]); Merge(i, a[i]); } int ans = 0; for(int i = 1; i <= n; i++){ //对于每个联通块找环后DP int x = get(i); if(!vis[x]){ cir.clear(); vis[x] = 1, fcur(i); // for(int i = 0; i < cir.size(); i++) cout<<cir[i]<<" "; cout<<endl; DP(); ans += max(dp[cir[0]][0], dp[cir[0]][1]); } } printf("%d\n", ans); return 0; }
在这里插一个基环树找环的代码(适用于无向基环树)
void fcur(int x){ dfn[x] = ++sz; for(int i = head[x]; i; i = edge[i].next){ int v = edge[i].to; if(v == pre[x]) continue; if(dfn[v]){ if(dfn[v] < dfn[x]) continue; cir.push_back(x); c[x] = 1; for(; x != v; v = pre[v]) c[v] = 1, cir.push_back(v); }else pre[v] = x, fcur(v); } return ; }