[DarkBZOJ4212]神牛的养成计划

题目

传送门 to DarkBZOJ

思路

其实想清楚了,就感觉这个题挺简单。没想清楚,就觉得很难。我就是想不清楚的那种啊。

首先前缀可以直接用 t r i e \tt trie trie 翻译过来。那么现在就是要计算 t r i e \tt trie trie 的子树内,后缀是 s 2 s_2 s2 的串的数量。感觉是可持久化 t r i e \tt trie trie 耶!——把反串拿出来建 t r i e \tt trie trie,然后一个点的 t r i e \tt trie trie 是其子树的合并结果。显然可以做到 O ( L 1 + L 2 ) \mathcal O(L_1+L_2) O(L1+L2) 的总复杂度。

而我们同样有一个很好写的做法:子树等价于 d f s \rm dfs dfs 序的一段区间。所以也可以正串、反串分别建 t r i e \tt trie trie 树,问题转化为了二维偏序。

在此题中,可以 O ( n 2 ) \mathcal O(n^2) O(n2) 暴力做二维前缀和,而后做到 O ( L 2 ) \mathcal O(L_2) O(L2) 的查询。喜欢主席树?可以做到 O ( n log ⁡ n + m log ⁡ n ) \mathcal O(n\log n+m\log n) O(nlogn+mlogn) 。喜欢分块?可以做到 O ( n n + m ) \mathcal O(n\sqrt{n}+m) O(nn +m) 。然而 O ( n + m n ) \mathcal O(n+m\sqrt{n}) O(n+mn ) 难以持久化,我也不知道到底行不行。

空间复杂度是 O ( L 1 ⋅ ∣ Σ ∣ ) \mathcal O(L_1\cdot |\Sigma|) O(L1Σ),感觉不是很好啊。路径压缩可以做到 O ( n ⋅ ∣ Σ ∣ + L 1 ) \mathcal O(n\cdot|\Sigma|+L_1) O(nΣ+L1),相当于只建出了虚树。

代码

似乎因为那个    n 2    \sout{\;n^2\;} n2的二维前缀和数组的存在,空间消耗仍然非常大呢

#include <cstdio>
#include <iostream>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
typedef long long int_;
# define rep(i,a,b) for(int i=(a); i<=(b); ++i)
# define drep(i,a,b) for(int i=(a); i>=(b); --i)
inline int readint(){
	int a = 0; char c = getchar(), f = 1;
	for(; c<'0'||c>'9'; c=getchar())
		if(c == '-') f = -f;
	for(; '0'<=c&&c<='9'; c=getchar())
		a = (a<<3)+(a<<1)+(c^48);
	return a*f;
}
inline void writeint(int x){
	if(x > 9) writeint(x/10);
	putchar((x-x/10*10)^48);
}

void caesar(char *p,int w){
	for(; *p!='\0'; ++p)
		*p = ((*p)-'a'+w)%26+'a';
}

const int MaxL = 2000005;
const int MaxN = 2005;
class Trie{
	static const int MaxM = MaxN<<1;
	int ch[MaxM][26], val[MaxM];
	int cntNode, st[MaxM], ed[MaxM];
	struct Node{
		char c; Node* nxt;
		Node(char C,Node* N){
			c = C, nxt = N;
		}
	};
	typedef Node* list;
	list toList(char *p){
		if(*p == '\0') return nullptr;
		return new Node(*p,toList(p+1));
	}
	list path[MaxM][26];
	int insert_on_chain(char* &p,list o,const int &end_of_chain){
		for(; true; ++p,o=o->nxt){
			if(o->nxt == nullptr) return 0; // run out
			if(*p == '\0' || *p != o->nxt->c){
				int x = ++ cntNode; // in the middle
				ch[x][o->nxt->c-'a'] = end_of_chain;
				path[x][o->nxt->c-'a'] = new Node(0,o->nxt);
				o->nxt = nullptr; return x; // need father
			}
		}
		return puts("THIS IS IMPOSSIBLE");
	}
	void insert(char *p,int id,int &o){
		if(!o) o = ++ cntNode; // new Node
		if(*p == '\0') return void(val[o] = id);
		int &x = ch[o][*p-'a'];
		if(x == 0){
			path[o][*p-'a'] = new Node(0,toList(p));
			x = ++ cntNode; // the leaf
			return void(val[x] = id);
		}
		int y = insert_on_chain(p,path[o][*p-'a'],x);
		if(y) x = y; // change child (split)
		return insert(p,id,x); // go down
	}
	void giveId(int dfn[],int &o,int &now_v){
		if(!o) return ; // empty subtree
		st[o] = now_v+1; // not include now_v
		if(val[o]) dfn[val[o]] = ++ now_v;
		rep(i,0,25) giveId(dfn,ch[o][i],now_v);
		ed[o] = now_v; // [st,ed]
	}
	int query_on_chain(char* &p,list &o,const int &end_of_chain){
		if(o->nxt == nullptr) return 0; // run out
		if(*p == '\0') return end_of_chain; // subtree
		if(*p != o->nxt->c) return -1; // fail
		return query_on_chain(++ p,o->nxt,end_of_chain);
	}
	pair<int,int> query(char *p,int &o){
		if(!o) return make_pair(1,0); // empty
		if(*p == '\0') // at the end
			return make_pair(st[o],ed[o]);
		int &x = ch[o][*p-'a'];
		if(!x) return make_pair(1,0);
		int y = query_on_chain(p,path[o][*p-'a'],x);
		if(y == -1) return make_pair(1,0); // fail
		if(y == 0) y = x; // continue matching
		return query(p,y); // go down
	}
	int rt;
public:
	Trie(){ rt = 0; }
	void insert(char *p,int id){
		return insert(p,id,rt);
	}
	void giveId(int dfn[]){
		int xyx = 0; // alloc memory
		return giveId(dfn,rt,xyx);
	}
	pair<int,int> query(char *p){
		return query(p,rt);
	}
};
Trie pre, suf;

int maze[MaxN][MaxN];
int p0[MaxN], p1[MaxN];

char str[MaxL];
int main(){
	int n = readint();
	rep(i,1,n){
		scanf("%s",str);
		pre.insert(str,i);
		reverse(str,str+strlen(str));
		suf.insert(str,i);
	}
	pre.giveId(p0), suf.giveId(p1);
	rep(i,1,n) ++ maze[p0[i]][p1[i]];
	rep(i,1,n) rep(j,1,n)
		maze[i][j] += maze[i][j-1];
	rep(j,1,n) rep(i,1,n)
		maze[i][j] += maze[i-1][j];
	for(int ans=0,m=readint(); m; --m){
		scanf("%s",str); caesar(str,ans);
		pair<int,int> rx = pre.query(str);
		scanf("%s",str); caesar(str,ans);
		reverse(str,str+strlen(str));
		pair<int,int> ry = suf.query(str);
		ans = maze[rx.second][ry.second]
			- maze[rx.second][ry.first-1]
			- maze[rx.first-1][ry.second]
			+ maze[rx.first-1][ry.first-1];
		printf("%d\n",ans);
	}
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值