Loading

广二集训 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;
}
posted @ 2025-06-13 19:49  Mortis_Life  阅读(25)  评论(0)    收藏  举报