7/27 训练日志(位运算+后缀数组)

Posted 钟钟终

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了7/27 训练日志(位运算+后缀数组)相关的知识,希望对你有一定的参考价值。

每日一题

A. Anu Has a Function

思路:
1.可发现只有当每个数位出现仅一个1时,才能对最大值产生贡献。
2.要输出重新列数组a的顺序,需从高位找出对最大值有贡献,即仅一个1的数放在首位。
3.对结果无贡献的数任意输出即可,且一个数对最大值生效后则不参与后面数位的比较。

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'

using namespace std;
const int N=1e5+5;
int n,a[N];
bool vis[N],p[55];

signed main()

    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    int g;
    for(int i=31;i>=0;i--)
    
        for(int j=1;j<=n;j++)
        
            if((a[j]>>i)&1==1&&vis[j]==0)
            
                g=j;
                if(p[i])
                
                    p[i]=0;
                    break;
                
                p[i]=1;
            
        
        if(p[i])
        
            cout<<a[g]<<" ";vis[g]=1;
        
    
    for(int i=1;i<=n;i++)
        if(!vis[i])
        cout<<a[i]<<" ";
    cout<<endl;
    return 0;


P2852 [USACO06DEC]Milk Patterns G

题意:给定一个数组a,要求至少出现k次的由数字组成的子串最大长度是多少
思路:
1.由于后缀表达式,可求出i和i-1的最长公共前缀,因此题意可转化为----->
在后缀表达式lcp数组中,找到k-1个最小值(区间长度最小为k-1)的最大值(区间至少为k-1时中的值最大)
2.对于每个数值ht[i],需用单调栈求出大于等于ht[i]的区间rr[i]-ll[i]-1

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'

using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
int n,m,k,a[N],b[N],mp[N],lim;
int rk[N],rk2[N];   //以i开头后缀的排名
int sa[N];   //表示sa[i]表示排名i的后缀的开头下标

//求解各个以i为起始下标的后缀字符串的排名
bool cmp(int i,int j)

    if(rk[i]!=rk[j])
        return rk[i]<rk[j];
    int ri=(i+k<=n ? rk[i+k]:-1);
    int rj=(j+k<=n ? rk[j+k]:-1);
    return ri<rj;

void getsa(int n,int s[])

    for(int i=1;i<=n;i++)
        sa[i]=i,rk[i]=s[i];
    for(k=1;k<=n;k*=2)
    
        sort(sa+1,sa+1+n,cmp);
        rk2[sa[1]]=1;
        for(int i=2;i<=n;i++)
            rk2[sa[i]]=rk2[sa[i-1]]+cmp(sa[i-1],sa[i]);
        for(int i=1;i<=n;i++)
            rk[i]=rk2[i];
    


int ht[N];   //维护数组rk相邻两个后缀的lcp(i-1和i的最长公共前缀)
void getht(int n,int s[])

    for(int i=1;i<=n;i++)
        rk[sa[i]]=i;
    int h=0;
    ht[1]=0;
    for(int i=1;i<=n;i++)
    
        int j=sa[rk[i]-1];
        if(h>0)
            h--;
        for(;j+h<=n&&i+h<=n;h++)
            if(s[j+h]!=s[i+h])
                break;
        ht[rk[i]]=h;
    

int top,ll[N],rr[N],sk[N],ans;
bool check(int x)

    for(int i=1;i<=n;i++)
    
        if(ht[i]>=x&&rr[i]-ll[i]-1>=m)
            return 1;
    
    return 0;

signed main()

    cin>>n>>m;
    m--;
    for(int i=1;i<=n;i++)
        cin>>a[i];
    getsa(n,a);
    getht(n,a);
    top=1,sk[1]=1;
    for(int i=2;i<=n;i++)
    
        while(top&&ht[sk[top]]>ht[i])
            rr[sk[top]]=i,top--;
        ll[i]=sk[top];
        sk[++top]=i;
    
    while(top)
        rr[sk[top]]=n+1,top--;
    int l=0,r=n,mid,ans=0;
    while(l<=r)
    
        mid=l+r>>1;
        if(check(mid))
            l=mid+1,ans=mid;
        else
            r=mid-1;
    
    cout<<ans<<endl;
    return 0;


P4341 [BJWC2010]外星联络

题意:给定一个01串,求解各个出现次数>1的子串的出现次数,输出的顺序按照字典序序列。
思路:
1.求解后缀数组lcp,由于ht[1]=0,因此从ht[2]开始
2.根据每个首字符在ht数组中找出单调不减的连续区间长度,即可求出子串出现大于1的次数

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'
#define ios ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N=1e5+5;
const int mod=1e9+7;
int n,k;
int rk[N],rk2[N];   //以i开头后缀的排名
int sa[N];   //表示sa[i]表示排名i的后缀的开头下标
char s[N];
//求解各个以i为起始下标的后缀字符串的排名
bool cmp(int i,int j)

    if(rk[i]!=rk[j])
        return rk[i]<rk[j];
    int ri=(i+k<=n ? rk[i+k]:-1);
    int rj=(j+k<=n ? rk[j+k]:-1);
    return ri<rj;

