广二集训 day2
T1 CDQ分治
Problem A: 白(bai)
给你一个长度为 \(n\) 的序列 \(a\) 和 \(m\) 次询问,每次询问给定三个整数
\(l,r,k\),你需要求出下面问题的答案。
- 有一个初始为空的栈,把序列中下标在 \(l\) 到 \(r\)
之间的元素依次加入栈中,每加入一个元素之前把栈顶前 \(k\)个元素中与即将加入的元素相同的元素删除,最后栈中有多少个元素?
【输入格式】
从文件 bai.in 中读入数据。
第一行两个整数 \(n,m\),表示序列长度和询问的个数。
第二行 \(n\) 个整数表示序列 \(a\)。
接下来 \(m\) 行,每行三个整数 \(l,r,k\),表示一次询问。
【输出格式】
输出到文件 bai.out 中。
输出共 \(m\) 行,每行一个整数表示询问的答案。
【样例1输入】
6 3
1 2 3 1 2 1
1 5 3
3 6 1
1 6 2
【样例1输出】
3
4
5
见选手目录下的 \(\mathit{bai/bai1.in}\) 与 \(\mathit{bai/bai1.ans}\)。
【样例1解释】
在第三次询问中往栈中加入元素的过程如下:
-
初始栈为空栈。
-
加入元素 \(1\)。在这之后栈中元素从底到顶为 \([1]\)。
-
加入元素 \(2\)。在这之后栈中元素从底到顶为 \([1,2]\)。
-
加入元素 \(3\)。在这之后栈中元素从底到顶为 \([1,2,3]\)。
-
加入元素 \(1\)。在这之前栈顶前 \(2\) 个元素中没有
\(1\),不需要删除。在这之后栈中元素从底到顶为 \([1,2,3,1]\)。 -
加入元素 \(2\)。在这之前栈顶前 \(2\) 个元素中没有
\(2\),不需要删除。在这之后栈中元素从底到顶为 \([1,2,3,1,2]\)。 -
加入元素 \(1\)。在这之前栈顶前 \(2\) 个元素中有
\(1\),删除后栈中元素从底到顶为 \([1,2,3,2]\)。加入元素 \(1\)
后栈中元素从底到顶为 \([1,2,3,2,1]\)。
最终栈中元素有 \(5\) 个。
【样例2输入】
20 8
9 18 10 9 12 10 7 10 3 10 12 7 3 7 7 3 12 12 3 9
14 17 1
4 19 4
1 2 5
6 7 4
10 17 6
3 3 4
13 17 1
8 17 5
【样例2输出】
3
5
2
2
4
1
4
4
CDQ分治好题,考场上想出来了除CDQ以外的全部内容
首先我们观察到:
- 一个元素最多只能删除一个相同元素
- 两个邻接元素(不妨称为贡献区间),前一个元素能否被另一个元素删除有不变临界值\(w_i\)
- 若贡献区间 \(i\) 包含其他共享区间 \(j(共有siz个)\) ,则有 \(w_i\ge w_j\),形式化的
:$ w_i=max(w_j,len_i-siz-1)$ ,也可以抽象为区间颜色数,维护一下即可
int t1[N];
void add(int x,int k) {
for(;x<=n;x+=(x&-x)) t1[x]+=k;
}
int query(int x) {
int sum=0;if(x==0) return 0;
for(;x;x-=(x&-x)) sum+=t1[x];
return sum;
}
int t2[N<<2];
void Add(int x,int k) {
for(;x<=n;x+=(x&-x)) t2[x]=max(t2[x],k);
}
int Query(int x) {
int sum=0;
for(;x;x-=(x&-x)) sum=max(sum,t2[x]);
return sum;
}
for(register int i=1;i<=n;i++) {
a[i]=read();
if(p[a[i]]) la[i]=p[a[i]];
p[a[i]]=i;
if(!la[i]) continue;
add(1,1);add(la[i],-1);
w[i]=max(Query(n-la[i]+1),i-la[i]-query(la[i]));
Add(n-la[i]+1,w[i]);
q[++cnt]={la[i],i,w[i],1,0};
}
有了这些我们可以来处理询问区间(\(l , r, k\))中有几个生效的贡献区间(\(L_i R_i w_i\)):
- \(l \le L_i\)
- \(r \ge R_i\)
- $k \ge w_i $
发现是一个三维偏序,CDQ分治即可
void CDQ(int l,int r) {
if(l==r) return ;
int mid=l+r>>1;
CDQ(l,mid),CDQ(mid+1,r);
sort(q+l,q+mid+1,cmp2),sort(q+mid+1,q+r+1,cmp2);
int j=l;
for(int i=mid+1;i<=r;i++) {
while(q[j].r<=q[i].r&&j<=mid) {
if(q[j].op==1) add(q[j].l,1);
j++;
}
if(q[i].op==2) A[q[i].id]+=query(n)-query(q[i].l-1);
}
for(int i=l;i<j;i++)
if(q[i].op==1) add(q[i].l,-1);
}
T3 90pts 莫队+字符串相关
【题目描述】
给你一个大小为 \(n\) 的字符串序列 \(a\)。定义 \(f(s,t)\) 为 字符串 \(s\) 在 \(t\)
中的出现次数。\(g(l,r)=\sum\limits_{i=l}^{r}\sum\limits_{j=i+1}^r\max(f(a_i,a_j),f(a_j,a_i))\)。
再给你 \(m\) 个询问,每次询问给定两个整数 \(l,r\),你需要求出 \(g(l,r)\)
的值。
【输入格式】
从文件 zhong.in 中读入数据。
第一行两个整数 \(n,m\),表示序列长度和询问的个数。
接下来 \(n\) 行,每行一个字符串,其中第 \(i\) 行的字符串 \(a_i\)
表示序列中的第 \(i\) 个字符串。
接下来 \(m\) 行,每行三个整数 \(l,r\),表示一次询问。
【输出格式】
输出到文件 zhong.out 中。
输出共 \(m\) 行,每行一个整数表示询问的答案。
首先发现这个式子长很丑,而且有很多无用的信息,除非两个字符串相等,不然 \(f(a_i,a_j) 和 f(a_j,a_i)\) 只会有一个非0
那么我们考虑化一下式子,容易得到 \(g(l,r)=\sum\limits_{i=l}^r\sum\limits_{j=l}^rg(a_i,a_j)-\sum\limits_{i=l}^r\sum\limits_{j=i}^r[a_i=a_j]\)
发现后半部分是好维护的(莫队维护AC自动机的结束位置即可),考虑前面的部分
对于非数论函数,这种形态的式子我们一般可以考虑接着化(doge),其实是还可以考虑容斥和增量法,这个显然不好容斥,考虑用莫队逐步增量,下面讲解加法
考虑加入一个字符串 \(s\) ,称字符串区间 \([l,r]\) 组成的AC自动机集合为 \(AC\),以下子树均为 \(Fail\) 树
AC在s中出现次数
板子题类似,考虑s在AC自动机走过路径中的点往上跳Fail边,这个题目不能直接照板子那样做,考虑每个串加进去时进行子树加操作,然后s走路径时对每个点进行单点查
void Add(int x) {
for(int nw=id[x];nw;nw=fa[nw]) {
ans+=B1.query(dfn[nw]);
}
B1.add(dfn[id[x]],1),B1.add(dfn[id[x]]+siz[id[x]],-1);
}
s在AC中出现次数
与上文类似,单点加子树求和即可
void Add(int x) {
for(int nw=id[x];nw;nw=fa[nw]) {
B2.add(dfn[nw],1);
}
ans+=B2.query(dfn[id[x]]+siz[id[x]]-1)-B2.query(dfn[id[x]]-1);
}
时间复杂度为 \(O(S\sqrt m\log S)\)
若使用二次离线莫队即为 \(O(S\sqrt m)\),等我学了二离再来补
int n,q1,cnt,id[N],tot;
char s[N];
int fail[N],t[N][26],fa[N];
void insert(int x) {
int nw=0,l=strlen(s+1);
for(int i=1;i<=l;i++) {
if(t[nw][s[i]-'a']) nw=t[nw][s[i]-'a'];
else t[nw][s[i]-'a']=++cnt,fa[cnt]=nw,nw=cnt;
}
id[x]=nw;
}
vector<int > c[N];
queue<int > q;
void Build() {
for(int i=0;i<26;i++)
if(t[0][i]) q.push(t[0][i]);
while(!q.empty()) {
int k=q.front();q.pop();
c[fail[k]].push_back(k);
for(int i=0;i<26;i++) {
if(t[k][i]) {
fail[t[k][i]]=t[fail[k]][i];
q.push(t[k][i]);
}
else t[k][i]=t[fail[k]][i];
}
}
}
int dfn[N],siz[N],A[N],fk[N],len;
void dfs(int p) {
dfn[p]=++tot;siz[p]++;
for(int i:c[p]) {
dfs(i);siz[p]+=siz[i];
}
}
int ans,tmp,num[N];
struct node {
int l,r,id;
}g[N];
int pos(int x) { return fk[x]/len; }
bool cmp(node a,node b) {
if(pos(a.l)^pos(b.l)) return a.l<b.l;
else if(pos(a.l)&1) return a.r<b.r;
else return a.r>b.r;
}
struct Tree{
int t[N];
void add(int x,int k) { for(;x<=tot;x+=x&-x) t[x]+=k; }
int query(int x) { int sum=0;if(!x) return 0;for(;x;x-=(x&-x)) sum+=t[x];return sum; }
}B1,B2;//区间在单体 单体在区间
void Add(int x) {
for(int nw=id[x];nw;nw=fa[nw]) {
ans+=B1.query(dfn[nw]);
B2.add(dfn[nw],1);
}
ans+=B2.query(dfn[id[x]]+siz[id[x]]-1)-B2.query(dfn[id[x]]-1);
B1.add(dfn[id[x]],1),B1.add(dfn[id[x]]+siz[id[x]],-1);
num[id[x]]++;
tmp+=num[id[x]];
}
void Del(int x) {
ans-=B2.query(dfn[id[x]]+siz[id[x]]-1)-B2.query(dfn[id[x]]-1);
B1.add(dfn[id[x]],-1),B1.add(dfn[id[x]]+siz[id[x]],1);
for(int nw=id[x];nw;nw=fa[nw]) {
ans-=B1.query(dfn[nw]);
B2.add(dfn[nw],-1);
}
tmp-=num[id[x]];
num[id[x]]--;
}
signed main() {
cin>>n>>q1;
for(int i=1;i<=n;i++) {
scanf("%s",s+1);fk[i]=fk[i-1]+strlen(s+1);
insert(i);
}
Build();dfs(0);
for(int i=1;i<=q1;i++) {
int l=read(),r=read();
g[i]={l,r,i};
}
len=pow(n,0.8);
sort(g+1,g+1+q1,cmp);
int l=1,r=0;
for(int i=1;i<=q1;i++) {
while(r<g[i].r) r++,Add(r);
while(l>g[i].l) l--,Add(l);
while(r>g[i].r) Del(r),r--;
while(l<g[i].l) Del(l),l++;
A[g[i].id]=ans-tmp;
}
for(int i=1;i<=q1;i++) printf("%lld\n",A[i]);
return 0;
}

T1:推性质+CDQ分治
T3:莫队+字符串相关,实际上是杂糅改编题就对了
浙公网安备 33010602011771号