1. 程式人生 > >bzoj3832[Poi2014]Rally(set,bitset,位運算,拓撲排序,堆,線段樹)

bzoj3832[Poi2014]Rally(set,bitset,位運算,拓撲排序,堆,線段樹)

傳送門:https://www.lydsy.com/JudgeOnline/problem.php?id=3832

Description
給定一個N個點M條邊的有向無環圖,每條邊長度都是1。
請找到一個點,使得刪掉這個點後剩餘的圖中的最長路徑最短。
Input
第一行包含兩個正整數N,M(2<=N<=500 000,1<=M<=1 000 000),表示點數、邊數。
接下來M行每行包含兩個正整數A[i],Bi,表示A[i]到B[i]有一條邊。
Output
包含一行兩個整數x,y,用一個空格隔開,x為要刪去的點,y為刪除x後圖中的最長路徑的長度,如果有多組解請輸出任意一組。
Sample Input
6 5
1 3
1 4
3 6
3 4
4 5
Sample Output
1 2


s o l u t i o n solution


思路神題
這題網上題解都是 O ( n l o g n )
O(nlogn)
的,加點科技就可以近乎 O ( n ) O(n)
先講一下 n l o g n nlogn 的做法
考慮一個點的答案

先求出 f i f_i 表示以 i i 為終點的最長鏈, g i g_i 表示以 i i 為起點的最長鏈

一條最長鏈肯定可以通過一條邊表示為 f i + g j + 1 f_i+g_j+1
刪掉一個點的答案也可以表示為上面那個式子

不難發現一個點的答案可以被所有跨過他的邊更新

於是就可以考慮用拓撲排序來做

也就是對於一條邊 i j i\rightarrow j
可以更新 t o p [ i ] t o p [ j ] top[i]\sim top[j] 的所有點(其中 t o p [ x ] top[x] 表示的是 x x 這個點在拓撲序中的標號)

用個 s e t set 維護一下
對於邊 i j i\rightarrow j i i 加入, j j 刪除
用堆和線段樹也可以維護

注意 a n s ans 只取 f i , g i f_i,g_i 的情況

這個做法只要加一點點科技就可以近似 O ( n ) O(n)
我們把一條邊的影響查分一下,用 v e c t o r vector 存下來,再開個 b i t s e t bitset 存一下當前的合法解
搞一下就好了
O ( n l o g n ) O(nlogn) 程式碼:

#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i = j;i <= k;++i)
#define repp(i,j,k) for(int i = j;i >= k;--i)
#define rept(i,x) for(int i = linkk[x],y = e[i].y;i;i = e[i].n,y = e[i].y)
#define reptt(i,x) for(int i = llinkk[x],y = ee[i].y;i;i = ee[i].n,y = ee[i].y)
#define P pair<int,int>
#define Pil pair<int,ll>
#define Pli pair<ll,int>
#define Pll pair<ll,ll>
#define pb push_back 
#define pc putchar
#define mp make_pair
#define file(k) memset(k,0,sizeof(k))
#define ll long long
int rd()
{
	int num = 0;char c = getchar();bool flag = true;
	while(c < '0'||c > '9') {if(c == '-') flag = false;c = getchar();}
	while(c >= '0' && c <= '9') num = num*10+c-48,c = getchar();
	if(flag) return num;else return -num;
}
int n,m,S,T;
int linkk[501000],t,llinkk[501000],tt;
int f[501000],g[501000],ru[501000],chu[501000];
int q[501000],head,tail,tp[501000],tot;
struct node{int n,y;}e[2010000],ee[2010000];
inline int max(int a,int b){return a>b?a:b;}
void insert(int x,int y)
{
	e[++t].y = y;e[t].n = linkk[x];linkk[x] = t;chu[x]++;
	ee[++tt].y = x;ee[t].n = llinkk[y];llinkk[y] = tt;ru[y]++;
}
void init()
{
	n = rd();m = rd();S = n+1;T = n+2;
	rep(i,1,m)
	{
		int x = rd(),y = rd();
		insert(x,y);
	}
	rep(i,1,n) insert(S,i),insert(i,T);
}
void topsort()
{
	q[0] = S;
	while(head<=tail)
	{
		int x = q[head++];
		rept(i,x)
		{
			f[y] = max(f[y],f[x]+1);
			ru[y]--;
			if(ru[y] == 0) q[++tail] = y;
		}
	}
	tot = tail-1;rep(i,1,tot) tp[i] = q[i];
	head = tail = 0;q[0] = T;
	while(head <= tail)
	{
		int x = q[head++];
	    reptt(i,x)
		{
			g[y] = max(g[y],g[x]+1);
			chu[y]--;
			if(chu[y] == 0) q[++tail] = y;
	    }
	}
	rep(i,1,n) f[i]--,g[i]--;
}
multiset<int>h;
int main()
{
	init();
	topsort();
	int ans = n+2,k = 0;
	rep(i,1,tot) h.insert(g[i]);
	rep(i,1,tot)
	{
		int x = tp[i];
		h.erase(h.lower_bound(g[x]));
		reptt(i,x) if(y != S) h.erase(h.lower_bound(f[y]+g[x]+1));
		multiset<int>::iterator it = h.end();
		if(it != h.begin())
		{
			it--;
			int tmp = (*it);
			if(tmp < ans) ans = tmp,k = x;
		}
	    rept(i,x)  if(y != T) h.insert(f[x]+g[y]+1);
	    h.insert(f[x]);
	}
	printf("%d %d\n",k,ans);
	return 0;
}