国庆集训 day1(牛客)

部分题解

A

题意:找一个最大的回文后缀。

题解:随便hash一下。

#include<bits/stdc++.h>

using namespace std;

const int N = 400005;

struct hashMap{
    typedef unsigned long long ull;

    const ull base = 233;

    ull h[N], pw[N];

    void getHash(char* s){
        int n=strlen(s+1);
        pw[0]=1;h[0]=0;
        for(int i=1;i<=n;++i){
            h[i]=h[i-1]*base+s[i]-'a';
            pw[i]=pw[i-1]*base;
        }
    }

    ull getVal(int l, int r){
        return h[r]-h[l-1]*pw[r-l+1];
    }
}hs, hs1;

char s[N], s1[N];

int n;

int ans[2];

int main(){
    cin>>n;
    cin>>s+1;

    for(int i=n;i>=1;--i){
        s1[n-i+1]=s[i];
    }

    hs.getHash(s);
    hs1.getHash(s1);

    int len=0;
    for(int i=n;i>=1;--i){
        int t=n-i+1;
        if(t>i-1)break;

        if(hs.getVal(i,n)==hs1.getVal(n-i+2,n-(i-t)+1)){
            len=t;
        }
    }

    ans[0]=n-2*len;

    len=0;
    for(int i=n;i>=1;--i){
        int t=n-i;
        if(t>i-1)break;

        if(hs.getVal(i+1,n)==hs1.getVal(n-i+2,n-i+t+1)){
            len=t;
        }
    }

    ans[1]=n-(2*len+1);

    cout<<min(ans[0],ans[1]);

    return 0;
}

B

题意:枚举区间,求区间gcd*区间max的和。

题解:考虑枚举最大值,这里采用笛卡尔树。原因是笛卡尔树采用大根堆结构的时候,我们能够轻易的知道,每一个最大值能够主导的区间。我们知道每一个最大值能够主导的区间之后,现在要考虑的是,这个区间内的gcd情况。首先我们要知道一个结论(比较直观),包含这个值,区间向左或者右延展的时候,区间gcd是递减的(非常直观),且取值最多有gcd个(每次至少少一个质因数)。我们只需要得到每一个gcd的值和区间长度(这个可以用st表二分查询)就可以得到答案。最终在O(nlogn^2)的时间就可以做完这道题。

#include<bits/stdc++.h>

using namespace std;

const int N = 200005;

const int MOD = 1e9+7;

int a[N], n, ans;

int st[N], top, ls[N], rs[N];

inline int gcd(int a, int b){return b==0?a:gcd(b,a%b);}

struct RMQ{
    int lg[N], mn[N][21];

    void build(){
        for(int i=1;i<=n;++i)mn[i][0]=a[i];
        for(int i=2;i<=n;++i)lg[i]=lg[i>>1]+1;

        for(int i=1;i<=20;++i){
            for(int j=1;j+(1<<i)-1<=n;++j){
                mn[j][i]=gcd(mn[j][i-1],mn[j+(1<<(i-1))][i-1]);/*bug*/
            }
        }
    }

    int query(int l, int r){
        int len=lg[r-l+1];
        return gcd(mn[l][len],mn[r-(1<<len)+1][len]);
    }
}table;

int L[N], R[N], tL[N], tR[N];

void solve(int nd, int l, int r){
    if(ls[nd])solve(ls[nd],l,nd-1);
    if(rs[nd])solve(rs[nd],nd+1,r);

    L[0]=R[0]=0;
    int x=nd, y=nd;

    while(x>=l){
        int g=table.query(x,nd);
        L[++L[0]]=g;
        int ll=l, rr=x, mid;
        int id=x;

        while(ll<=rr){
            mid=ll+rr>>1;
            if(table.query(mid,nd)==L[L[0]]){
                id=mid;
                rr=mid-1;
            }else{
                ll=mid+1;
            }
        }

        tL[L[0]]=x-id+1;
        x=id-1;
    }

    while(y<=r){
        int g=table.query(nd,y);
        R[++R[0]]=g;
        int ll=y, rr=r, mid;
        int id=y;

        while(ll<=rr){
            mid=ll+rr>>1;
            if(table.query(nd,mid)==R[R[0]]){
                id=mid;
                ll=mid+1;
            }else{
                rr=mid-1;
            }
        }

        tR[R[0]]=id-y+1;
        y=id+1;
    }
    int res=0;
    for(int i=1;i<=L[0];++i){
        for(int j=1;j<=R[0];++j){
            int g=gcd(L[i],R[j]);
            res+=1ll*g*tL[i]%MOD*tR[j]%MOD;
            res%=MOD;
        }
    }

    ans+=1ll*res*a[nd]%MOD;
    ans%=MOD;
}

