这套题很棒!题目很有趣,很考验思维能力和代码强度,看得出命题人很认真,就连题目名称首字母都是和题号对应的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
题意:维护一个集合,支持如下操作:
- 插入一个元素x
- 删除一个元素x
- 询问集合中是否存在一对(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
*/