[20220305联考] ⼩ G 的 DAG

前言

好嘛,这种奇奇怪怪的题虽然一眼看过去就应该分块,但是完全不知道该怎么分。

以前没做过操作分块的,这次长见识了。

题目

没有链接

\(\operatorname{512MiB,2s}\)

给一个 \(n\) 个点 \(m\) 条边的 DAG,每个点点权初始为 0,\(q\) 个操作,类型分别是:

  • \(1\ u\ x\),把所有 \(u\) 可以到达的点的点权设为 \(x\)

  • \(2\ u\ x\),把所有 \(u\) 可以到达的点的点权与 \(x\)\(\operatorname{min}\)

  • \(3\ u\),询问 \(u\) 的点权。

\(1\le n,m,q\le 10^5;0\le x\le 10^9.\)

部分分懒得抄过来了。

讲解

前言里面已经提到了,这题的做法是对操作分块。

对于操作一,每达到 \(B\) 个就 \(O(n)\) 扫,每个操作需要记录时间戳,复杂度 \(O(n\sqrt{n})\)

对于操作二,每达到 \(B\) 个就 \(O(n)\) 扫,需要记录整块对 \(n\) 个点的 \(\operatorname{min}\),当然时间戳也需要记录。

对于操作三,也就是询问,首先我们需要找出最后一个对它有影响的操作一,但是我们发现这里需要知道其它点能否到达询问点才能做,在 DAG 上其实就是传递闭包,没啥好说的,直接 bitset,但是时间复杂度很离谱,是 \(O(\frac{n^2}{\omega})\) ,而且直接预处理是空间也是这么多,存不下,因此还要分块!对询问分块,每次空间就是 \(O(\frac{n\sqrt{n}}{\omega})\) 的。

找到最后一个操作一之后,再去查一个时间段内对这个点有影响的操作二即可

复杂度瓶颈竟然是 \(O(\frac{n^2}{\omega})\),而且这个一脸不能过的样子。

好吧,所以最终复杂度是 \(O(n\log_2n+n\sqrt{n}+\frac{n^2}{\omega})\),反正是 \(O\),管我怎么写。

这题一共用了三个分块,妥妥能过。

代码

玄学调块长,否则要加 Ofast 才能过。
//12252024832524
#pragma GCC optimize("Ofast")
#include <bits/stdc++.h>
#define TT template<typename T>
using namespace std; 

typedef long long LL;
const int MAXN = 100005;
const int MAXB = 355;
const int B = 350;
const int INF = 0x3f3f3f3f;
int n,m,q;

LL Read()
{
	LL x = 0,f = 1;char c = getchar();
	while(c > '9' || c < '0'){if(c == '-')f = -1;c = getchar();}
	while(c >= '0' && c <= '9'){x = (x*10) + (c^48);c = getchar();}
	return x * f;
}
TT void Put1(T x)
{
	if(x > 9) Put1(x/10);
	putchar(x%10^48);
}
TT void Put(T x,char c = -1)
{
	if(x < 0) putchar('-'),x = -x;
	Put1(x); if(c >= 0) putchar(c);
}
TT T Max(T x,T y){return x > y ? x : y;}
TT T Min(T x,T y){return x < y ? x : y;}
TT T Abs(T x){return x < 0 ? -x : x;}

int head[MAXN],tot;
struct edge{
	int v,nxt;
}e[MAXN];
void Add_Edge(int u,int v){
	e[++tot] = edge{v,head[u]};
	head[u] = tot;
}

