6.25联考题解

A:
维护一个集合,兹磁插入一个数 x x ,询问集合里的数和x and,or,xor a n d , o r , x o r 运算的最大值
权值 ai<65536=216 a i < 65536 = 2 16 因为位运算不同位之间互相独立,我们把一个数拆成前8位和后8位,枚举前8位dp后8位以平衡复杂度
f[a][b] f [ a ] [ b ] 表示集合中前8位的数是 a a 的数里,和一个后8位是b的数做位运算,后8位结果的最大值
x x 的前8位是x,后8位是 y y
插入x时枚举 b b 更新f[x][b]
询问时,枚举 a a ,用f[a][y]更新答案

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 210000;
const int maxb = 256;

int n,opt,type;
int f[maxb][maxb],g[maxb][maxb];

char str[110];
int main()
{
    freopen("binary.in","r",stdin);
    freopen("binary.out","w",stdout);

    scanf("%d%s%d",&n,str,&type);
    opt=str[0]=='a'?0:(str[0]=='o'?1:2);

    for(int i=1;i<=n;i++)
    {
        int x; scanf("%d",&x); int y=x&255; x^=y;
        if(i>1)
        {
            int nowmax=0,nowsum=0;
            for(int j=0;j<maxb;j++)
            {
                int cx=f[j][y],cs=g[j][y];
                if(!cs) continue;
                int cj=j<<8;
                cx|=opt==0?(cj&x):(opt==1?(cj|x):(cj^x));
                if(cx>nowmax) nowmax=cx,nowsum=0;
                if(cx==nowmax) nowsum+=cs;
            }
            if(type) printf("%d %d\n",nowmax,nowsum);
            else printf("%d\n",nowmax);
        }

        x>>=8;
        for(int j=0;j<maxb;j++)
        {
            int cx=opt==0?(y&j):(opt==1?(y|j):(y^j));
            if(f[x][j]<cx) f[x][j]=cx,g[x][j]=0;
            if(f[x][j]==cx) g[x][j]++;
        }
    }

    return 0;
}

B:
一开始有个空串, n n 次操作,每次操作形如在第i次操作后的串尾添加一个字符 x x 并且询问操作完的串最小周期p的长度,强制在线
对于串 S S ,有p=|S|border,即他的最小周期=串长减最长的border(border就是一段和后缀相等的前缀)
于是考虑怎么求这个串的 border b o r d e r ,假设我们对这个单串建AC自动机,发现 border b o r d e r 就是这个串的 fail f a i l 的长度
问题变成在线维护一个AC自动机,要求每个点只能 fail f a i l 到他的祖先上
加入一个点时如果我们暴力去跳 fail f a i l 显然会T,于是我们要对每个节点维护一个 son[26] s o n [ 26 ] ,表示在他后面接上字符 c c ,会跳到哪个节点

那这个东西要怎么在线维护呢
考虑在某个点x后面接上一个字符 c c 后我们要更新哪些信息
我们要新建一个点 y 代表在 x x 后面接上字符c后的串
然后更新 x x son,维护 y y fail y y son y y len
这个东西显然只能可持久化维护,但是注意到如果我们更新了 x x son,因为可持久化我们会新建一个 x x ′ ,那么我们这个串上所有原来 son s o n 指向 x x 的都要更新,指向x,这样子我们单次插入更新的信息量十分巨大完全无法承受
所以我们要改一下 son s o n fail f a i l 的定义,改成指向原来应该指向的点代表的串的长度 len l e n ,比如原来 son[a] s o n [ a ] fail[b] f a i l [ b ] 都指向点 c c ,现在他们要指向len[c]
然后我们再对每个点可持久化每个长度 len l e n 下标对应的点 id[len] i d [ l e n ]
这样我们插入 c c 的时候,只要更新x son s o n ,用 fail[x] f a i l [ x ] 维护 fail[y] f a i l [ y ] son[y] s o n [ y ] 就继承 son[fail[y]] s o n [ f a i l [ y ] ] ,再维护 y y id就行了

复杂度 O(nlogn) O ( n l o g n )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 610000;
const int maxp = maxn*25;

