珂朵莉喊你一声大佬(Tarjan&拓扑序&卡常)

链接:https://ac.nowcoder.com/acm/problem/14524
来源:牛客网
 

时间限制:C/C++ 2秒,其他语言4秒
空间限制:C/C++ 131072K,其他语言262144K
64bit IO Format: %lld

题目描述

有n种大佬,第i种大佬有ai个
珂朵莉想让最少个数的一种大佬的个数最多
你可以创造m个任意种类的大佬,并且可以把一些大佬变成另一些大佬
x -> y意味着可以把任意个x类型的大佬变成y类型的大佬
一个大佬可以被转换多次
对于每个y,最多有一个x使得x -> y成立

 

输入描述:

 

第一行两个数n,m

之后一行,第i个数xi表示第i种大佬可以被哪种大佬转换得到

如果xi为-1表示这种大佬不可以被任何大佬转换得到

之后一行,第i个数ai表示第i种大佬的个数

输出描述:

输出一行一个数表示答案
答案即
你要求让最少个数的一种大佬的个数最多的方案
输出这个方案下最少个数的一种大佬的个数

示例1

输入

复制5 5 -1 1 1 1 1 4 5 1 3 2

5 5
-1 1 1 1 1 
4 5 1 3 2

输出

复制3

3

示例2

输入

复制10 10 -1 1 1 2 1 5 5 6 10 5 6 1 7 1 7 1 10 5 1 1

10 10
-1 1 1 2 1 5 5 6 10 5 
6 1 7 1 7 1 10 5 1 1

输出

复制4

4

备注:

 

对于100%的数据,n <=1000000 , m , ai <= 1000000000

 

题目大意:

给你一个有向图,每个点都有一个初始权值,u连v代表u可以把自己一部分转换成v,你有M次机会可以使任意一个点的权值+1,问你最小权值的最大值是多少。

解法:

先缩点形成一个DAG图,然后二分每次逆拓扑dp一下,可惜这样会TLE、MLE。优化方法就是用链式前向星存图(对于1e6的点来说这样会快一些),新图由于只有一个儿子结点,单数组存即可,再预处理一下缩点后的拓扑序,感觉这道题的难点就是得想到预处理吧。

Accepted code

#pragma GCC optimize(3)
#include
#include
using namespace std;

#define sc scanf
#define ls rt << 1
#define rs ls | 1
#define Min(x, y) x = min(x, y)
#define Max(x, y) x = max(x, y)
#define ALL(x) (x).begin(),(x).end()
#define SZ(x) ((int)(x).size())
#define pir pair 
#define MK(x, y) make_pair(x, y)
#define MEM(x, b) memset(x, b, sizeof(x))
#define MPY(x, b) memcpy(x, b, sizeof(x))
#define lowbit(x) ((x) & -(x))
#define P2(x) ((x) * (x))

typedef long long ll;
const int Mod = 1e9 + 7;
const int N = 1e6 + 100;
const int INF = 0x3f3f3f3f;
const ll LINF = 0x3f3f3f3f3f3f3f3f;
inline ll dpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t) % Mod; b >>= 1; t = (t*t) % Mod; }return r; }
inline ll fpow(ll a, ll b){ ll r = 1, t = a; while (b){ if (b & 1)r = (r*t); b >>= 1; t = (t*t); }return r; }

struct Edge
{
	int v, nxt;
}G[N];
int h[N], c;
ll a[N], dp[N], k;
ll sz[N], val[N];

void Add(int u, int v) {
	G[++c] = { v, h[u] };
	h[u] = c;
}

// 强连通
int dfn[N], low[N], cnt, scnt;
int stk[N], n, s;
int suo[N];
bool vis[N];

void Tarjan(int x) {    // 缩点
	dfn[x] = low[x] = ++cnt;
	stk[++s] = x;
	vis[x] = true;
	for (int i = h[x]; i != -1; i = G[i].nxt) {
		int v = G[i].v;
		if (!dfn[v]) {
			Tarjan(v);
			Min(low[x], low[v]);
		}
		else if (vis[v])
			Min(low[x], low[v]);
	}
	if (dfn[x] == low[x]) {
		scnt++;
		int k;
		do {
			k = stk[s--];
			sz[scnt]++;    // 大小
			suo[k] = scnt;   // 属于集合
			val[scnt] += a[k];   // 集合权值
			vis[k] = false;
		} while (k != x);
	}
}

// 拓扑序
int nxt[N], in[N];
int top[N], res;
void Init() {
	for (int i = 1; i <= n; i++)
		nxt[i] = h[i] = -1;
}
void Topsort() {
	queue  q;
	for (int i = 1; i <= scnt; i++) {
		if (!in[i])
			q.push(i);
	}

	while (!q.empty()) {
		int u = q.front();
		q.pop();
		top[++res] = u;   // 拓扑序

		if (nxt[u] != -1) {
			int v = nxt[u];
			in[v]--;
			if (!in[v])
				q.push(v);
		}
	}
}
bool Check(ll x) {
	ll tot = k;
	
	for (int i = 1; i <= scnt; i++) {
		int u = top[i];
		dp[u] += max(0ll, sz[u] * x - val[u]);  // 需要的花费

		if (val[u] > sz[u] * x)                 // 抵消花费
			dp[u] -= min(dp[u], val[u] - sz[u] * x);

		if (nxt[u] != -1)
			dp[nxt[u]] += dp[u];      // 到终点,更新答案
		else
			tot -= dp[u];
		dp[u] = 0;
	}
	return tot >= 0;          // 足够就满足
}

int main()
{
	cin >> n >> k;
	Init();
	for (int i = 1; i <= n; i++) {
		int v;
		sc("%d", &v);
		if (v != -1)
			Add(i, v);
	}
	for (int i = 1; i <= n; i++)
		sc("%lld", &a[i]);

	for (int i = 1; i <= n; i++)
		if (!dfn[i])
			Tarjan(i);

	for (int i = 1; i <= n; i++) {
		for (int j = h[i]; j != -1; j = G[j].nxt) {
			int u = suo[i], v = suo[G[j].v];
			if (u != v)
				nxt[u] = v, in[v]++;
		}
	}

	Topsort();

    int l = 0, r = 1e9, ans = 1e9;
	while (l <= r) {
		ll mid = (l + r) >> 1;
		if (Check(mid))
			ans = mid, l = mid + 1;
		else
			r = mid - 1;
	}

	printf("%d\n", ans);
	return 0;  // 改数组大小!!!用pair记得改宏定义!!!
}

 

你可能感兴趣的:(拓扑排序,Tarjan)