LOJ bitset+分块 大内存毒瘤题
题面
$ solution: $
真的没有想到可以用分块。
但是可以发现一个性质,每个询问只关心这个点最后一次赋值操作,和这个赋值操作后的所有取 $ min $ 操作。这个感觉很有用,但是真的很难让人想到低于 $ n\times m $ 的做法。基于 $ DAG $ 的数据结构是目前很少需要掌握的(好吧我都不知道有什么数据结构可以维护 $ DAG $ )所以肯定得骚操作。
我们可以发现一个 $ DAG $ 的性质,如果有一连串赋值操作我们可以根据拓扑序 $ O(n) $ 将所有操作完成,直接按顺序从后往前赋值,这样每个点赋值之后就不会再被访问。同理的, 如果有一连串取 $ min $ 操作我们也可以根据拓扑序 $ O(n) $ 将所有操作完成,直接 $ min $ 值从小到大取 $ min $, 这样每个点在取 $ min $ 之后就不会再被访问。但是当我们将这两种操作合到一起时就不行了。
但是联想一下上面说的性质:每个询问只关心这个点最后一次赋值操作,和这个赋值操作后的所有取 $ min $ 操作。我们可以搞出一个分块来,先预处理2操作,将2操作序列分块,并将每一块用上面的方法统计出每个结点在每个块内的取 $ min $ 后的值(初值inf)。然后我们就可以 $ \sqrt{n} $ 的求出任意一个区间里某个节点取 $ min $ 的最小值(其实还需要一个操作)。然后我们只需要快速找到每个询问的节点的最后一次赋值操作的编号,即赋值的大小,就可以得到答案。找到这个编号,我们可以对1操作分块来完成。
但是上述操作我们还需要知道一个东西,因为分块两边的小区间是要暴力遍历的,这个我们需要知道每个操作能否对某个点产生影响,这个等同于我们要知道 $ DAG $ 中一个点能否到另一个点。这个很奇妙的我们可以用 $ bitset $ 暴力完成。因为这个是无法用低于 $ n\times m $ 的复杂度完成,但是只涉及能否我们可以用二进制。
- 仔细分析求得答案需要什么关键信息
- 对于一连串操作可以一次完成,就考虑分治或分块
- 对于两种操作会互相影响,考虑先预处理一种操作在进行第二种操作
- 二进制和是与否,这个对于复杂度优化很好用。
- $ DAG $ 中的一些问题是难以用低于 $ n\times m $ 的做法完成的!
$ code: $
#include<iostream>
#include<cstdio>
#include<iomanip>
#include<algorithm>
#include<cstring>
#include<cstdlib>
#include<ctime>
#include<cmath>
#include<vector>
#include<queue>
#include<map>
#include<set>
#include<bitset>
#define ll long long
#define db double
#define rg register int
using namespace std;
int tt,t=1;
int n,m,q,ff;
int a[100005];
int idx[100005];
int fq[100005];
int fm[100005][404];
int vis[100005];
bitset<100005> f[100005];
struct su{
int to,next;
}b[200005];
int tou[100005];
struct pi{
int id,x,v,op;
inline bool operator <(const pi &i)const{
return v<i.v;
}
}s[100005],k[100005];
inline int qr(){
register char ch; register bool sign=0; rg res=0;
while(!isdigit(ch=getchar()))if(ch=='-')sign=1;
while(isdigit(ch))res=res*10+(ch^48),ch=getchar();
if(sign)return -res; else return res;
}
inline void yu(int i){ //预处理dag中两点是否可达
vis[i]=1; f[i][i]=1;
for(rg j=tou[i];j;j=b[j].next){
if(!vis[b[j].to]) yu(b[j].to);
f[i]|=f[b[j].to];
}
}
inline void dfs(int i,int v,int time,bool op){ //修改操作
if(vis[i]==t)return ; else vis[i]=t; //根据op判断是1操作还是2操作
if(op) a[i]=v,idx[i]=time; else fm[i][time]=v;
for(rg j=tou[i];j;j=b[j].next){
if(vis[b[j].to]==t)continue;
dfs(b[j].to,v,time,op);
}
}
int main(){
//freopen("dag.in","r",stdin);
//freopen("dag.out","w",stdout);
n=qr(); m=qr(); q=qr(); ff=sqrt(q-1)+1;
for(rg i=1;i<=q;++i) fq[i]=(i-1)/ff+1; //分块
for(rg i=1;i<=m;++i){
rg x=qr(),y=qr();
b[i]=su{y,tou[x]}; tou[x]=i;
}
for(rg i=1;i<=n;++i) if(!vis[i])yu(i);
for(rg i=1;i<=q;++i){ //先预处理每个块内的2操作
rg op=qr(); s[i]=pi{i,qr(),0,op};
if(op<=2) s[i].v=qr();
if(op==2) k[++tt]=s[i];
if(fq[i]!=fq[i+1]){
sort(k+1,k+tt+1); ++t; //从小到大,保证每个点只修改一次
for(rg j=1;j<=n;++j) fm[j][fq[i]]=1e9; //赋初值
for(rg j=1;j<=tt;++j)
dfs(k[j].x,k[j].v,fq[i],0);
tt=0;
}
}
for(rg i=1;i<=q;++i){
if(s[i].op==3){
rg x=s[i].x,y=idx[x],v=a[x],sf=0;
for(rg j=i;fq[j]==fq[i];--j){ //在同一个块内,暴力处理
if(s[j].op==1&&f[s[j].x][x]){ sf=1; y=j; v=s[j].v;
for(rg o=y;o<=i;++o)
if(s[o].op==2&&f[s[o].x][x]) v=min(v,s[o].v);
break;
}
}
if(!sf){ //这个if调了半个上午
for(rg j=y+1;fq[j]==fq[y];++j) //前小块
if(s[j].op==2&&f[s[j].x][x]) v=min(v,s[j].v);
for(rg j=fq[y]+1;j<=fq[i]-1;++j) v=min(v,fm[x][j]); //中间的大块
for(rg j=i;fq[j]==fq[i];--j) //后小块
if(s[j].op==2&&f[s[j].x][x]) v=min(v,s[j].v);
}
printf("%d\n",v);
}
if(fq[i]!=fq[i+1]){ ++t; //将这个块内的1操作一遍做完
for(rg j=i;fq[j]==fq[i];--j)
if(s[j].op==1)dfs(s[j].x,s[j].v,j,1);
}
}
return 0;
}