[BZOJ2754][SCOI2012]喵星球上的点名

写了5kb,AC之后去查题解,发现居然暴力就可以过了,而且写的好的暴力比我的代码跑的快……这题卡暴力的数据随手造,出题人没卡什么心态,考场上写正解的人岂不是玩脱了……在网上没有找到写正解的题解,都是和答案大小相关的暴力(有些版本只有第二问和答案大小相关),于是我在这里简要写一下正解好了。虽然这种题肯定之前有人是写了正解的,不过不知道是他们没写题解还是百度搜不到……

首先把所有的喵星人的姓名串接起来,然后做SA,对于每个教师的点名,找出来一个极大范围[l,r]使得后缀sa[l]~sa[r]的前缀可以和教师的点名匹配。
把位置i的权值定义为sa[i]属于第几号喵星人,于是第一问就变成了询问[l,r]之间有几个不同的数,经典问题。

第二问的话,我们把教师的点名看成一个覆盖了[l,r]的线段,那么问题就变成了权值相同的所有点,被多少个不同的线段覆盖。直接做有点难,我们先忽略线段不同的限制,也就是计算被覆盖的次数而不是被几条线段覆盖,这个显然是可以随便做的。接下来我们只要把多算的那些次数减掉,整道题就完了。

多的那些次数怎么计算呢……定义pre[i]为与位置i权值相同的前一个位置。假如我们采取如下的方式统计“权值相同的所有点,被多少个不同的线段覆盖”:对于同一条线段和权值相同的所有点,我们只在线段能覆盖的第一个点处统计这条线段,那么按照上一段的算法,我们在每个位置i处多算的线段数=能完全覆盖[pre[i],i]的线段,这个玩意也是可以随便搞搞就做掉。

此题至此解决,时间复杂度O(xlogx)(x=n,m,字符串总长……)。


#include <cstdio>
#include <algorithm>
using namespace std;

const int MAXN=20005,MAXM=50005,MAXL=100005,MAXLEN=MAXN*2+MAXL;

struct segment
{
	int l,r,no;
}cat[MAXLEN],tch[MAXM];

int maxbit,l,s[MAXLEN],s2[MAXL],a[MAXLEN],b[MAXLEN],nxt[MAXLEN],last[MAXN],tag[MAXLEN],C[MAXLEN],d[20][MAXLEN],delta[MAXLEN],height[MAXLEN],rk[MAXLEN],sa[MAXLEN],t[MAXLEN],t2[MAXLEN],c[MAXLEN],anst[MAXM];
long long ansc[MAXN];

bool cmp(const segment &a,const segment &b)
{
	return a.l<b.l;
}

inline void getname(int id)
{
	int k;
	scanf("%d",&k);
	for (int c;k--;)
	{
		scanf("%d",&c);
		a[l]=id;
		s[l++]=c;
	}
	s[l++]=10001;
}

inline void build_sa(int m)
{
	int *x=t,*y=t2;
	for (int i=0;i<l;i++) c[x[i]=s[i]]++;
	for (int i=1;i<m;i++) c[i]+=c[i-1];
	for (int i=l-1;i>=0;i--) sa[c[x[i]]--]=i;
	for (int k=1;k<l;k<<=1)
	{
		int p=0;
		for (int i=l-k;i<l;i++) y[p++]=i;
		for (int i=1;i<=l;i++) if (sa[i]>=k) y[p++]=sa[i]-k;
		for (int i=0;i<m;i++) c[i]=0;
		for (int i=0;i<l;i++) c[x[i]]++;
		for (int i=1;i<m;i++) c[i]+=c[i-1];
		for (int i=l-1;i>=0;i--) sa[c[x[y[i]]]--]=y[i];
		swap(x,y);
		p=1;x[sa[1]]=0;
		for (int i=2;i<=l;i++)
			x[sa[i]]=y[sa[i-1]]==y[sa[i]]&&y[sa[i-1]+k]==y[sa[i]+k]?p-1:p++;
		if (p>=l) break;
		m=p;
	}
}

inline void getheight()
{
	for (int i=1;i<=l;i++) rk[sa[i]]=i;
	for (int i=0,k=0,j;i<l;i++)
	{
		if (rk[i]==1)
		{
			k=0;
			continue;
		}
		if (k) k--;
		j=sa[rk[i]-1];
		while (s[i+k]==s[j+k]) k++;
		height[rk[i]]=k;
	}
}

inline void RMQ_init()
{
	while ((1<<maxbit)<=l) maxbit++;
	maxbit--;
	delta[1]=0;
	for (int i=2;i<=l;i++) delta[i]=delta[i>>1]+1;
	for (int i=2;i<=l;i++) d[0][i]=height[i];
	for (int j=1;j<=maxbit;j++)
		for (int i=2;i+(1<<j)-1<=l;i++)
			d[j][i]=min(d[j-1][i],d[j-1][i+(1<<j-1)]);
}