int n,m,type;
struct segment
{
    int cnt;
    int seg[maxp],lc[maxp],rc[maxp];
    int loc,c;
    int newnode(int x)
    {
        ++cnt;
        lc[cnt]=lc[x],rc[cnt]=rc[x];
        seg[cnt]=seg[x];
        return cnt;
    }
    void upd(int &x,const int l,const int r)
    {
        x=newnode(x);
        if(l==r){ seg[x]=c;return; }
        int mid=(l+r)>>1;
        loc<=mid?upd(lc[x],l,mid):upd(rc[x],mid+1,r);
    }
    int q(int x,const int l,const int r)
    {
        if(l==r) return seg[x];
        int mid=(l+r)>>1;
        return loc<=mid?q(lc[x],l,mid):q(rc[x],mid+1,r);
    }
}Son,P;
int tot,fl[maxn],len[maxn],rootp[maxn],rootson[maxn];
int stri[maxn];
int ans;

void add(int i,int x,int c)
{
    x=stri[x];
    int now=++tot; stri[i]=now;
    len[now]=len[x]+1;

    int y=++tot; fl[y]=fl[x],len[y]=len[x],rootp[y]=rootp[x],rootson[y]=rootson[x];
    P.loc=len[y],P.c=y,P.upd(rootp[y],0,n);

    P.loc=fl[y]; int tmp=P.q(rootp[y],0,n);
    Son.loc=c; tmp=Son.q(rootson[tmp],1,m);
    P.loc=tmp; tmp=P.q(rootp[y],0,n);

    Son.loc=c,Son.c=len[now],Son.upd(rootson[y],1,m);

    fl[now]=len[tmp];
    rootp[now]=rootp[y];
    P.loc=len[now],P.c=now,P.upd(rootp[now],0,n);
    rootson[now]=rootson[tmp];

    //P.loc=0,printf("%d\n",P.q(rootp[now],0,n));
    //Son.loc=1; int temp=Son.q(rootson[now],1,m);
    //int ii=1;
}

int main()
{
    freopen("string.in","r",stdin);
    freopen("string.out","w",stdout);

    read(n); read(m); read(type);
    for(int i=1;i<=n;i++)
    {
        int x,c; read(x); read(c);
        x^=ans,c^=ans;

        add(i,x,c);
        printf("%d\n",ans=(len[stri[i]]-fl[stri[i]]));
        ans*=type;
    }

    return 0;
}

C:

显然要对每个 k k ,预处理出ans[k]表示有多少个 (x,y) ( x , y ) ,满足 f(x,y)=k f ( x , y ) = k
如果暴力的做,就是枚举 x x ,然后以x为根,dfs一遍整棵树,我们将 ( ′ ( ′ 视为1, ) ′ ) ′ 视为-1,如果 S(x,y) S ( x , y ) 满足任意前缀非负任意后缀非正,然后我们就可以 O(n2) O ( n 2 ) 的处理出这个东西
要加速这个暴力的话,考虑点分治,对每个分治中心 x x 往下dfs下去分别搜可以作为左半边的串和可以作为右半边的串,比如左半边的串,要求从x y y ,任意后缀非负,且和为正,设和为s,拿他和右半边的和为 s − s 的那些串合并,合并的时候可以用FFT加速
因为对于每个分治中心去计算答案是,设当前树的大小是 n n 的,合并时多项式的次数和是O(n)的,这次计算的复杂度是 O(nlogn) O ( n l o g n )
所以总复杂度是 O(nlog2n) O ( n l o g 2 n )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define pb push_back
#define SZ(x) (int)x.size()
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
inline void up(int &a,const int &b){if(a<b)a=b;}
const int maxn = 210000;
const double pi = acos(-1);

