[LOJ2838][JOISC 2018 Day 3]比太郎的聚会(分块暴力)

题意

  给定一张 n n n个点 m m m条边的DAG,保证所有边都是从编号小的点往编号大的点连,给 q q q次询问,每次询问给出一个点和一个点集,点集大小为 k k k,询问对于可达这个点的除该点集外的最大距离是多少。
   ( n ≤ 1 e 5 , m ≤ 2 e 5 , q ≤ 1 e 5 , ∑ k ≤ 1 e 5 ) (n\le 1e5,m\le 2e5,q\le1e5,\sum k\le 1e5) (n1e5,m2e5,q1e5,k1e5)

分析

  一开始我口胡了线段树合并的算法,写完之后发现假掉了,然后就自闭了好久。后来我发现,这题的 ∑ k \sum k k最大只有 1 e 5 1e5 1e5,所以他如果每次询问的点集大小很大的话,那询问次数就很少,如果每次询问的点集大小很小的话,那询问次数才会到达 1 e 5 1e5 1e5级别。于是我们可以自然而然想到这么一个算法,设 B = n B=\sqrt n B=n ,然后我们对于每个点,预处理出可到达它的所有点的距离从大到小排序的前 B B B个点,由于边数只有 2 e 5 2e5 2e5级别,那么我们每次可以对于每条边转移时暴力 O ( B ) O(B) O(B)转移,来得到所有点的前 B B B大距离。那么如果 k &lt; B k&lt;B k<B的话,说明他除去的点不足以把这 B B B个点都去除,所以我们可以暴力从大到小找到第一个没有被删去的点,这个点的距离就是答案。如果 k ≥ B k\ge B kB的话,说明这样的询问次数最多只有 1 e 5 / B 1e5/B 1e5/B次,那么我们可以直接暴力跑图找答案。这样复杂度均摊就是 q ∗ B = q n q*B=q\sqrt n qB=qn 的,足以通过此题。

Code

#pragma GCC optimize(3,"Ofast","inline")
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
bool Finish_read;
template<class T>inline void read(T &x){Finish_read=0;x=0;int f=1;char ch=getchar();while(!isdigit(ch)){if(ch=='-')f=-1;if(ch==EOF)return;ch=getchar();}while(isdigit(ch))x=x*10+ch-'0',ch=getchar();x*=f;Finish_read=1;}
template<class T>inline void print(T x){if(x/10!=0)print(x/10);putchar(x%10+'0');}
template<class T>inline void writeln(T x){if(x<0)putchar('-');x=abs(x);print(x);putchar('\n');}
template<class T>inline void write(T x){if(x<0)putchar('-');x=abs(x);print(x);}
/*================Header Template==============*/
const int maxn=100005,maxs=400;
int n,m,q,cnt[maxn],fx[maxn][maxs][2],clk[maxn],f[maxn],fxx,fy[maxs][2];
vector<int>G[maxn];
int main() {
	read(n),read(m),read(q);
	for(int i=0,u,v;i<m;++i)
		read(u),read(v),G[v].push_back(u);
	for(int u=1;u<=n;++u) {
		cnt[u]=1,fx[u][0][0]=0,fx[u][0][1]=u;
		for(auto v:G[u]) {
//			cerr<<"Edge "<<v<<" -> "<<u<<endl;
			++fxx;
			int l=0,r=0,tot=0;
			for(;l<cnt[u]&&r<cnt[v]&&tot<maxs;) {
				while(l<cnt[u]&&clk[fx[u][l][1]]==fxx)
					++l;
				while(r<cnt[v]&&clk[fx[v][r][1]]==fxx)
					++r;
				if(l>=cnt[u]||r>=cnt[v])
					break;
//				cerr<<"Now == : l="<<l<<": "<<fx[u][l][0]<<" "<<fx[u][l][1]<<" r="<<r<<": "<<fx[v][r][0]<<" "<<fx[v][r][1]<<endl;
				if(fx[u][l][0]>fx[v][r][0])
					fy[tot][0]=fx[u][l][0],
					fy[tot][1]=fx[u][l][1],
					clk[fx[u][l][1]]=fxx,
					++l,
					++tot;
				else
					fy[tot][0]=fx[v][r][0]+1,
					fy[tot][1]=fx[v][r][1],
					clk[fx[v][r][1]]=fxx,
					++r,
					++tot;
			}
//			cerr<<cnt[u]<<" "<<cnt[v]<<" "<<l<<" "<<r<<" "<<tot<<endl;
			for(;l<cnt[u]&&tot<maxs;++l)
				if(clk[fx[u][l][1]]!=fxx)
					fy[tot][0]=fx[u][l][0],
					fy[tot][1]=fx[u][l][1],
					clk[fx[u][l][1]]=fxx,
					++tot;
			for(;r<cnt[v]&&tot<maxs;++r)
				if(clk[fx[v][r][1]]!=fxx)
					fy[tot][0]=fx[v][r][0]+1,
					fy[tot][1]=fx[v][r][1],
					clk[fx[v][r][1]]=fxx,
					++tot;
//			cerr<<cnt[u]<<" "<<cnt[v]<<" "<<l<<" "<<r<<" "<<tot<<endl;
			cnt[u]=tot;
			for(int i=0;i<tot;++i)
				fx[u][i][0]=fy[i][0],fx[u][i][1]=fy[i][1];
		}
	}
//	cerr<<"Now =========================== "<<u<<endl;
//	for(int i=1;i<=n;++i) {
//		printf("Index = %d, Count %d:\n",i,cnt[i]);
//		for(int j=0;j<cnt[i];++j)
//			printf("Des = %d, Dis = %d\n",fx[i][j][1],fx[i][j][0]);
//	}
	for(int i=0,t,y;i<q;++i) {
		++fxx;
		read(t),read(y);
		for(int j=0,x;j<y;++j)
			read(x),clk[x]=fxx;
		int ans=-1;
		if(y<=maxs) {
			for(int j=0;j<cnt[t];++j)
				if(clk[fx[t][j][1]]!=fxx) {
					ans=fx[t][j][0];
					break;
				}
		}
		else {
			for(int j=1;j<=t;++j) {
				f[j]=clk[j]==fxx?-1e9:0;
				for(auto v:G[j])
					f[j]=max(f[j],f[v]+1);
			}
			ans=max(ans,f[t]);
		}
		printf("%d\n",ans);
	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值