【WC模拟】覆盖的串

5 篇文章 0 订阅
4 篇文章 0 订阅

Description

我们称一个字符串A 覆盖了一个字符串B 当且仅当对于B 中的每一个字符,都有一个包含它的和A 相同的子串。
例如,A={1,2,1}覆盖了B={1,2,1,2,1,1,2,1}。
所谓的最短覆盖子串,指的是覆盖该串的最短子串。
例如B 的最短覆盖子串为A,长度为3。
最短覆盖前缀数组指的是对于一个串的每一个前缀,它们的最短覆盖子串长度按顺序组成的数组。
例如B 的最短覆盖前缀数组为{1,2,3,2,3,6,7,3}。
现在给你一个最短覆盖前缀数组,判断是否存在某个串符合条件,如果存在则给出一组解。

对于100%的数据满足所有数据的n 之和不超过500,000 且t<=10。

Solution

并没有什么头绪~
看了题解才大概理解
对于一个前缀i的最短覆盖子串,要么是i,要么是next[i]的最短覆盖子串。
可以是next[i]的条件就是i的前next[i]个最短覆盖子串中也有一个next[i]的最短覆盖子串。
因为这样才可以将整个串覆盖。
那么我们就得到了一种check的方法:将你构造出来的串还原成最短覆盖前缀数组,判断和给出的是否等价。
如何构造呢?
我们发现如果i的最短覆盖子串为pi,那么区间[1,pi]和[i-pi+1,i]是一一对应的。
用ST表把它们合并。
最后把在同一个集合中的位置全部变成同一个数。
正确性感性理解一下吧。。。(不要鄙视蒟蒻)

Code

#include <cmath>
#include <cstdio>
#include <cstring>
#include <algorithm>
#define fo(i,a,b) for(int i=a;i<=b;i++)
#define fd(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N=5*1e5+5;
int f[N*19],id[N][19],pos[N*19],mi[19];
int a[N],c[N],an[N],h[N],next[N];
int n,tot,cnt,ty;
int get(int x) {
    return f[x]?f[x]=get(f[x]):x;
}
void merge(int x,int y) {
    x=get(x);y=get(y);
    if (x>y) swap(x,y);
    if (x==y) return;
    f[y]=x;
}
bool check() {
    memset(next,0,sizeof(next));
    memset(h,0,sizeof(h));
    int k=0;
    fo(i,2,n) {
        while (k&&c[k+1]!=c[i]) k=next[k];
        next[i]=k+=c[k+1]==c[i];
    }
    an[1]=1;h[1]=1;
    fo(i,2,n) {
        if (h[an[next[i]]]>=i-next[i]) an[i]=an[next[i]];
        else an[i]=i;
        h[an[i]]=i;
    }
    fo(i,1,n) if (an[i]!=a[i]) return 0;
    return 1;
}
int main() {
    freopen("chuan.in","r",stdin);
    freopen("chuan.out","w",stdout);
    for(scanf("%d",&ty);ty;ty--) {
        scanf("%d",&n);
        cnt=tot=0;
        memset(f,0,sizeof(f));
        int lg=log(n)/log(2);
        mi[0]=1;fo(i,1,lg) mi[i]=mi[i-1]*2;
        fo(i,0,lg) 
            fo(j,1,n-mi[i]+1) {
                pos[++tot]=j;
                id[j][i]=tot;
            }
        fo(i,1,n) {
            scanf("%d",&a[i]);
            if (a[i]==i) continue;
            int x=a[i],l1=1,l2=i-x+1;
            fd(j,lg,0) if (x>=mi[j]) {
                x-=mi[j];
                merge(id[l1][j],id[l2][j]);
                l1+=mi[j];l2+=mi[j];
            }
        }
        fd(i,lg,1) 
            fo(j,1,n-mi[i]+1) {
                int x=get(id[j][i]);
                int l=pos[x];
                merge(id[j][i-1],id[l][i-1]);
                merge(id[j+mi[i-1]][i-1],id[l+mi[i-1]][i-1]);
            }
        fo(i,1,n) {
            int x=get(id[i][0]);
            if (x==id[i][0]) c[i]=++cnt;
            else c[i]=c[x];
        }
        if (check()) {
            printf("Yes\n");
            fo(i,1,n) printf("%d ",c[i]);
            printf("\n");
        } else printf("No\n");
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值