inline int cmp_suffix(int bg,int len)
{
	for (int i=bg;i<l&&i-bg<len;i++)
		if (s[i]!=s2[i-bg])
			return s[i]<s2[i-bg]?-1:1;
	return 0;
}

inline int RMQ(int l,int r)
{
	int k=delta[r-l+1];
	return min(d[k][l],d[k][r-(1<<k)+1]);
}

inline int LCP(int l,int r)
{
	if (r==0) return 0;
	if (l==r) return MAXLEN;
	if (l>r) swap(l,r);
	return RMQ(l+1,r);
}

inline int check(int len)
{
	int lt=1,rt=l,now=0,nowp=0;
	if (cmp_suffix(sa[lt],len)>0) return 0;
	if (cmp_suffix(sa[rt],len)<0) return 0;
	while (lt<=rt)
	{
		int m=lt+rt>>1;
		int lcp=LCP(m,now);
		if (lcp<nowp)
		{
			if (m<now) lt=m+1;
			else rt=m-1;
		}
		else
		{
			while (nowp<len&&s[sa[m]+nowp]==s2[nowp]) nowp++;
			if (nowp==len) return m;
			now=m;
			if (s[sa[m]+nowp]<s2[nowp]) lt=m+1;
			else rt=m-1;
		}
	}
	return 0;
}

inline int getl(int x,int lcp)
{
	int lt=1,rt=x;
	while (lt<=rt)
	{
		int m=lt+rt>>1;
		if (LCP(m,x)<lcp) lt=m+1;
		else rt=m-1;
	}
	return lt;
}

inline int getr(int x,int lcp)
{
	int lt=x,rt=l;
	while (lt<=rt)
	{
		int m=lt+rt>>1;
		if (LCP(x,m)<lcp) rt=m-1;
		else lt=m+1;
	}
	return rt;
}

inline void add(int x,int d)
{
	while (x<=l)
	{
		C[x]+=d;
		x+=x&-x;
	}
}

inline int sum(int x)
{
	int ret=0;
	while (x)
	{
		ret+=C[x];
		x-=x&-x;
	}
	return ret;
}

int main()
{
	int n,m;
	scanf("%d%d",&n,&m);
	for (int i=1;i<=n;i++)
	{
		getname(i);
		getname(i);
	}
	build_sa(10002);
	for (int i=1;i<=l;i++) b[i]=a[sa[i]];
	int cntc=0;
	for (int i=1;i<=l;i++)
	{
		if (b[i]==0) continue;
		if (last[b[i]])
		{
			cat[cntc].l=last[b[i]];
			cat[cntc].r=i;
			cat[cntc].no=b[i];
			cntc++;
		}
		else add(i,1);
		last[b[i]]=i;
	}
	fill(last,last+n+1,l+1);
	for (int i=l;i>=1;i--)
	{
		nxt[i]=last[b[i]];
		last[b[i]]=i;
	}
	getheight();
	RMQ_init();
	int cnt=0;
	for (int i=0;i<m;i++)
	{
		int len;
		scanf("%d",&len);
		for (int j=0;j<len;j++) scanf("%d",&s2[j]);
		int ret=check(len);
		if (ret)
		{
			tch[cnt].l=getl(ret,len);
			tch[cnt].r=getr(ret,len);
			tch[cnt].no=i;
			tag[tch[cnt].l]++;
			tag[tch[cnt].r+1]--;
			cnt++;
		}
	}
	sort(tch,tch+cnt,cmp);
	for (int i=0,head=1;i<cnt;i++)
	{
		while (head<tch[i].l)
		{
			if (b[head])
			{
				add(head,-1);
				add(nxt[head],1);
			}
			head++;
		}
		anst[tch[i].no]=sum(tch[i].r);
	}
	for (int i=0;i<m;i++) printf("%d\n",anst[i]);
	for (int i=1,sum=0;i<=l;i++)
	{
		sum+=tag[i];
		ansc[b[i]]+=sum;
	}
	sort(cat,cat+cntc,cmp);
	fill(C,C+l+1,0);
	for (int i=0,head=0;i<cntc;i++)
	{
		while (head<cnt&&tch[head].l<=cat[i].l) add(tch[head++].r,1);
		ansc[cat[i].no]-=head-sum(cat[i].r-1);
	}
	for (int i=1;i<n;i++) printf("%lld ",ansc[i]);
	printf("%lld",ansc[n]);
	return 0;
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值