首先由题意可以知道, 该图不一定连通, 可能是好几个图, 但是一定是特殊的树形结构, 即根节点和叶节点可能是一个环。
这时候运用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;
}
京公网安备 11010502036488号