P10693 [SNCPC2024] 换座位 题解
发表于|更新于
|阅读量:
前言
这个题真的是太困难了。
思路
考虑从每个人向他想坐的地方建图。
发现 ≤n 的点出度均为 1,>n 的点出度为 0,如果联通块里全是 ≤n 的点那里面就有 n 条边,否则就有 n−1 条边(因为构成一个联通块至少需要 n−1 条边),所以这一定是一个内向基环树 + 内向树森林。
然后我们考虑怎么选座位。分类讨论,对于树的情况答案为树上到根节点的最长链长度再 -1(根节点 ≥n 所以根节点可以随便让人坐),而基环树的情况只能选环上的所有节点,不然就有人没地方坐。
实现上,树的情况可以对于每个 ≥n 的点 dfs;基环树的情况直接拓扑排序,没有出队过的点一定在环上,求出一共有几个点没有入队即可。
代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| constexpr int N = 2e5+5;
vector<int> tr[N]; set<int> ring; int nxt[N],deg[N],dep[N],mx[N],n;
void dfs(int u,int f){ dep[u] = dep[f]+1; mx[u] = dep[u]; for(int v:tr[u]){ if(v==f) continue; dfs(v,u); } for(int v:tr[u]){ if(v==f) continue; mx[u] = max(mx[u],mx[v]); } }
void solve(){ read(n); for(int i=1;i<=n;i++){ read(nxt[i]); deg[nxt[i]]++; tr[nxt[i]].pb_(i); } queue<int> q; for(int i=1;i<=n*2;i++){ if(!deg[i]) q.push(i); ring.insert(i); } while(!q.empty()){ int u = q.front();q.pop(); ring.erase(u); deg[nxt[u]]--; if(!deg[nxt[u]]) q.push(nxt[u]); } int ans = ring.size(); for(int i=n+1;i<=n*2;i++){ dfs(i,0); ans+=mx[i]-1; } cout<<ans; }
|