int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    cin>>n;
    for(int i=1;i<=n;++i){
        cin>>a[i];
    }

    table.build();

    for(int i=1;i<=n;++i){
        while(top&&a[st[top]]<a[i])ls[i]=st[top--];
        if(top)rs[st[top]]=i;
        st[++top]=i;
    }

    solve(st[1],1,n);

    cout<<ans;

    return 0;
}

G

题意:你现在需要去组织一个长度为n的只包含小写字母的字符串,使得这个字符串中不能出现给定的q个禁忌字符串。

题解:首先,我们将trie树建出来。然后可以想到,在匹配过程中,树上有一些节点是不合法的。这就是那些本身是结束节点,或者自己的后缀是结束节点(由于建立fail树的时候,是按照BFS序去建立的,所以可以直接对这些不合法的节点打上标记)。然后,我们可以将带有fail指针的trie树看成是一个有向图。这样,问题就能转化为:在一个有向图中,从0节点出发,走k步,到达某一个节点的方案数。这个可以通过矩阵的快速幂来解决。

#include<bits/stdc++.h>

using namespace std;

const int N = 100005;

const int MOD = 1e9+7;

const int M = 26;

struct trieNode{
    int son[M], mark, fail;
}T[N];

int cntNode;

void Insert(char* s){
    int len=strlen(s+1);
    int cur=0;

    for(int i=1;i<=len;++i){
        if(!T[cur].son[s[i]-'a']){
            T[cur].son[s[i]-'a']=++cntNode;
        }
        cur=T[cur].son[s[i]-'a'];
    }

    T[cur].mark=1;
}

struct m{
    int a[105][105];

    m operator*(m m1){
        m ret;
        for(int i=0;i<=cntNode;++i){
            for(int j=0;j<=cntNode;++j){
                ret.a[i][j]=0;
                for(int k=0;k<=cntNode;++k){
                    ret.a[i][j]+=1ll*a[i][k]*m1.a[k][j]%MOD;
                    ret.a[i][j]%=MOD;
                }
            }
        }

        return ret;
    }
}gkd;

m powmod(m m1, int k){
    m ret;
    memset(ret.a,0,sizeof ret.a);

    for(int i=0;i<=cntNode;++i)ret.a[i][i]=1;

    while(k){
        if(k&1)ret=ret*m1;
        m1=m1*m1;
        k>>=1;
    }

    return ret;
}

void getFail(){
    queue<int> q;

    for(int i=0;i<26;++i){
        int nd=T[0].son[i];
        if(nd){
            q.push(nd);
            T[nd].fail=0;
        }
    }

    while(!q.empty()){
        int nd=q.front();
        q.pop();
        if(T[T[nd].fail].mark>0)T[nd].mark=1;
        for(int i=0;i<26;++i){
            int& nd1=T[nd].son[i];
            if(!nd1){
                nd1=T[T[nd].fail].son[i];
            }else{
                T[nd1].fail=T[T[nd].fail].son[i];
                q.push(nd1);
            }
        }
    }
}

void build(){
    for(int i=0;i<=cntNode;++i){
        for(int j=0;j<26;++j){
            int nd=T[i].son[j];
            if(T[i].mark==0&&T[nd].mark==0){
                gkd.a[i][nd]++;
            }
        }
    }
}

char s[N];