int op1[MAXB][MAXN],tim[MAXB][MAXN],t1,t2,t3,o1t,vis1[MAXN],o2t;
struct operation{
	int u,x,t;
	bool operator < (const operation &px)const{
		return x < px.x;
	}
}o1[MAXB],o2[MAXB],o3[MAXB];
void dfs1(int x,int val,int t){
	if(vis1[x] == o1t) return;
	op1[o1t][x] = val; tim[o1t][x] = t; vis1[x] = o1t;
	for(int i = head[x]; i ;i = e[i].nxt)
		dfs1(e[i].v,val,t);
}
operation g1[MAXB][MAXB];
void up1(){
	++o1t;
	for(int i = B;i >= 1;-- i) dfs1(o1[i].u,o1[i].x,o1[i].t),g1[o1t][i] = o1[i];
	t1 = 0;
}
int MIN[MAXB][MAXN];
operation g2[MAXB][MAXB];
void dfs2(int x,int val){
	if(val >= MIN[o2t][x]) return;
	MIN[o2t][x] = val;
	for(int i = head[x]; i ;i = e[i].nxt) dfs2(e[i].v,val);
}
void up2(){
	++o2t;
	for(int i = 1;i <= n;++ i) MIN[o2t][i] = INF;
	for(int i = 1;i <= B;++ i) g2[o2t][i] = o2[i];
	sort(o2+1,o2+B+1);
	for(int i = 1;i <= B;++ i) dfs2(o2[i].u,o2[i].x);
	t2 = 0;
}
bitset<MAXB> rch[MAXN];//reachable
int vis3[MAXN],ID[MAXN],tag[MAXN],trans,o3t;
void dfs3(int x){
	vis3[x] = o3t; if(tag[x] == o3t) rch[x][ID[x]] = 1;
	for(int i = head[x],v; i ;i = e[i].nxt){
		if(vis3[v = e[i].v] ^ o3t) dfs3(v);
		rch[x] |= rch[v];
	}
}
void up3(){
	++o3t; trans = 0;
	for(int i = 1;i <= t3;++ i) {
		tag[o3[i].u] = o3t;
		ID[o3[i].u] = ++trans;
	}
	for(int i = 1;i <= n;++ i) rch[i].reset();
	//可略微优化 懒惰dfs3 
	for(int i = 1;i <= n;++ i) if(vis3[i] ^ o3t) dfs3(i);
	for(int i = 1;i <= t3;++ i){
		int now = o3[i].u,lst = 0,vv = 0,nowt = o3[i].t;
		for(int j = t1;j >= 1;-- j)//操作一 散块 
			if(o1[j].t < nowt && rch[o1[j].u][ID[now]]){
				lst = o1[j].t; vv = o1[j].x;
				break;
			}
		for(int j = o1t;j >= 1 && !lst;-- j)//操作一 整块
			if(g1[j][1].t < nowt && tim[j][now]){
				for(int k = B;k >= 1;-- k)
					if(g1[j][k].t < nowt && rch[g1[j][k].u][ID[now]])
						{lst = g1[j][k].t,vv = g1[j][k].x;break;}
				if(!lst){//dn[j][now] time > nowt
					for(int k = j-1;k >= 1;-- k)
						if(tim[k][now]){
							lst = tim[k][now];
							vv = op1[k][now];
							break;
						}
				}
				break;
			}
		if(!vv) {Put(0,'\n');continue;}
		//操作二 散块
		for(int j = t2;j >= 1 && o2[j].t > lst;-- j) if(o2[j].t < nowt && rch[o2[j].u][ID[now]]) vv = Min(vv,o2[j].x);
		//可略微优化 
		//操作二 整块
		for(int j = o2t;j >= 1;-- j){
			if(g2[j][1].t < nowt){
				for(int k = 1;k <= B && g2[j][k].t < nowt;++ k) if(g2[j][k].t > lst && rch[g2[j][k].u][ID[now]]) vv = Min(vv,g2[j][k].x);
				for(int k = j-1;k >= 1;-- k){
					if(g2[k][1].t > lst) vv = Min(vv,MIN[k][now]);
					else{
						for(int l = B;l >= 1 && g2[k][l].t > lst;-- l) if(rch[g2[k][l].u][ID[now]]) vv = Min(g2[k][l].x,vv);
						break;
					}
				} 
				break;
			}
		}
		Put(vv,'\n');
	}
	t3 = 0;
}

int main()
{
	freopen("dag.in","r",stdin);
	freopen("dag.out","w",stdout);
	n = Read(); m = Read(); q = Read();
	for(int i = 1,u;i <= m;++ i) u = Read(),Add_Edge(u,Read());
	for(int T = 1;T <= q;++ T){
		int opt = Read(),u = Read();
		if(opt == 1) {
			o1[++t1] = operation{u,Read(),T};
			if(t1 == B) up1();
		}
		else if(opt == 2){
			o2[++t2] = operation{u,Read(),T};
			if(t2 == B) up2();
		}
		else if(opt == 3) {
			o3[++t3] = operation{u,0,T};
			if(t3 == B) up3();
		}
	}
	if(t3) up3();
	return 0;
}
/*
操作一:分块,到上限之后从后往前更新,顺便记录一下操作时间
操作二:分块,散块就放着就行,整块维护所有点,类似操作一 
操作三:分块,一块一块做,对于操作一,整块直接查,然后我们需要维护每个点是否能到操作三块的点;
		有了这个信息可以更新操作一的散块,找到最后的操作时间后可以暴力散块,整块直接查 
*/

后记

联考的 OJ 可以手动开 Ofast,嘿嘿,以后可以乱搞咯。

posted @ 2022-03-06 11:39  皮皮刘  阅读(56)  评论(0编辑  收藏  举报