题目
[树形DP, 缩点]BZOJ 2427:缩点后跑树上背包
模板及讲解
参考资料:https://www.byvoid.com/blog/scc-tarjan/
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<stack>
#include<vector>
#include<cmath>
#define ms(i,j) memset(i, j, sizeof(i));
using namespace std;
struct node
{
int x;
int y;
}; //图里的边
vector<node> q[205]; //边集
stack<int> s; //tarjan栈
int n;
int index = 0; //时间戳
int ans = 0; //强连通分量个数即答案
int ex[205]; //是否访问过,0=没访问,-1=在栈中,1=访问完毕
int dn[205], low[205]; //dn为时间戳,low[i]为结点i能追溯到的最早栈中元素
int tarjan(int u)
{
dn[u]=low[u]=++index; //赋值时间戳和low的初值
ex[u]=-1; //标记在栈中
s.push(u); //放进栈中
node p;
for (int i=0;i<q[u].size();i++)
{ //枚举每一条边(u,v)
p = q[u][i];
if (ex[p.y]==0) { //结点没访问过
tarjan(p.y);
low[u] = min(low[u], low[p.y]); //树枝,即(u,v)没访问过且u->v
} else if (ex[p.y]==-1) //结点在栈中
{
low[u] = min(low[u], dn[p.y]); //后向边,即(u,v)中的v在栈中
}
}
if (dn[u]==low[u]) //找到一个强连通分量
{
int e;
do
{
e = s.top();
s.pop();
ex[e] = 1; //标记访问完毕
} while (u!=e); //退栈直到u!=e
ans++;
}
}
int main ()
{
scanf("%d", &n);
for (int i=1;i<=n;i++)
{
int a;
scanf("%d", &a);
while (a!=0)
{
node r;
r.x = i, r.y = a;
q[i].push_back(r);
scanf("%d", &a);
}
}
ms(ex, 0);
ms(dn,0);
for (int i=1;i<=n;i++)
if (!dn[i]) tarjan(i);
printf("%d\n", ans);
return 0;
}