namespace FFT
{
    struct E
    {
        double x,y;
        friend inline E operator +(const E &x,const E &y){return (E){x.x+y.x,x.y+y.y};}
        friend inline E operator -(const E &x,const E &y){return (E){x.x-y.x,x.y-y.y};}
        friend inline E operator *(const E &x,const E &y){return (E){x.x*y.x-x.y*y.y,x.x*y.y+x.y*y.x};}
    }s1[maxn],s2[maxn];
    int N,id[maxn],ln;
    void dft(E f[],int sig)
    {
        for(int i=0;i<N;i++) if(i<id[i]) swap(f[i],f[id[i]]);
        for(int m=2;m<=N;m<<=1)
        {
            int t=m>>1;
            E w=(E){cos(2*pi/m),sin(2*pi*sig/m)};
            for(int j=0;j<N;j+=m)
            {
                E wn=(E){1,0};
                for(int i=0;i<t;i++)
                {
                    E tx=f[j+i],ty=f[j+i+t]*wn;
                    f[j+i]=tx+ty;
                    f[j+i+t]=tx-ty;
                    wn=wn*w;
                }
            }
        }
        if(sig==-1)
        {
            for(int i=0;i<N;i++) f[i].x/=(double)N;
        }
    }
    void solve(int f[],int g[],int fn,int gn)
    {
        N=1,ln=0;
        while(N<=fn+gn) N<<=1,ln++;
        for(int i=1;i<N;i++) id[i]=id[i>>1]>>1|(i&1)<<(ln-1);

        for(int i=0;i<=fn;i++) s1[i]=(E){(double)f[i],0};
        for(int i=fn+1;i<N;i++) s1[i]=(E){0,0};

        for(int i=0;i<=gn;i++) s2[i]=(E){(double)g[i],0};
        for(int i=gn+1;i<N;i++) s2[i]=(E){0,0};

        dft(s1,1); dft(s2,1);
        for(int i=0;i<N;i++) s1[i]=s1[i]*s2[i];
        dft(s1,-1);

        for(int i=0;i<N;i++) f[i]=(int)(s1[i].x+0.5);
    }
}

int n,m;
int c[maxn];
ll ans[maxn];
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn],v[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int siz[maxn];
int findrt(int x,int fa,int N)
{
    siz[x]=1; int mxs=0;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[k]&&y!=fa)
    {
        int re=findrt(y,x,N); if(re) return re;
        siz[x]+=siz[y];
        up(mxs,siz[y]);
    }
    if(mxs*2<=N&&(N-siz[x])*2<=N) return x;
    return 0;
}
vector<int>Vl[maxn],Vr[maxn];
int lx,rx;
void dfsl(int x,int fa,int sum,int mx,int now)
{
    siz[x]=1;

    sum+=c[x]; up(lx,sum);
    if(sum>mx) mx=sum,now=-1;
    if(sum==mx) Vl[sum].pb(++now);

    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=fa&&!v[k])
        dfsl(y,x,sum,mx,now),siz[x]+=siz[y];
}
void dfsr(int x,int fa,int sum,int mn,int now)
{
    sum+=c[x]; up(rx,-sum);
    if(sum<mn) mn=sum,now=-1;
    if(sum==mn) Vr[-sum].pb(++now);

    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=fa&&!v[k])
        dfsr(y,x,sum,mn,now);
}
int f[maxn],g[maxn],fn,gn;
void calc(int sig)
{
    for(int i=0,ui=min(lx,rx);i<=ui;i++)
    {
        for(int j=0;j<=fn+gn;j++) f[j]=g[j]=0;
        fn=gn=0;

        for(int j=0;j<SZ(Vl[i]);j++)
        {
            int x=Vl[i][j]; 
            f[x]++; up(fn,x);
        }
        for(int j=0;j<SZ(Vr[i]);j++)
        {
            int x=Vr[i][j];
            g[x]++; up(gn,x);
        }
        FFT::solve(f,g,fn,gn);
        for(int j=0;j<=fn+gn;j++) ans[j+(i>0)]+=f[j]*sig;
    }
}
void Divide_And_Conquer(int x,int N)
{
    x=findrt(x,0,N);
    while(lx>=0) Vl[lx--].clear();
    while(rx>=0) Vr[rx--].clear();
    dfsl(x,0,0,0,0);
    Vr[rx=0].pb(0);
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[k])
        dfsr(y,x,0,0,0);
    calc(1);

    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[k])
    {
        while(lx>=0) Vl[lx--].clear();
        while(rx>=0) Vr[rx--].clear();
        dfsl(y,x,c[x],max(c[x],0),0);
        dfsr(y,x,0,0,0);
        calc(-1);
    }
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(!v[k])
    {
        v[k]=v[k^1]=1;
        Divide_And_Conquer(y,siz[y]);
    }
}

char str[110];
int main()
{
    freopen("inverse.in","r",stdin);
    freopen("inverse.out","w",stdout);

    len=1;

    read(n);
    for(int i=1;i<n;i++)
    {
        int x,y; read(x),read(y);
        ins(x,y),ins(y,x);
    }
    for(int i=1;i<=n;i++)
    {
        scanf("%s",str);
        c[i]=str[0]=='('?1:-1;
    }

    Divide_And_Conquer(1,n);

    read(m);
    while(m--)
    {
        int x; read(x);
        printf("%lld\n",ans[x]);
    }

    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值