题意:
给出一个n个点,m条边的dag,每个点有点权
ai
,初始均为0。有q种操作,每种操作有3种类型:
1 u x:将所有u能到达的点的权值设为x。
2 u x:将所有u能到达的点的权值对x取min。
3 u:查询u的点权。
n,m,q≤105,n≥2,m,q≥1,1≤u≤n,0≤x≤109
又用bitset强上了一道题好开心啊~(然而这题标算似乎就是bitset?)
如果暴力的话,可以用bitset,但是空间爆炸;所以我们先解决一下空间问题。
如果考虑对操作分块的话,我们可以只用处理所有节点能否由块内的操作节点抵达,这样首先就可以解决bitset的空间问题!
然后一开始我是这样想的:
如果我们可以
O(n)
处理出每一个块的操作结束后的
ai
的话,那么就好办了!而这个似乎很容易处理的样子。然后就各种YY,写了一发,但是搞了半天,发现实在是没法搞。。这个覆盖操作和取min操作在一起根本做不了。
但是我们仔细考虑一下,对u的询问的答案是能到达它的最后一个1操作的x和之间所有的2操作的x最小值。这样的话其实我们没必要处理出每个块的最终状态。我们只需要找到这最后一个1在哪个块里,这是可以对每个块用一个
O(n)
的类似于dp的东西处理出来的,于是两边的块便都可以
O(n√)
暴力(在用bitset处理过连通性以后);中间的块只需要算出每个节点在这个块中会得到的2操作的最小值是多少即可,这个玩意儿也可以用一个类似
O(n)
dp的东西搞出来。
时间复杂度
O(nn√+n264)
看了题解,发现它是这么解决空间问题的:把所有的节点分成3份,对于暴力部分计算3次——这时我才突然明白,bitset可以开到 109 !
发现不知不觉似乎已经用这种类似对时间分块的方法a了很多题了。。而且做法主要是有两种,一种是处理出每个块的最终状态,这种适用于那种很多操作一起处理比较方便的,一种是查询的是一个点,那么我们可以分块找出这个点。
似乎如果允许离线,而且本来的点与点之间的关系比较奇怪,对时间分块还是一个不错的搞法呢~
代码:
#include<stdio.h>
#include<iostream>
using namespace std;
#include<algorithm>
#include<cstring>
#include<bitset>
char * cp=(char *)malloc(4000000);
inline void in(int &x)
{
while(*cp<'0'||*cp>'9')++cp;
for(x=0;*cp>='0'&&*cp<='9';)x=x*10+(*cp++^'0');
}
const int N=1e5+5,M=1e5+5,Q=1e5+5;
int nxt[M],succ[M],ptr[N],etot=1;
inline void addedge(int u,int v)
{
nxt[etot]=ptr[u],ptr[u]=etot,succ[etot++]=v;
}
bool exist_deg[N];
int indeg[N];
int topo[N],ttot=1;
void topo_dfs(int node)
{
topo[ttot++]=node;
for(int i=ptr[node];i;i=nxt[i])
if(--indeg[succ[i]]==0)
topo_dfs(succ[i]);
}
struct PS
{
int opt,u,x,i;
}perform[Q];
struct QS
{
int u,i;
}que[Q];
int ans[Q];
const int B=316;
//const int B=4;
bitset<320> b[N];
bool cover[N];
int Min[N];
int main()
{
freopen("dagq.in","r",stdin);
freopen("dagq.out","w",stdout);
fread(cp,1,4000000,stdin);
int n,m,q;
in(n),in(m),in(q);
for(int u,v;m--;)
{
in(u),in(v);
addedge(u,v);
exist_deg[v]=1;
++indeg[v];
}
for(int i=n;i;--i)
if(!exist_deg[i])
topo_dfs(i);
int ptot=0,qtot=0;
for(int i=1;i<=q;++i)
{
in(perform[ptot].opt);
if(perform[ptot].opt<=2)
{
in(perform[ptot].u),in(perform[ptot].x);
perform[ptot++].i=i;
}
else
{
in(que[qtot].u);
que[qtot++].i=i;
}
}
memset(ans,127,sizeof(ans));
//printf("ptot=%d,qtot=%d\n",ptot,qtot);
for(int l=(ptot-1)-(ptot-1)%B,r=ptot-1;l>=0;r=l-1,l-=B)
{
/*printf("---[%d,%d]:[%d,%d]---\n",l,r,perform[l].i,perform[r].i);
printf("perform=");
for(int i=l;i<=r;++i)printf("(opt=%d,u=%d,x=%d,i=%d) ",perform[i].opt,perform[i].u,perform[i].x,perform[i].i);
puts("");*/
for(int i=n;i;--i)b[i].reset();
for(int i=l;i<=r;++i)b[perform[i].u][i-l]=1;
for(int i=1;i<=n;++i)
for(int j=ptr[topo[i]];j;j=nxt[j])
b[succ[j]]|=b[topo[i]];
/*for(int i=1;i<=n;++i)
{
printf("%d:",topo[i]);
for(int j=0;j<=r-l;++j)printf("%d",(int)b[topo[i]][j]);
puts("");
}*/
memset(cover,0,sizeof(cover));
for(int i=l;i<=r;++i)
if(perform[i].opt==1)
cover[perform[i].u]=1;
for(int i=1;i<=n;++i)
if(cover[topo[i]])
for(int j=ptr[topo[i]];j;j=nxt[j])
cover[succ[j]]=1;
memset(Min,127,sizeof(Min));
for(int i=l;i<=r;++i)
if(perform[i].opt==2)
Min[perform[i].u]=min(Min[perform[i].u],perform[i].x);
for(int i=1;i<=n;++i)
for(int j=ptr[topo[i]];j;j=nxt[j])
Min[succ[j]]=min(Min[succ[j]],Min[topo[i]]);
{
int i=qtot;
while(i&&que[i-1].i>perform[l].i)--i;
while(i<qtot)
{
//printf("Q:u=%d,i=%d,now=%d\n",que[i].u,que[i].i,ans[que[i].i]);
if(que[i].i<perform[r].i)
{
int j=r;
while(perform[j].i>que[i].i)--j;
for(;j>=l;--j)
if(b[que[i].u][j-l])
{
ans[que[i].i]=min(ans[que[i].i],perform[j].x);
if(perform[j].opt==1)
{
swap(que[i],que[--qtot]);
break;
}
}
i+=j<l;
}
else
if(cover[que[i].u])
{
int j=r;
for(;perform[j].opt==2||!b[que[i].u][j-l];--j)
if(perform[j].opt==2&&b[que[i].u][j-l])
ans[que[i].i]=min(ans[que[i].i],perform[j].x);
ans[que[i].i]=min(ans[que[i].i],perform[j].x);
swap(que[i],que[--qtot]);
}
else
{
ans[que[i].i]=min(ans[que[i].i],Min[que[i].u]);
++i;
}
}
}
}
while(qtot--)ans[que[qtot].i]=0;
for(int i=1;i<=q;++i)
if(ans[i]<=1e9)
printf("%d\n",ans[i]);
}
总结:
①一定要想好细节再写代码!
②bitset占的内存是
18
,char/bool是1,int是4,long long是8。所以bitset一般能开
109
,char/bool能开
108
,int能开
107/108
,long long能开
107
。
③对时间分块:对每个块处理出最终状态;操作对查询的影响是一个关于时间的点/区间。