起初打算直接用拓扑排序模拟看书过程。每一遍都把能加的点全部加进去队列,等队列空了就重新看一遍。
对于节点i,如果本轮中i的父亲节点编号都小于i,且此时i的入度已经为0,就把i加入队列。
但是这个思路始终写不对,应该是假了,遂放弃。
题解在拓扑排序的基础上用了dp,dp[i]表示学完第i个章节至少需要的次数。 然后从上到下一层一层地更新。
dp[i]=max(dp[fa])+1 (i<fa)
dp[i]=max(dp[fa]) (i>fa)
对于第i个节点,我们枚举它的所有父亲节点求出次数最大的,(如果有多个就取顶点编号最大的) ,然后更新i。
如此一来很巧妙地解决了答案的保存过程。
时间复杂度O(n+e)
虽然每个顶点都枚举了一遍父节点,但是由于每个节点只会作为父节点被枚举一次,所以最多枚举完1次所有的边。
#include<bits/stdc++.h>
using namespace std;
//#pragma GCC optimize(2)
#define ull unsigned long long
#define ll long long
#define pii pair<int, int>
#define pdd pair<double, double>
#define re register
const int maxn = 2e5 + 10;
const int mx = 40;
const ll mod = 1e9+7;
const ll inf = (ll)4e17+5;
const int INF = 1e9 + 7;
const double pi = acos(-1.0);
ll inv(ll b){if(b==1)return 1;return(mod-mod/b)*inv(mod%b)%mod;}
vector<int> g[maxn],fa[maxn];
queue<int> q;
int n;
int du[maxn];//入度
int dp[maxn];//看完第i章 需要的最少次数
void tupo()//从上层往下层dp
{
int ans=1;
int cnt=0;
while(q.size())
{
int h=q.front();
q.pop();
cnt++;
for(int &i:g[h])
{
du[i]--;
if(!du[i])
{
//找出父节点中最大的 如果有多个 取id最大的
int mx=0,idx=-1;
for(int &f:fa[i])
{
if(dp[f] > mx)
{
mx=dp[f];
idx=f;
}
else if(dp[f] == mx) idx=max(idx,f);
}
if(i>idx) dp[i]=mx;
else dp[i]=mx+1;
ans=max(ans,dp[i]);
q.push(i);
}
}
}
if(cnt<n) ans=-1;//存在环
printf("%d\n",ans);
}
int main()
{
int t;cin>>t;
while(t--)
{
scanf("%d",&n);
for(int i=1;i<=n;i++) g[i].clear(),du[i]=0,dp[i]=1,fa[i].clear();
while(q.size()) q.pop();
for(int i=1;i<=n;i++)
{
scanf("%d",du+i);
if(!du[i]) q.push(i);
for(int j=1;j<=du[i];j++)
{
int u;
scanf("%d",&u);
g[u].push_back(i);
fa[i].push_back(u);
}
}
tupo();
}
return 0;
}