Welcome to the NightCi|

XiaoLe_MC

园龄:1年2个月粉丝:3关注:9

2024-10-30 20:41阅读: 81评论: 0推荐: 0

Tarjan 两道题

[THUPC 2024 决赛] 转化

tarjan + DAG DP + 拓扑排序 挺 NB 的一道题

先按照题目说的连边,然后下意识弄个 tarjan。然后根据题意很好发现有以下性质:

  • 如果一个转化方法向自己的 scc 里连的边大于 1 条时,这个 scc 里的所有物品一定为无限多。
  • 如果一个转化方法向外连了大于等于 1 条边,并且自己所在 scc 大小不唯一或者有自环,那么向外连到的 scc 里的所有物品一定无限多。

那么 Infinity 就很好判断了。现在说说 DP 的过程。考虑到一个不为无限的 scc 里的物品数量一定相等,那么就缩点后建新图,拓扑排序后,在 DAG 上跑 DP。对于每个点 u,令 fi 表示点(scc) i 有一个物品时能向 u 提供的贡献。那么初始化 fu=1,根据拓扑序逆向转移即可。这个点的答案 ansu=fi×gigu 表示 u 这个 scc 里已有的物品有多少个。

对于 infinity 的判定。如果当前 ansi>0 并且连边情况符合上述第一个条件,那么给这个 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。

因为值相等的点性质相同,不妨根据值域连边,先将每个 w 的值连向 e,然后再用 e 连接他的倍数,最后判断每一对 e,w 是否在一个 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 中国大陆许可协议进行许可。

posted @   XiaoLe_MC  阅读(81)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起