牛客暑期多校训练营2020第2场

这套题很棒!题目很有趣,很考验思维能力和代码强度,看得出命题人很认真,就连题目名称首字母都是和题号对应的hhhh!

A All with Pairs

题意:n个串(s1,s2,……,sn),求 ∑ i = 1 n \sum_{i=1}^n i=1n ∑ j = 1 n \sum_{j=1}^n j=1nf2(si,sj)。其中f(s,t)为s的前缀和t的后缀的最长匹配长度。
思路:hash / 广义后缀自动机+KMP
1.hash+KMP
  记录所有后缀的hash值,对每一个前缀(长度为i)查询有多少后缀与之匹配。但要除去同一对串(s, t)的多次匹配的情况。假设s[1……i] = t[l-i+1……l] 且s[1……j] = t[l-j+1, l],(i < j),那么nxt[j] = i,因此只需在其kmp中的前驱节点除去后继的值就可以了。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<map>
#define LL long long
using namespace std;
const int N=1e5+10;
const int M=1e6+10;
const LL base=37;
const LL md=998244353;
map<LL,int>mp[M];
string s[N];
int nxt[M];
LL f[M],cnt[M];
void get_next(int id)
{
	int l=s[id].length();
	nxt[0]=-1;
	int j=-1;
	for(int i=1;i<l;i++)
	{
		while(j!=-1&&s[id][j+1]!=s[id][i])
			j=nxt[j];
		if(s[id][j+1]==s[id][i])
			j++;
		nxt[i]=j;
	}
}
int main()
{
	LL ans=0;
	int n;
	f[0]=1;
	for(int i=1;i<=1e6;i++)
		f[i]=f[i-1]*base;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		cin>>s[i];
		int l=s[i].length();
		LL now=0;
		for(int j=l-1;j>=0;j--)
		{
			now=now*base+s[i][j]-'a';
			mp[l-j][now]++;
		}
	}
	for(int i=1;i<=n;i++)
	{
		get_next(i);
		int l=s[i].length();
		LL now=0;
		for(int j=0;j<l;j++)
		{
			now=now+(s[i][j]-'a')*f[j];
			if(mp[j+1].find(now)!=mp[j+1].end())
			{
				cnt[j]=mp[j+1][now];
				if(nxt[j]>=0)
					cnt[nxt[j]]-=cnt[j];
			}
			else
				cnt[j]=0;
		}
		for(int j=0;j<l;j++)
			ans=(ans+(LL)(j+1)*(j+1)%md*cnt[j]%md)%md;
	}
	cout<<ans;
	return 0;
}

