Tarjan缩点题单 刷题题解
Tarjan缩点可以将一个图的每个强连通分量缩成一个点,然后构建新图,该图就会变成一个有向无环图。变成有向无环图之后就能结合最短路,拓扑......解决相应题目
洛谷题单分享:
https://www.luogu.com.cn/training/526565
前几道是绿题,没什么好写的,大致过一下
1.强连通分量
题目链接:https://www.luogu.com.cn/problem/B3609
题意:求出每个强连通分量,太模板了
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] += a[y];//SCC大小
} while (y != x);
}
}
int st[N];
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
cout << cnt << endl;
vector<int> ans[N];
for (int i = 1; i <= n; i++) {//这样能直接满足每个强连通分量中的节点编号从小到大
ans[scc[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (!st[i]) {
for (auto k: ans[scc[i]]) {
st[k] = 1;
cout << k << " ";
}
cout << endl;
}
}
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
2.The Cow Prom S
题目链接:https://www.luogu.com.cn/problem/P2863
模板题,直接放代码了
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m,a,b;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
++siz[cnt];//SCC大小
} while (y != x);
}
}
signed main() {
cin >> n >> m;
while (m--)
cin >> a >> b, e[a].push_back(b);
for (int i = 1; i <= n; i++)//可能不连通
if (!dfn[i]) tarjan(i);
int ans = 0;
for (int i = 1; i <= cnt; i++)
if (siz[i] > 1) ans++;
cout << ans << endl;
return 0;
}
3.受欢迎的牛
题目链接:https://www.luogu.com.cn/problem/P2341
如果有多个出度为0的强连通分量,那么受欢迎的牛就为0,否则,受欢迎的牛的数量就是该强连通分量的牛的数量
AC代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N],dout[N];
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
++siz[cnt];//SCC大小
} while (y != x);
}
}
signed main() {
cin >> n>>m;
for (int i = 1; i <= m; i++) {
int u,v;
cin>>u>>v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++)//可能不连通
if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++) {//scc缩点,将连通分量缩成一个点
for (auto k: e[i]) {
if (scc[i] != scc[k]) {
dout[scc[i]]++;
din[scc[k]]++;
}
}
}
int ans,num=0;
for (int i = 1; i <= cnt; i++) {
if (!dout[i]) ans+=siz[i],num++;
}
if(num==1) cout<<ans<<endl;
else cout<<0<<endl;
return 0;
}
4.【模板】缩点
题目链接:https://www.luogu.com.cn/problem/P3387
拓扑+缩点
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] += a[y];//SCC大小
} while (y != x);
}
}
vector<int> ne[N];
int din[N],sum[N];
int topo(){
queue<int> q;
for(int i=1;i<=cnt;i++){
if(!din[i]) q.push(i),sum[i]=siz[i];
}
while(q.size()){
int u=q.front();
q.pop();
for(auto v:ne[u]){
sum[v]=max(sum[v],siz[v]+sum[u]);
--din[v];
if(din[v]==0) q.push(v);
}
}
int ans=0;
for(int i=1;i<=cnt;i++){
ans=max(ans,sum[i]);
}
return ans;
}
void solve() {
int n, m;
cin >> n >> m;
for (int i = 1; i <= n; i++) cin >> a[i];
for (int i = 1; i <= m; i++) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
for (int u = 1; u <= n; u++) {
for (auto v: e[u]) {
if (scc[v] == scc[u]) continue;
ne[scc[u]].push_back(scc[v]);
din[scc[v]]++;
}
}
cout<<topo()<<endl;
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
5.上白泽慧音
题目链接:https://www.luogu.com.cn/problem/P1726
模板题
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e4+10;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] ++;//SCC大小
} while (y != x);
}
}
void solve() {
int n, m;
cin >> n >> m;
int x, u, v;
for (int i = 1; i <= m; i++) {
cin >> u >> v >> x;
if (x == 1) {
e[u].push_back(v);
} else {
e[u].push_back(v);
e[v].push_back(u);
}
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
vector<int> ans[N];
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i);
}
int ans1 = 0;
for (int i = 1; i <= cnt; i++) {
ans1 = max(ans1, siz[i]);
}
cout << ans1 << endl;
for (int i = 1; i <= cnt; i++) {
if (siz[i] == ans1) {
for (auto k: ans[i]) {
cout << k << " ";
}
cout << endl;
}
}
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
5.消息扩散
题目链接:https://www.luogu.com.cn/problem/P2002
答案就是缩点后入度为0的个数
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] ++;//SCC大小
} while (y != x);
}
}
int din[N];
void solve() {
int n, m;
cin >> n >> m;
int u, v;
for (int i = 1; i <= m; i++) {
cin >> u >> v;
if (u != v) e[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
for (int u = 1; u <= n; u++) {
for (auto v: e[u]) {
if (scc[u] == scc[v]) continue;
din[scc[v]]++;
}
}
int pp = 0;
for (int i = 1; i <= cnt; i++) {
if (!din[i]) pp++;
}
cout << pp << endl;
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
接下来是几道蓝题
P1262 间谍网络
题目链接:https://www.luogu.com.cn/problem/P1262
首先,怎么去判断可以控制所有的间谍呢(也就是判断Yes or No),缩点构建新图后,如果入度为0且该入度为0的强连通分量中所有的点都不能被控制,这样就会有间谍不能被控制,就输出No。输出YES后面的要支付的最小贿金,就把入度为0的强连通分量中的每个点最小金额相加就行。
最后就是No后面的输出,利用拓扑(拓扑中,先存入队列的点是可以被控制的强连通分量),将不能控制的强连通分量求出来,然后取最小的那个。
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=1e5+10;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, p;
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] ++;//SCC大小
} while (y != x);
}
}
vector<int> ans[N];
vector<int> ne[N];
int book[N],st[N];
int topo(){
int t=1e18;
queue<int> q;
for(int i=1;i<=cnt;i++){
if(book[i]) q.push(i),st[i]=1;
}
while(q.size()){
int k=q.front();
q.pop();
for(auto v:ne[k]){
din[v]--;
if(din[v]==0){
q.push(v);
st[v]=1;
}
}
}
for(int i=1;i<=cnt;i++){
if(!st[i]){
t=min(t,ans[i][0]);
}
}
return t;
}
void solve() {
cin >> n >> p;
for (int i = 1; i <= p; i++) {
int x, w;
cin >> x >> w;
a[x] = w;
}
int r;
cin >> r;
while (r--) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i);
}
for (int i = 1; i <= n; i++) {
if (a[i]) {
book[scc[i]] = 1;
}
}
for (int u = 1; u <= n; u++) {
for (int v: e[u]) {
if (scc[v] != scc[u]) {
din[scc[v]]++;
ne[scc[u]].push_back(scc[v]);
}
}
}
int ans1 = 0;
int fl = 0;
for (int i = 1; i <= cnt; i++) {
if (din[i] == 0) {
int mn = 1e18, f = 0;
for (auto k: ans[i]) {
if (!a[k]) continue;
else {
f = 1;
mn = min(mn, a[k]);
}
}
if (f) {
ans1 += mn;
} else {
fl = 1;
}
}
}
if (fl) {
cout << "NO" << endl;
cout << topo() << endl;
} else {
cout << "YES" << endl;
cout << ans1 << endl;
}
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
P2169 正则表达式
题目链接:https://www.luogu.com.cn/problem/P2169
这题就是缩点后最短路,很简单
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=2e5+10,inf=0x3f3f3f3f;
struct node{
int v,w;
};
vector<node> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m;
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (auto k: e[x]) {
int y=k.v;
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] ++;//SCC大小
} while (y != x);
}
}
vector<node> ne[N];
struct node1 {
int x, y;
};
struct cmp {bool operator()(const node1 &a, const node1 &b) const {return a.x > b.x;}};
priority_queue<node1,vector<node1>,cmp> q;
int d[N],vis[N];
void dj(int s) {
for (int i = 1; i <= cnt; i++) {
d[i] = inf;
}
d[s] = 0;
q.push({0, s});
while (q.size()) {
auto t = q.top();
q.pop();
int u = t.y;
if (vis[u]) continue;
vis[u] = 1;
for (auto k: ne[u]) {
int v = k.v, w = k.w;
if (d[v] > d[u] + w) {
d[v] = d[u] + w;
q.push({d[v], v});
}
}
}
}
void solve() {
cin >> n >> m;
for (int i = 1; i <= m; i++) {
int u, v, w;
cin >> u >> v >> w;
e[u].push_back({v, w});
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
for (int u = 1; u <= n; u++) {
for (auto k: e[u]) {
int v = k.v, w = k.w;
if (scc[u] != scc[v]) {
ne[scc[u]].push_back({scc[v], w});
}
}
}
dj(scc[1]);
cout << d[scc[n]] << endl;
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
P2194 HXY烧情侣
题目链接:https://www.luogu.com.cn/problem/P2194
最小花费就求出每个强连通分量中的最小的点的汽油金额,方案数就是每个强连通分量中的最小的个数的乘积
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=2e5+10,inf=0x3f3f3f3f,mod=1e9+7;
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m;
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
// siz[cnt]++;
siz[cnt] =min(siz[cnt],a[y]);//SCC大小
} while (y != x);
}
}
vector<int> ne[N];
vector<int> ans[N];
void solve() {
cin >> n;
for (int i = 1; i <= n; i++) {
cin >> a[i];
}
for (int i = 1; i <= n; i++) {
siz[i] = 1e9;
}
cin >> m;
while (m--) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
if (!dfn[i]) tarjan(i);
}
for (int u = 1; u <= n; u++) {
for (int v: e[u]) {
if (scc[u] != scc[v]) {
din[scc[v]]++;
}
}
}
for (int i = 1; i <= n; i++) {
ans[scc[i]].push_back(i);
}
int ans1 = 0;
int ans2 = 1;
for (int i = 1; i <= cnt; i++) {
ans1 += siz[i];
int t = 0;
for (auto k: ans[i]) {
if (a[k] == siz[i]) t++;
}
ans2 = ((ans2 % mod) * (t % mod)) % mod;
}
cout << ans1 << " " << ans2 << endl;
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
P2812 校园网络【[USACO]Network of Schools加强版】
题目链接:https://www.luogu.com.cn/problem/P2812
第一行一个整数,表示至少选几所学校作为共享软件的母机,能使每所学校都可以用上。缩点后,也就是入度为0的个数
第二行一个整数,表示至少要添加几条线路能使任意一所学校作为母机都可以使别的学校使用上软件。也就是max(入度为0的个数,出度为0的个数)
注意要特判一下强连通分量的个数为1的时候
AC代码:
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=1e5+10;
int n,m;
vector<int> e[N];
int dfn[N],low[N],tot;//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N],dout[N];
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
++siz[cnt];//SCC大小
} while (y != x);
}
}
signed main() {
cin >> n;
for(int i=1;i<=n;i++){
while(1){
cin>>m;
if(m==0){
break;
}
e[i].push_back(m);
}
}
for (int i = 1; i <= n; i++)//可能不连通
if (!dfn[i]) tarjan(i);
for(int i=1;i<=n;i++){//scc缩点
for(auto k:e[i]){
if(scc[i]!=scc[k]){
dout[scc[i]]++;
din[scc[k]]++;
}
}
}
int a,b=0;
for(int i=1;i<=cnt;i++){
if(!din[i]) a++;
if(!dout[i]) b++;
}
if(cnt==1){
cout<<1<<endl<<0<<endl;
}
else
cout<<a<<endl<<max(a,b)<<endl;
return 0;
}
P3627 [APIO2009] 抢掠计划
题目链接:https://www.luogu.com.cn/problem/P3627
题目大致目的:缩点后跑一个最长路
首先这个题为点权图,所以先将点权变成边权,然后将边权变为负数,用spfa跑最短路,就可以求最长路了
AC代码:
#include<bits/stdc++.h>
#define int long long
#define endl '\n'
using namespace std;
const int N=5e5+10,inf=0x3f3f3f3f,mod=1e9+7;
struct node{
int v,w;
};
vector<int> e[N];
int dfn[N],low[N],tot,a[N];//dfn[x]为节点第一次被访问的顺序(时间戳),low[x]为从节点x出发能访问的最早时间戳
int stk[N],instk[N],top;//标记是否在栈中
int scc[N],siz[N],cnt;//scc[x]计入x在哪个强连通分量中
int din[N];
int n, m, tmp;
void tarjan(int x) {
//入x时,盖戳、入栈
dfn[x] = low[x] = ++tot;
stk[++top] = x, instk[x] = 1;
for (int y: e[x]) {
if (!dfn[y]) {//若y尚未访问
tarjan(y);
low[x] = min(low[x], low[y]);//回x时更新low
} else if (instk[y])//若y已访问且在栈中
low[x] = min(low[x], dfn[y]);//在x时更新low
}
//离x时,收集SCC
if (dfn[x] == low[x]) {//若x是SCC的根
int y;
++cnt;
do {
y = stk[top--];
instk[y] = 0;
scc[y] = cnt;//SCC编号
siz[cnt] += a[y];//SCC大小
} while (y != x);
}
}
vector<int> ans[N];
vector<node> ne[N];
int ed[N],d[N],vis[N];
queue<int> q;
void spfa(int s) {
for (int i = 1; i <= tmp; i++) {
d[i] = inf;
}
d[s] = 0;
vis[s] = 1;
q.push(s);
while (q.size()) {
int u = q.front();
q.pop();
vis[u] = 0;
for (auto k: ne[u]) {
int v = k.v, w = k.w;
if (d[v] > d[u] + w) {
d[v] = d[u] + w;
if (!vis[v]) q.push(v), vis[v] = 1;
}
}
}
}
void solve() {
cin >> n >> m;
while (m--) {
int u, v;
cin >> u >> v;
e[u].push_back(v);
}
for (int i = 1; i <= n; i++) {
cin >> a[i];
a[i] = -1 * a[i];
}
for (int i = 1; i <= n; i++) if (!dfn[i]) tarjan(i);
for (int u = 1; u <= n; u++) {
for (int v: e[u]) {
if (scc[u] != scc[v]) {
ne[scc[u]].push_back({scc[v], siz[scc[u]]});
}
}
}
int s, p;
cin >> s >> p;
tmp = cnt;
for (int i = 1; i <= p; i++) {
cin >> ed[i];
ne[scc[ed[i]]].push_back({++tmp, siz[scc[ed[i]]]});
}
spfa(scc[s]);
int ans1 = 0;
for (int i = cnt + 1; i <= tmp; i++) {
ans1 = max(-1 * d[i], ans1);
}
cout << ans1 << endl;
}
signed main() {
ios_base::sync_with_stdio(0), cin.tie(0), cout.tie(0);
// int t;
// cin >> t;
// while (t--)
solve();
return 0;
}
接下来的一点点题就打算后面无聊的时候再做了,现在要去刷Tarjan割点了。
Tarjan缩点题单 刷题题解的更多相关文章
- Tarjan缩点【p1262】间谍网络
Description 由于外国间谍的大量渗入,国家安全正处于高度的危机之中.如果A间谍手中掌握着关于B间谍的犯罪证据,则称A可以揭发B.有些间谍收受贿赂,只要给他们一定数量的美元,他们就愿意交出手中 ...
- Tarjan缩点【p1726】上白泽慧音
Description 在幻想乡,上白泽慧音是以知识渊博闻名的老师.春雪异变导致人间之里的很多道路都被大雪堵塞,使有的学生不能顺利地到达慧音所在的村庄.因此慧音决定换一个能够聚集最多人数的村庄作为新的 ...
- Tarjan缩点+DAG图dp
题目背景 缩点+DP 题目描述 给定一个n个点m条边有向图,每个点有一个权值,求一条路径,使路径经过的点权值之和最大.你只需要求出这个权值和. 允许多次经过一条边或者一个点,但是,重复经过的点,权值只 ...
- 看完互联网大佬的「LeetCode 刷题手册」, 手撕了 400 道 Leetcode 算法题
大家好,我是 程序员小熊 ,来自 大厂 的程序猿.相信绝大部分程序猿都有一个进大厂的梦想,但相较于以前,目前大厂的面试,只要是研发相关岗位,算法题基本少不了,所以现在很多人都会去刷 Leetcode ...
- LeetCode刷题感想之DFS
在剑指Offer里专门有一个部分(大概是连续十几题的样子)考察了 DFS ,也就是深度优先遍历,感觉DFS 也是一种套路,只要思路找对,套一下思路也可以了,在这里记录一下. 在很多Leetcode 的 ...
- 洛谷 P2194 HXY烧情侣【Tarjan缩点】 分析+题解代码
洛谷 P2194 HXY烧情侣[Tarjan缩点] 分析+题解代码 题目描述: 众所周知,HXY已经加入了FFF团.现在她要开始喜(sang)闻(xin)乐(bing)见(kuang)地烧情侣了.这里 ...
- 「刷题笔记」Tarjan
贴一个讲得非常详细的\(tarjan\)入门教程 信息传递 讲个笑话:我之前用并查集求最小环过的这题,然后看见题目上有个\(tarjan\)标签 留下了深刻的印象:\(tarjan\)就是并查集求最小 ...
- NOI题库刷题日志 (贪心篇题解)
这段时间在NOI题库上刷了刷题,来写点心得和题解 一.寻找平面上的极大点 2704:寻找平面上的极大点 总时间限制: 1000ms 内存限制: 65536kB 描述 在一个平面上,如果有两个点( ...
- 刷题向》关于搜索+tarjan的奇怪组合题 BZOJ1194 (normal+)
关于这道题,其实看懂了的话还是比较好写的,只是题目实在又臭又长,没有让人读下去的勇气. 给出题目翻译: 给你S张图, 每张图有M个点,其中M个点中有N个是特殊单位,会给出. 每个点又有0.1两条边指向 ...
- LeetCode 刷题 App / LeetCode 题解 App
LeetCode 刷题 APP / LeetCode 题解 App 全端支持 http://leetcode-app.xgqfrms.xyz/ http://leetcode-desktop.xgqf ...
随机推荐
- 【转载】 tf.slice()介绍
原文地址: https://blog.csdn.net/nini_coded/article/details/79852031 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议, ...
- 国内的开源AI模型共享网站(AI模型的GitHub)—— modeldscope —— 对标外网的“huggingface”,modelscope好用吗?
搞AI的应该都是知道huggingface是啥的,这里不过多介绍,简单的来说就是AI模型的Github,之所以这么说是因为计算机的项目往往都是代码文件,所有计算机项目的Github只需要上传项目的代码 ...
- 决定了,今日起开始准备弃用京东JD
估计京东是为了节约开支,然后开始大比例的把快递物流业务进行外包了,这直接导致服务质量的直线下滑,10多年前我选择弃用当当网而选择京东JD就是因为当时当地的当当网快递是用沈阳晚报的快递上门的,快递员连P ...
- 老代码报错:scipy.misc.imresize报错: AttributeError: module 'scipy.misc' has no attribute 'imresize'
运行老代码报错: image = misc.imresize(image, [Config.IMAGE_HEIGHT, Config.IMAGE_WIDTH], 'bilinear')Attribut ...
- ubuntu 安装osx 主题 转自linux公社
繁體 你好,游客 登录 注册 搜索 首页Linux新闻Linux教程数据库技术Linux编程服务器应用Linux安全Linux下载Linux认证Linux主题Linux壁纸Linux软件数码手机电脑 ...
- java创建一个日历(某个月)实例
''' package helloworld; //需要导入py4j.jar包 import py4j.GatewayServer; import java.util.*; import java.l ...
- Cookie、localStorage 和 sessionStorage 的区别及应用实例
在前端开发中,持久化数据存储是一个非常常见的需求.为了实现这一点,浏览器提供了多种方式,包括 Cookie.localStorage 和 sessionStorage.这三者各有优劣,适用于不同的场景 ...
- idea汉化包安装失败解决方法
idea安装中文插件时提示: Plugin "Chinese (Simplified) Language Pack / 中文语言包" was not installed: 查看自己 ...
- 【主席树】P3834 【模板】可持久化线段树 2
P3834 [模板]可持久化线段树 2 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) #include <bits/stdc++.h> using namespace ...
- Antd-React-TreeSelect前端搜索过滤
在开发过程中,但是antd中的搜索会把多余的也会带出来 就例如下图,我们本想去搜索1但是他会把其子节点都带出来,其实我们的本意是像搜2一样或者当中间隔层处理 但是我们该如何解决这样的问题呢如何做到下面 ...