void getsa(int n,char *s)

    for(int i=1;i<=n;i++)
        sa[i]=i,rk[i]=s[i];
    for(k=1;k<=n;k*=2)
    
        sort(sa+1,sa+1+n,cmp);
        rk2[sa[1]]=1;
        for(int i=2;i<=n;i++)
            rk2[sa[i]]=rk2[sa[i-1]]+cmp(sa[i-1],sa[i]);
        for(int i=1;i<=n;i++)
            rk[i]=rk2[i];
    


int ht[N];   //维护数组rk相邻两个后缀的lcp(i-1和i的最长公共前缀)
void getht(int n,char *s)

    for(int i=1;i<=n;i++)
        rk[sa[i]]=i;
    int h=0;
    ht[1]=0;
    for(int i=1;i<=n;i++)
    
        int j=sa[rk[i]-1];
        if(h>0)
            h--;
        for(;j+h<=n&&i+h<=n;h++)
            if(s[j+h]!=s[i+h])
                break;
        ht[rk[i]]=h;
    


signed main()

    IOS;
    cin>>n;
    for(int i=1;i<=n;i++)
        cin>>s[i];
    getsa(n,s);
    getht(n,s);
    for(int i=2;i<=n;i++)
    
        for(int j=ht[i-1]+1;j<=ht[i];j++)
        
            int g=i+1;
            while(ht[g]>=j) g++;
            g--;
            cout<<g-(i-1)+1<<endl;
        
    
    return 0;


P3181 [HAOI2016]找相同字符

思路:
关键在于求解相同子串的个数(即代码中cal函数),再采用容斥原理。
分别对字符数组a、b和构造数组a+#+b操作
排序方式效率较低,开启O2优化勉强过。需要优化cmp中排序方式

#include<bits/stdc++.h>
#define int long long
#define endl '\\n'
//#define IOS ios::sync_with_stdio(false),cin.tie(0),cout.tie(0)
using namespace std;
const int N=5e5+5;
const int mod=1e9+7;
int n,k,n1,n2;
int rk[N],rk2[N];   //以i开头后缀的排名
int sa[N];   //表示sa[i]表示排名i的后缀的开头下标
char s[N],a[N],b[N];
//求解各个以i为起始下标的后缀字符串的排名
bool cmp(int i,int j)

    if(rk[i]!=rk[j])
        return rk[i]<rk[j];
    int ri=(i+k<=n ? rk[i+k]:-1);
    int rj=(j+k<=n ? rk[j+k]:-1);
    return ri<rj;

void getsa(int n,char *s)

    for(int i=1;i<=n;i++)
        sa[i]=i,rk[i]=s[i];
    for(k=1;k<=n;k*=2)
    
        sort(sa+1,sa+1+n,cmp);
        rk2[sa[1]]=1;
        for(int i=2;i<=n;i++)
            rk2[sa[i]]=rk2[sa[i-1]]+cmp(sa[i-1],sa[i]);
        for(int i=1;i<=n;i++)
            rk[i]=rk2[i];
    


int ht[N];   //维护数组rk相邻两个后缀的lcp(i-1和i的最长公共前缀)
void getht(int n,char *s)

    for(int i=1;i<=n;i++)
        rk[sa[i]]=i;
    int h=0;
    ht[1]=0;
    for(int i=1;i<=n;i++)
    
        int j=sa[rk[i]-1];
        if(h>0)
            h--;
        for(;j+h<=n&&i+h<=n;h++)
            if(s[j+h]!=s[i+h])
                break;
        ht[rk[i]]=h;
    

int sk[N],ll[N],rr[N],top;
int cal(int n,char *s)

    getsa(n,s);
    getht(n,s);
    top=1,sk[1]=1以上是关于7/27 训练日志(位运算+后缀数组)的主要内容,如果未能解决你的问题,请参考以下文章

2022/7/22 训练日志

2021牛客暑期多校训练营3 I-Kuriyama Mirai and Exclusive Or (差分+位运算)

2021牛客暑期多校训练营3 I-Kuriyama Mirai and Exclusive Or (差分+位运算)

BZOJ 1396: 识别子串( 后缀数组 + 线段树 )

树状数组 + 位运算 LA 4013 A Sequence of Numbers

Codeforces Round #613 (Div. 2) D - Dr. Evil Underscores(思维,位运算)