DFS + tarjan(缩点) + 二分 + 拓扑

珂朵莉喊你一声大佬

https://ac.nowcoder.com/acm/problem/14524

首先由题意可以知道, 该图不一定连通, 可能是好几个图, 但是一定是特殊的树形结构, 即根节点和叶节点可能是一个环。
这时候运用tarjan缩点后重新建图, 就会建成树形结构(可能是好几颗树)。
然后二分(二分最小的大佬数量)
剩下的就是check的问题了
check要用到DFS回溯和拓扑排序
DFS不能从根节点开始往下面去计算, 这样子就不知道在每个子树里分配几个人过去。
换一种思想, 从叶节点开始回溯, 返回该节点所在子树还缺几个人。*如果多出来人是不能反馈给父节点的, 少人是需要向父节点索取的。 *
拓扑排序来确定每个树的根节点位置, 入度为0的就是根节点

#include<map>
#include<set>
#include<cmath>
#include<queue>
#include<stack>
#include<cstdio>
#include<vector>
#include<bitset>
#include<string>
#include<cstdlib>
#include<cstring>
#include<fstream>
#include<sstream>
#include<iomanip>
#include<iostream>
#include<algorithm>
#include<functional>
using namespace std;
//#define INF 0x3f3f3f3f //1061109567
#define pi 3.141592653589793
#define ll_mx 9223372036854775807
#define dbg(x) cout << #x << "=" << x << endl;
const long long MAXN = 2e6 + 7;
const double eps = 1e-7;
const long long mod = 1e9 + 7;
const int INF = 0x3f3f3f3f;
#define lowbit(n) (n)&(-n)
typedef long long ll;
int n;
ll m;
struct node{
    int u;
    int v;
    int next;
}edge1[MAXN], edge2[MAXN];
int head[MAXN], tol = 1, in[MAXN], low[MAXN], dfn[MAXN], cnt1 = 1, cnt2 = 1, pos[MAXN], vis[MAXN];
ll a[MAXN], num[MAXN], peo[MAXN];
vector<int>root;
void add(int u, int v, node* edge)
{
    edge[tol].u = u;
    edge[tol].v = v;
    edge[tol].next = head[u];
    head[u] = tol++;
}
stack<int>S;
void tarjan(int x, node * edge)
{
    dfn[x] = low[x] = cnt1++;
    vis[x] = 1;
    S.push(x);
    for (int i = head[x]; i; i = edge[i].next)
    {
        int k = edge[i].v;
        if (!dfn[k])
        {
            tarjan(k, edge);
            low[x] = min(low[x], low[k]);
        }
        else if (vis[k])
            low[x] = min(low[x], dfn[k]);
    }
    if (low[x] == dfn[x])
    {
        int y;
        do{
            y = S.top();
            S.pop();
            vis[y] = 0;
            num[cnt2] += a[y];
            pos[y] = cnt2;
            peo[cnt2]++;
        }while(x != y);
        cnt2++;
    }
}
//返会该节点所在子树里还缺多少人
ll dfs(int x, ll mid, node * edge)
{
    ll need = 0;
    //如果是叶节点, 叶节点不单独判莫名其妙tle
    if (head[x] == 0){
        ll p = num[x] - peo[x] * mid;
        if (p > 0)
            return 0;
        return p;
    }
    for (int i = head[x]; i; i = edge[i].next)
    {
        int k = edge[i].v;
        need += dfs(k, mid, edge);
    }

    //处理该节点
    ll p = num[x] - peo[x] * mid;
    need += p;
    //人多出来是不能向父节点反馈的
    if (need > 0)
        return 0;
    return need;
}
bool check(ll mid)
{
    ll need = 0;
    for (int i = 0; i < root.size(); i++){
        //need表示这个树还需要分配多少个人
        need += dfs(root[i], mid, edge2);
        if (need + m < 0)//随时准备跳出循环, 否则会tle
            return false;
    }

    return true;
}
int main(int argc, char const *argv[])
{
    scanf("%d%lld", &n, &m);
    for (int i = 1; i <= n; i++)
    {
        int u;
        scanf("%d", &u);
        if (u == -1 || u == i)//自环的情况舍弃
            continue;
        add(u, i, edge1);
    }
    ll mx = 0, mi = ll_mx;
    for (int i = 1; i <= n; i++){
        scanf("%lld", &a[i]);
        mx = max(mx, a[i]);
        mi = min(mi, a[i]);
    }
    //普通缩点
    for (int i = 1; i <= n; i++)
        if (!dfn[i])
            tarjan(i, edge1);
    int sum = tol;
    //初始化
    tol = 1;
    for (int i = 1; i <= cnt2; i++)
    {
        head[i] = 0;
    }
    //重新建图, 如果不会就学一下tarjan
    for (int i = 1; i < sum; i++)
    {
        int u = edge1[i].u;
        int v = edge1[i].v;
        if (pos[u] != pos[v])
        {
            add(pos[u], pos[v], edge2);
            in[pos[v]]++;
        }
    }
    //记录根节点
    for (int i = 1; i < cnt2; i++)
        if (!in[i])
            root.push_back(i);

    //二分check
    ll l = mi, r = mx + m;
    while(l <= r)
    {
        ll mid = (l + r) >> 1;
        if (check(mid))
        {
            l = mid + 1;
        }
        else
            r = mid - 1;
    }
    printf("%lld\n", r);

    return 0;
}
全部评论

相关推荐

无情咸鱼王的秋招日记之薛定谔的Offer:好拒信,偷了,希望有机会用到
点赞 评论 收藏
分享
10-09 22:05
666 C++
找到工作就狠狠玩CSGO:报联合国演讲,报电子烟设计与制造
点赞 评论 收藏
分享
评论
1
收藏
分享
牛客网
牛客企业服务