Codeforces Round #743 (Div. 2) C. Book (拓扑排序+dp)

传送门

起初打算直接用拓扑排序模拟看书过程。每一遍都把能加的点全部加进去队列,等队列空了就重新看一遍。
对于节点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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值