2.广义后缀自动机+KMP
建出广义后缀自动机,枚举串在后缀自动机上跑匹配。KMP判重同上。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#define LL long long
using namespace std;
const LL md=998244353;
const int N=2e6+10;
struct EDGE{
	int to,nxt;
	EDGE(){}
	EDGE(int x,int y){to=x; nxt=y;}
}edge[N];
int ch[N][26],fa[N],len[N],t[N],nxt[N];
LL g[N];
string s[N];
int tot=1,last,K;
void addedge(int x,int y)
{
	edge[++K]=EDGE(y,t[x]);
	t[x]=K;
}
int insert(int c,int last)
{
	int p=last;
	if(ch[p][c])
	{
		int np=ch[p][c];
		if(len[p]+1==len[np])
			return np;
		else
		{
			int nq=++tot;
			len[nq]=len[p]+1;
			for(int i=0;i<26;i++)
				ch[nq][i]=ch[np][i];
			while(p&&ch[p][c]==np)
				ch[p][c]=nq,p=fa[p];
			fa[nq]=fa[np],fa[np]=nq;
			return nq;	
		}
	}
	int q=++tot;
	len[q]=len[p]+1;
	while(p&&!ch[p][c]) ch[p][c]=q,p=fa[p];
	if(!p)	fa[q]=1;
	else
	{
		int np=ch[p][c];
		if(len[p]+1==len[np])	fa[q]=np;
		else
		{
			int nq=++tot;
			len[nq]=len[p]+1;
			for(int i=0;i<26;i++)
				ch[nq][i]=ch[np][i];
			while(p&&ch[p][c]==np)
				ch[p][c]=nq,p=fa[p];
			fa[nq]=fa[np],fa[np]=fa[q]=nq; 
		}
	}
	return q;
}
void dfs(int x)
{
	for(int p=t[x];p;p=edge[p].nxt)
	{
		int y=edge[p].to;
		dfs(y);
		g[x]+=g[y];
	}
}
void get_next(int id)
{
	int l=s[id].length();
	nxt[0]=-1;
	int j=-1;
	for(int i=1;i<l;i++)
	{
		while(j!=-1&&s[id][j+1]!=s[id][i])
			j=nxt[j];
		if(s[id][j+1]==s[id][i])
			j++;
		nxt[i]=j;
	}
}
int main()
{
	int n;
	LL ans=0;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		cin>>s[i]; 
		last=1;
		for(int j=0;s[i][j];j++)
			last=insert(s[i][j]-'a',last);
		g[last]++;
		
	}
	for(int i=2;i<=tot;i++)
		addedge(fa[i],i);
	dfs(1);
	for(int i=1;i<=n;i++)
	{
		int now=1;
		get_next(i);
		for(int j=0;s[i][j];j++)
		{
			now=ch[now][s[i][j]-'a'];
			ans=(ans+(LL)(j+1)*(j+1)%md*g[now]%md)%md;
			if(nxt[j]>=0)
				ans=(ans-(LL)(nxt[j]+1)*(nxt[j]+1)%md*g[now]%md+md)%md;
		}
	}
	printf("%lld",ans);
	return 0;
}

H Happy Triangle

题意:维护一个集合,支持如下操作:

  1. 插入一个元素x
  2. 删除一个元素x
  3. 询问集合中是否存在一对(a, b)使得x, a, b可以构成三角形

思路
  求三角形即使求一组(a, b)使得a-b<x<a+b,所以问题就可以转化成区间覆盖问题。但是,如果两两求区间,区间数量达到O(n2)的级别,会TLE,所以要考虑如何减少区间。我们发现对于确定的a,当a>b>c的时候,(a-c, a+c)的区间被包含在(a-b, a+b)的区间内。因此对于确定的a,第一个比它小的元素与它构成的区间能够包含所有比a小的元素与a构成的区间,也就是说,我们只需要处理相邻元素的区间就可以了。
  因此,我们用set维护集合,插入或删除元素时,只需处理相邻元素与之构成的区间即可。线段树做区间覆盖。