int n, q;

int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>q;

    for(int i=1;i<=q;++i){
        int t;cin>>t;
        cin>>s+1;
        Insert(s);
    }

    getFail();

    build();

    m res=powmod(gkd,n);

    int ans=0;
    for(int i=0;i<=cntNode;++i){
        ans+=res.a[0][i];
        ans%=MOD;
    }

    cout<<ans;

    return 0;
}

D

题意:给你一根过原点的直线和平面直角坐标系中n个点,你现在需要在直线上选择一个点,使得过这个点的一个固定大小直径的圆能覆盖最多的点。

题解:首先过每一个点,作一个大小为R的圆。然后,我们需要的仅仅是原点到这两个点的有向线段长度而已。丢进一个容器里,排序后扫描线。

#include<bits/stdc++.h>

using namespace std;

const int N = 300005;

#define all(x) x.begin(), x.end()

const double eps = 1e-3;

struct P{
    double x, y;

    P(){}
    P(double _x, double _y){
        x=_x;
        y=_y;
    }
}pts[N];

struct Node{
    double x;
    int o;

    bool operator<(const Node& rhs)const{
        if(fabs(x-rhs.x)<1e-10)return o>rhs.o;
        else return x<rhs.x;
    }
};

double cross(P p1, P p2){
    return p1.x*p2.y-p1.y*p2.x;
}

double dot(P p1, P p2){
    return p1.x*p2.x+p1.y*p2.y;
}

int n;

double r, a, b;

int main(){
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    cin>>n>>r>>a>>b;
    r+=eps;
    for(int i=1;i<=n;++i){
        double x,y;
        cin>>x>>y;
        pts[i]=(P){x,y};
    }

    P v1=P(a,b);

    vector<Node> points;

    double len=sqrt(a*a+b*b);

    if(len<eps){
        int cnt=0;
        for(int i=1;i<=n;++i){
            if(pts[i].x*pts[i].x+pts[i].y*pts[i].y<=r*r)++cnt;
        }

        cout<<cnt;
        return 0;
    }

    for(int i=1;i<=n;++i){
        P v2=P(pts[i].x, pts[i].y);

        double t1=dot(v1,v2)/len;
        double t2=cross(v1,v2)/len;

        if(fabs(t2)-r>0)continue;

        double delta=sqrt(r*r-t2*t2);

        double ll=t1-delta, rr=t1+delta;

        points.push_back((Node){ll,1});
        points.push_back((Node){rr,-1});
    }

    sort(all(points));

    int t=0;
    int res=0;

    for(auto& p:points){
        t+=p.o;
        res=max(res,t);
    }

    cout<<res;

    return 0;
}

F

题意:n个数字,从中选取k个。使得它们的二进制按位与之后得到的结果最大。

题解:首先,从高位到低位考虑的时候,如果某一位对应的数字个数超过k个,那剩下的就会被丢弃。然后问题就被转化为一个子问题了,模拟一下就能过。

#include<bits/stdc++.h>

using namespace std;

int vis[200005];

int a[200005][32];

int n, k;

int main(){
    cin>>n>>k;

    for(int i=1;i<=n;++i){
        int x;cin>>x;
        for(int j=0;j<=29;++j){
            if(x>>j&1){
                a[i][j]=1;
            }
        }
    }

    for(int i=29;i>=0;--i){
        int cnt=0;
        for(int j=1;j<=n;++j){
            if(!vis[j]&&a[j][i])++cnt;
        }

        if(cnt>=k){
            for(int j=1;j<=n;++j){
                if(!vis[j]&&!a[j][i])vis[j]=1;
            }
        }
    }

    int res=-1;
    for(int i=1;i<=n;++i){
        if(!vis[i]){
            int t=0;
            for(int j=0;j<=29;++j){
                if(a[i][j]){
                    t|=(1<<j);
                }
            }
            res&=t;
        }
    }

    cout<<res;

    return 0;
}

posted @ 2020-10-08 23:14  John_Ran  阅读(143)  评论(0编辑  收藏  举报