Tarjan 两道题
[THUPC 2024 决赛] 转化
tarjan + DAG DP + 拓扑排序 挺 NB 的一道题
先按照题目说的连边,然后下意识弄个 tarjan。然后根据题意很好发现有以下性质:
- 如果一个转化方法向自己的
scc
里连的边大于 1 条时,这个scc
里的所有物品一定为无限多。 - 如果一个转化方法向外连了大于等于 1 条边,并且自己所在
scc
大小不唯一或者有自环,那么向外连到的scc
里的所有物品一定无限多。
那么 Infinity 就很好判断了。现在说说 DP 的过程。考虑到一个不为无限的 scc
里的物品数量一定相等,那么就缩点后建新图,拓扑排序后,在 DAG 上跑 DP。对于每个点 ,令 表示点(scc
) 有一个物品时能向 提供的贡献。那么初始化 ,根据拓扑序逆向转移即可。这个点的答案 , 表示 这个 scc
里已有的物品有多少个。
对于 infinity 的判定。如果当前 并且连边情况符合上述第一个条件,那么给这个 scc
打上标记。那么这个 scc
能到达的所有的点,都要打上标记。
以后知道了,再有 DAG 上 DP 首先想到拓扑排序
#include<bits/stdc++.h> using namespace std; constexpr int B = 1 << 23; char buf[B], *p1 = buf, *p2 = buf, obuf[B], *O = obuf; #define gt() (p1==p2 && (p2=(p1=buf)+fread(buf, 1, B, stdin), p1==p2) ? EOF : *p1++) template<typename T> inline void rd(T &x){ x = 0; int f = 0; char ch = gt(); for(; !isdigit(ch); ch = gt()) f ^= ch == '-'; for(; isdigit(ch); ch = gt()) x = (x<<1) + (x<<3) + (ch^48); x = f ? -x : x; } //#define pt(ch) (O-obuf==B && (fwrite(obuf, 1, B, stdout), O=obuf), *O++ = (ch)) #define pt(ch) putchar_unlocked(ch) template<typename T> inline void wt(T x){ if(x > 9) wt(x / 10); pt(x % 10 ^ 48); } #define fw fwrite(obuf, 1, O - obuf, stdout) int n, m, bg[1005], g[105], c[105], dfn[105], low[105], p[105], tot, scc[105], scnt, cnt[105], tp[105], pos[105], its[1005], in[105]; vector<int> G[105], sG[105], Scc[105], Trs[105], meb[1005]; bitset<105> vis, inf; queue<int> q; __int128 f[105], ans[105]; inline void tarjan(int u){ dfn[u] = low[u] = ++tot; p[++p[0]] = u; vis[u] = 1; for(int v : G[u]){ if(!dfn[v]){ tarjan(v); low[u] = min(low[u], low[v]); } else if(vis[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]){ int v; ++scnt; do{ v = p[p[0]--]; scc[v] = scnt; vis[v] = 0; Scc[scnt].emplace_back(v); } while(v ^ u); } } int main(){ rd(n), rd(m); for(int i=1; i<=n; ++i) rd(c[i]); for(int i=1, k; i<=m; ++i){ rd(bg[i]), rd(k); Trs[bg[i]].emplace_back(i); for(int j=1, v; j<=k; ++j){ rd(v); G[bg[i]].emplace_back(v); meb[i].emplace_back(v); } } for(int i=1; i<=n; ++i) if(!dfn[i]) tarjan(i); for(int u=1; u<=n; ++u) for(int v : G[u]) if(scc[u] ^ scc[v]) sG[scc[u]].emplace_back(scc[v]), ++in[scc[v]]; for(int i=1; i<=m; ++i) for(int v : meb[i]) if(scc[bg[i]] == scc[v]) ++its[i]; // tp sort for(int i=1; i<=scnt; ++i) if(!in[i]) q.push(i); while(!q.empty()){ int u = q.front(); q.pop(); tp[pos[u] = ++tp[0]] = u; for(int v : sG[u]) if(!(--in[v])) q.push(v); } // dp for(int i=1; i<=n; ++i) if(c[i]) g[scc[i]] += c[i]; for(int i=1; i<=scnt; ++i){ memset(f, 0, sizeof(__int128) * (scnt+1)); f[i] = 1; for(int j=1; j<pos[i]-1; ++j){ int u = tp[j]; for(int to : Scc[u]) for(int z : Trs[to]) if(!its[z]){ // 只存在外向边 __int128 sum = 0; for(int o : meb[z]) sum += f[scc[o]]; f[u] = max(f[u], sum); } } for(int j=1; j<=scnt; ++j) ans[i] += f[j] * g[j]; } // inf for(int i=1; i<=scnt; ++i) if(ans[i]){ for(int j : Scc[i]) for(int z : Trs[j]){ if(its[z] > 1) inf[i] = 1; if(its[z] > 0){ // 必须有环 (判自环) for(int k : meb[z]) if(scc[k] ^ scc[j]) inf[scc[k]] = 1; } } } for(int i=1; i<=scnt; ++i) if(inf[i]) q.push(i); while(!q.empty()){ int u = q.front(); q.pop(); for(int v : sG[u]) if(!inf[v]) inf[v] = 1, q.push(v); } for(int i=1; i<=n; ++i){ if(inf[scc[i]]) printf("infinity\n"); else wt(ans[scc[i]]), pt('\n'); } return 0; }
[GZOI2017] 小z玩游戏
一眼 tarjan。然后暴力连边。喜提 MLE 60pts。
因为值相等的点性质相同,不妨根据值域连边,先将每个 的值连向 ,然后再用 连接他的倍数,最后判断每一对 是否在一个 scc
里即可。
#include<bits/stdc++.h> using namespace std; constexpr int N = 1e5 + 5; int T, n, a[N], b[N], mx, dfn[N], low[N], tot, p[N], scc[N], scnt; vector<int> G[N]; bitset<N> vis; inline void tarjan(int u){ dfn[u] = low[u] = ++tot; p[++p[0]] = u; vis[u] = 1; for(int v : G[u]){ if(!dfn[v]){ tarjan(v); low[u] = min(low[u], low[v]); } else if(vis[v]) low[u] = min(low[u], dfn[v]); } if(dfn[u] == low[u]){ int v; ++scnt; do{ v = p[p[0]--]; scc[v] = scnt; vis[v] = 0; } while(v ^ u); } } inline void clear(){ for(int i=1; i<N; ++i) G[i].clear(), dfn[i] = low[i] = scc[i] = 0; tot = scnt = mx = 0; vis.reset(); } int main(){ ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); cin>>T; while(T--){ cin>>n; for(int i=1; i<=n; ++i) cin>>a[i], mx = max(mx, a[i]); for(int i=1; i<=n; ++i){ cin>>b[i]; // for(int j=b[i]*2; j<=mx; j+=b[i]) // G[b[i]].emplace_back(j); G[a[i]].emplace_back(b[i]); } for(int i=1; i*2<N; ++i) for(int j=i*2; j<N; j+=i) G[i].emplace_back(j); for(int i=1; i<N; ++i) if(!dfn[i]) tarjan(i); int ans = 0; for(int i=1; i<=n; ++i) if(scc[b[i]] == scc[a[i]]) ++ans; cout<<ans<<'\n'; clear(); } }
本文作者:XiaoLe_MC
本文链接:https://www.cnblogs.com/xiaolemc/p/18516570
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步