[SCOI2012]喵星球上的点名(后缀数组+莫队+ST表)

传送门
这题是真的秀.一眼看下去感觉AC自动机很可做,第一个问比较好处理,dfs序即可搞定,可第二问有点抽象,目前对树形结构的知识点不足以支持我解决这个问题.所以舍弃AC自动机,用SA做.
SA做法: 做法比较套路,刚接触SA算法可能不太好想. 把所有字符串(询问串+模式串)都串成一个大字符串,中间用互不相同的字符隔开. 然后对这个串求SA和Height数组.
在读入询问的时候,记录一下每个询问串的起点,求完SA和Height数组后求出每个询问串在SA数组中LCP(i,j) >= len的区间[L,R].然后第一问就变成了区间种类数问题,这是莫队模板题.第二问可以用差分做,每次加入一个新颜色的时候这个颜色的答案加上剩余询问数,删掉的时候减掉剩余询问数.
如何二分出[L,R]区间也是一个问题.由LCP Theorem的性质我们可以从询问串的起点出发,往右边和左边分别二分得到这个[L,R]区间. 这个过程需要ST表加速一下二分时候判断的过程.
这题的综合性很强,后缀数组,莫队,还有ST表,以及一些奇奇怪怪的边界问题,是个很有价值的题目.
PS:这题的两个问用树状数组离线也是可做的.
参考代码(在注释里标明了各块函数的作用).

#define LL long long
#define pq priority_queue
#define ULL unsigned long long
#define pb push_back
#define mem(a,x) memset(a,x,sizeof a)
#define pii pair<int,int>
#define fir(i,a,b) for(int i=a;i<=(int)b;++i)
#define afir(i,a,b) for(int i=(int)a;i>=b;--i)
#define ft first
#define vi vector<int>
#define sd second
#define ALL(a) a.begin(),a.end()
#define bug puts("-------")
#define mpr(a,b) make_pair(a,b)
#include <bits/stdc++.h>

using namespace std;
const int N = 4e5+10;

inline int read(){
	int x = 0,f=1;char ch = getchar();
	while(ch<'0'||ch>'9'){if(ch=='-')f=-1;ch=getchar();}
	while(ch<='9'&&ch>='0'){x=x*10+ch-'0';ch=getchar();}
	return x*f;
}
struct Q{
	int l,r,id;
}q[N];

// SA部分
int col[N],s[N],cnt[N],id[N],px[N],sa[N],rk[N],oldrk[N],ht[N],st[21][N],pre[N];
bool cmp(int x,int y,int w){
	return oldrk[x] == oldrk[y] && oldrk[x+w] == oldrk[y+w];
}
void SA(int n){
	int m = 3e5,p;
	fir(i,1,n) cnt[rk[i] = s[i]]++;
	fir(i,1,m) cnt[i] += cnt[i-1];
	afir(i,n,1) sa[cnt[rk[i]]--] = i;
	for(int w = 1;w < n;w <<= 1,m = p){
		p = 0;
		afir(i,n,n-w+1) id[++p] = i;
		fir(i,1,n) if(sa[i] > w) id[++p] = sa[i]-w;
		mem(cnt,0);
		fir(i,1,n) cnt[px[i] = rk[id[i]]]++;
		fir(i,1,m) cnt[i] += cnt[i-1];
		afir(i,n,1) sa[cnt[px[i]]--] = id[i];
		p = 0;swap(rk,oldrk);
		fir(i,1,n)
			rk[sa[i]] = cmp(sa[i],sa[i-1],w)?p:++p;
		if(p == n) break;
	}
	int k = 0;
	fir(i,1,n){
		if(k) k--;
		while(s[i+k] == s[sa[rk[i]-1]+k]) k++;
		ht[rk[i]] = k;
	}
}

// ST表部分
void prework(int n){
	fir(i,0,N-1) pre[i] = log(i)/log(2);
	fir(i,1,n) st[0][i] = ht[i];
	fir(i,1,20) fir(j,1,n-(1<<i)-1) st[i][j] = min(st[i-1][j],st[i-1][j+(1<<(i-1))]);
}
int lcp(int l,int r){
	int len = pre[r-l+1]; return min(st[len][l],st[len][r-(1<<len)+1]);	
}

// 莫队部分
int blo,nn,L[N],R[N],pos[N];
void build(int n){
	blo = sqrt(n);
	nn = n/blo;
	fir(i,1,nn){
		L[i] = (i-1)*blo+1;
		R[i] = i*blo;
	}
	if(n % blo){
		nn++;
		L[nn] = R[nn-1]+1;
		R[nn] = n;
	}
	fir(i,1,nn) fir(j,L[i],R[i]) pos[j] = i;
}

bool c1(Q x,Q y){
	return pos[x.l]^pos[y.l]?pos[x.l]<pos[y.l]:x.r<y.r;
}

int ans,vis[N],res[N],Ans[N],n;
void add(int x,int num){
	if(!vis[x] && x <= n){
		res[x]+=num;
		ans++;
	}
	vis[x]++;
}
void del(int x,int num){
	vis[x]--;
	if(!vis[x] && x <=n){
		res[x]-=num;
		ans--;
	}
}

// 读入数据部分
int main(){
	int m,x,cnt=0,idd=1e4+1,ct=0;
	n = read();m = read();
	s[0] = -1e9;
	fir(i,1,n){
		int len;
		len = read();
		++ct;
		fir(j,1,len){
			s[++cnt] = read();
			col[cnt] = ct;
		}
		s[++cnt] = ++idd;
		len = read();
		fir(j,1,len){
			s[++cnt] = read();
			col[cnt] = ct;
		}
		s[++cnt] = ++idd;
	}
	fir(i,1,m){
		int len;
		len = read();
		++ct;
		q[i] = {cnt+1,len,i};
		fir(j,1,len){
			s[++cnt] = read();
			col[cnt] = ct;
		}
		s[++cnt] = ++idd;
	}
	SA(cnt);
	prework(cnt);
	fir(i,1,m){
		int sta = rk[q[i].l],l = sta,r = cnt+1,len = q[i].r;
		while(l < r){
			int mid = (l + r) >> 1;
			if(lcp(sta,mid) >= len) l = mid+1;
			else r = mid;
		}
		q[i].r = l-1;
		r = sta;
		l = 1;
		while(l < r){
			int mid = (l + r) >> 1;
			if(lcp(mid,sta) >= len) r = mid;
			else l = mid+1;
		}
		q[i].l = l;
		if(q[i].l <= q[i].r) q[i].l--;
		else q[i].r++;
	}
	build(cnt);
	sort(q+1,q+1+m,c1);
	int l = 1,r = 0;
	fir(i,1,m){
		int num = m-i+1;
		while(r < q[i].r) add(col[sa[++r]],num);
		while(l > q[i].l) add(col[sa[--l]],num);
		while(r > q[i].r) del(col[sa[r--]],num);
		while(l < q[i].l) del(col[sa[l++]],num);
		Ans[q[i].id] = ans;
	}
	fir(i,1,m) cout << Ans[i] << "\n";
	fir(i,1,n) cout << res[i] << " ";
	
	return 0;
}	
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值