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; }