#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<set>
#include<stack>
using namespace std;
const int N=2e5+10;
const int inf=1e9+10; 
struct EDGE{
	int x,id;
	EDGE(){}
	EDGE(int a,int b)
	{	x=a;	id=b;}
	bool operator < (const EDGE &other)
	const{	return x<other.x||(x==other.x&&id<other.id);}
};
set<EDGE>s;
set<EDGE>ss;
set<EDGE>::iterator it;
set<EDGE>::iterator itt;
stack<int>stk[N];
int v[N*4],tag[N*4],h[N],a[N],opt[N];
int cnt;
void pushdown(int x)
{
	tag[x*2]+=tag[x];
	tag[x*2+1]+=tag[x];
	v[x*2]+=tag[x];
	v[x*2+1]+=tag[x];
	tag[x]=0;
}
void insert(int x,int l,int r,int ll,int rr,int vv)
{
	if(r<ll||rr<l)
		return;
	if(ll<=l&&r<=rr)
	{
		v[x]+=vv;		tag[x]+=vv;		return;
	}
	pushdown(x);
	int mid=(l+r)>>1;
	insert(x*2,l,mid,ll,rr,vv);
	insert(x*2+1,mid+1,r,ll,rr,vv);
}
int query(int x,int l,int r,int ll)
{
	if(l==r)
		return v[x];
	pushdown(x);
	int mid=(l+r)>>1;
	if(ll<=mid)
		return query(x*2,l,mid,ll);
	else
		return query(x*2+1,mid+1,r,ll);
}
void ins(int i)
{
	int l,r;
	int x=lower_bound(h+1,h+cnt+1,a[i])-h;
	stk[x].push(i);
	it=ss.upper_bound(EDGE(-a[i],-i));//负集 
	if(it!=ss.end())
	{
		l=lower_bound(h+1,h+cnt+1,a[i]+(*it).x)-h;	r=lower_bound(h+1,h+cnt+1,a[i]-(*it).x)-h;
		if(h[l]==a[i]+(*it).x)	l++;
		r--;
		insert(1,1,cnt,l,r,1);
	}
	itt=s.upper_bound(EDGE(a[i],i));
	if(itt!=s.end()&&it!=ss.end())
	{
		l=lower_bound(h+1,h+cnt+1,(*itt).x+(*it).x)-h;	r=lower_bound(h+1,h+cnt+1,(*itt).x-(*it).x)-h;
		if(h[l]==(*itt).x+(*it).x)	l++;
		r--;
		insert(1,1,cnt,l,r,-1);
	}
	if(itt!=s.end())
	{
		l=lower_bound(h+1,h+cnt+1,(*itt).x-a[i])-h;	r=lower_bound(h+1,h+cnt+1,(*itt).x+a[i])-h;
		if(h[l]==(*itt).x-a[i])	l++;
		r--;
		insert(1,1,cnt,l,r,1);
	}
	s.insert(EDGE(a[i],i));
	ss.insert(EDGE(-a[i],-i));
}
void del(int i)
{
	int l,r;
	int x=lower_bound(h+1,h+cnt+1,a[i])-h;
	int ti=stk[x].top();
	stk[x].pop();
	it=ss.upper_bound(EDGE(-a[i],-ti));//负集 
	if(it!=ss.end())
	{
		l=lower_bound(h+1,h+cnt+1,a[i]+(*it).x)-h;	r=lower_bound(h+1,h+cnt+1,a[i]-(*it).x)-h;
		if(h[l]==a[i]+(*it).x)	l++;
		r--;
		insert(1,1,cnt,l,r,-1);
	}
	itt=s.upper_bound(EDGE(a[i],ti));
	if(itt!=s.end()&&it!=ss.end())
	{
		l=lower_bound(h+1,h+cnt+1,(*itt).x+(*it).x)-h;	r=lower_bound(h+1,h+cnt+1,(*itt).x-(*it).x)-h;
		if(h[l]==(*itt).x+(*it).x)	l++;
		r--;
		insert(1,1,cnt,l,r,1);
	}
	if(itt!=s.end())
	{
		l=lower_bound(h+1,h+cnt+1,(*itt).x-a[i])-h;	r=lower_bound(h+1,h+cnt+1,(*itt).x+a[i])-h;
		if(h[l]==(*itt).x-a[i])	l++;
		r--;
		insert(1,1,cnt,l,r,-1);
	}
	s.erase(EDGE(a[i],ti));
	ss.erase(EDGE(-a[i],-ti));
}
void que(int i)
{
	int x=lower_bound(h+1,h+cnt+1,a[i])-h;
	if(query(1,1,cnt,x))	printf("Yes\n");
	else	printf("No\n");
} 
int main()
{
	int n;
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d",&opt[i],&a[i]);
		h[i]=a[i];
	}
	sort(h+1,h+n+1);
	cnt=unique(h+1,h+n+1)-h-1;
	h[cnt+1]=inf;
	for(int i=1;i<=n;i++)
	{
		if(opt[i]==1)	ins(i);
		if(opt[i]==2)	del(i);
		if(opt[i]==3)	que(i);
	}
	return 0;
}
/*
10 
1 1
1 2
1 3
3 1
3 2 
3 3
1 1 
1 2 
2 3
3 1 
*/ 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值