P12704 Retribution

P12704 Retribution

我也不知道为什么能过做法。

考虑暴力缩点,然后做线段树合并。

细节上,由于要在可持久化线段树上合并,所以每次要新开节点,在合并的时候多剪枝减少栈调用和新开节点。
如果尝试将询问离线挂在每个 SCC 上的话,\(10^6\) 的无序 vector 应该还不如存新节点。

如果乱开东西大概率空间会炸。
一共有 \(10^6\) 个 SCC,注意到线段树上实际有非常多节点都连向同一个节点每次新建的点并不会很多。
实际精细计算一下空间只要 \(9\times 10^7\) 个 int 就行。千万不要乱开 vector。

留下不到一百兆给栈调用和临时的队列啥的应该是差不多够了,事实上对于数据是刚刚好。

代码

const int N = 1510,LG = 21;
int n,m,q,seed;
char s[N][N];
int dx[4] = {-1,+1,+0,+0};
int dy[4] = {+0,+0,-1,+1};
inline int id(int x,int y){
	return m*(x-1)+y;
}
struct{
	int to,nex;
}edge[N*N*3];
int head[N*N],edge_num,ind[N*N];
inline void add(int x,int y){
	edge[++edge_num].to = y;
	edge[edge_num].nex = head[x];
	head[x] = edge_num;
}
int dfn[N*N],low[N*N],st[N*N],id_scc[N*N],dtop,stop,col_cnt;
inline void tarjan(int now){
	dfn[now] = low[now] = ++dtop;
	st[++stop] = now;
	for(int i=head[now];i;i=edge[i].nex){
		int tto = edge[i].to;
		if(!dfn[tto]){
			tarjan(tto);
			low[now] = Min(low[tto],low[now]);
		}
		else if(!id_scc[tto]) low[now] = Min(low[now],dfn[tto]);
	}
	if(low[now]==dfn[now]){
		++col_cnt;
		while(st[stop]^now){
			id_scc[st[stop]] = col_cnt;
			--stop;
		}
		id_scc[st[stop]] = col_cnt;
		--stop;
	}
	head[now] = 0;
}
struct node{
	int ls,rs;
}tr[N*N*LG];
int rt[N*N],cnt;
inline int merge(int u,int v,int nl,int nr){
	if(!u || !v) return u|v;
	if(u==v) return u;
	int p = ++cnt;
    if(nl==nr){
		tr[p].ls = tr[u].ls | tr[v].ls;
        return p;
    }
    int mid = (nl+nr) >> 1;
    tr[p].ls = merge(tr[u].ls,tr[v].ls,nl,mid);
    tr[p].rs = merge(tr[u].rs,tr[v].rs,mid+1,nr);
    return p;
}
inline void insert(int &p,int nl,int nr,int x){
	if(!p) p  = ++cnt;
	int mid = (nl+nr) >> 1;
	if(nl==nr){
		tr[p].ls = 1;
		return ;
	}
	if(x<=mid) insert(tr[p].ls,nl,mid,x);
	else insert(tr[p].rs,mid+1,nr,x);
}
inline int query(int p,int nl,int nr,int x){
	if(!p) return 0;
	int mid = (nl+nr) >> 1;
	if(nl==nr) return tr[p].ls;
	if(x<=mid) return query(tr[p].ls,nl,mid,x);
	else return query(tr[p].rs,mid+1,nr,x);
}
inline void topo(){
	queue <int> q;
	for(int i=1;i<=col_cnt;++i){
		insert(rt[i],1,col_cnt,i);
		if(!ind[i]) q.push(i);
	}
	while(!q.empty()){
		int now = q.front();
		q.pop();
		for(int i=head[now];i;i=edge[i].nex){
			int tto = edge[i].to;
			rt[tto] = merge(rt[tto],rt[now],1,col_cnt);
			--ind[tto];
			if(!ind[tto]) q.push(tto);
		}
	}
}
int main(){
	// freopen("in.in","r",stdin);
    // freopen("out.out","w",stdout);

	read(n,m,q,seed);
	init(seed);
	for(int i=1;i<=n;++i){
		scanf("%s",s[i]+1);
		for(int j=1;j<=m;++j){
			for(int t=0;t<4;++t){
				if(s[i][j]=='U' && t==0) continue;
				if(s[i][j]=='D' && t==1) continue;
				if(s[i][j]=='L' && t==2) continue;
				if(s[i][j]=='R' && t==3) continue;
				int tx = i+dx[t];
				int ty = j+dy[t];
				if(tx<1 || tx>n || ty<1 || ty>m) continue;
				add(id(i,j),id(tx,ty));
			}
		}
	}
	for(int i=1;i<=n;++i){
		for(int j=1;j<=m;++j){
			if(!dfn[id(i,j)]) tarjan(id(i,j));
		}
	}
	edge_num = 0;
	for(int i=1;i<=n;++i){ 
		for(int j=1;j<=m;++j){
			for(int t=0;t<4;++t){
				if(s[i][j]=='U' && t==0) continue;
				if(s[i][j]=='D' && t==1) continue;
				if(s[i][j]=='L' && t==2) continue;
				if(s[i][j]=='R' && t==3) continue;
				int tx = i+dx[t];
				int ty = j+dy[t];
				if(tx<1 || tx>n || ty<1 || ty>m) continue;
				if(id_scc[id(i,j)]^id_scc[id(tx,ty)]){
					add(id_scc[id(tx,ty)],id_scc[id(i,j)]);
					++ind[id_scc[id(i,j)]];
				}
			}
		}
	}
	topo();
	for(int i=1,a,b,x,y;i<=q;++i){
		a = get(1,n); b = get(1,m); x = get(1,n); y = get(1,m);
		query(rt[id_scc[id(a,b)]],1,col_cnt,id_scc[id(x,y)]) ? putchar('1') : putchar('0');
	}

	// fclose(stdin);
	// fclose(stdout);
	return 0;
}

后话

机房里大家都觉得这种东西非常假(我也觉得很假,但是就是写了然后过了),但实测这玩意跑得飞快。

我们在做合并的时候,只要发现处于同一个点就可以直接退出。而题里给出的图,会使得有非常多的线段树共用了同一个节点,合并复杂度事实上非常跑不满。

根据我本人精心构造的数据以及对数据点的判断,线段树新开的节点实际非常少大概在 \(O(n^2\log{n^2})\),合并次数大概在 \(O(n\log^2n)\) 级别。这是绰绰有余的。

posted @ 2025-10-01 11:52  Tmbcan  阅读(13)  评论(0)    收藏  举报