[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
#define TT template
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 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,嘿嘿,以后可以乱搞咯。