NOI2019省选模拟赛 第五场

爆炸了QAQ

传送门

\(A\) \(Mas\)的童年

这题我怎么感觉好像做过……我记得那个时候还因为没有取\(min\)结果\(100\to 0\)……

因为是个异或我们肯定得按位考虑贡献了

\(a\)做个前缀异或和,记为\(s_i\),那么就是要找到

\[\max_{j<i}\{s_j+(s_j\oplus s_i)\}\]

我们假设\(s_i\)\(k\)位为\(a\)\(s_j\)\(k\)位为\(b\)\(s_j+(s_j\oplus s_i)\)\(k\)位为\(c\)

然后分类讨论\(a,b\)的取值

\[a=1,b=0\to c=1\]

\[a=1,b=1\to c=1\]

\[a=0,b=1\to c=2\]

\[a=0,b=0\to c=0\]

那么我们显然可以按位贪心,如果\(s_i\)\(k\)位为\(1\),那么\(s_j\)的第\(k\)位无论取\(1\)还是取\(0\)都没有关系。如果\(s_i\)\(k\)位为\(0\),那么如果在满足之前的一堆限制的条件下(即之前可能有几位也需要强制取\(1\)),\(s_j\)的第\(k\)位取\(1\)的数仍然存在的话,那么肯定是取\(1\)最优。而且显然如果不取\(1\),后面怎么取都不会更优

所以有一个\(O(n^2)\)的做法,记一个\(vis_s\)表示是否存在\(s\)这几位全为\(1\)的数,然后处理完\(a_i\)之后暴力枚举子集,把对应的\(vis\)设为\(1\)就行了

所以我们成功优化到了和暴力相同的复杂度……

我们换个思路,记\(pos_s\)表示“满足\(s\)这几位全为\(1\)的数中,最小的位置是哪个”,那么每一次判断前面是否有某个数\(s\)这几位全为\(1\)的时候只要比较一下\(pos_s\)\(i\)的大小就可以了。\(pos\)可以直接用类似高位前缀和的思路\(O(n\log n)\)搞出来

//minamoto
#include<bits/stdc++.h>
#define R register
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
template<class T>inline bool cmin(T&a,const T&b){return a>b?a=b,1:0;}
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
int read(){
    R int res,f=1;R char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
char sr[1<<21],z[20];int C=-1,Z=0;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
void print(R int x){
    if(C>1<<20)Ot();if(x<0)sr[++C]='-',x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]=' ';
}
const int N=(1<<20)+5;
int a[N],pos[N],n,x,res;
int main(){
//  freopen("testdata.in","r",stdin);
//  freopen("testdata.out","w",stdout);
    n=read();
    fp(i,0,1048575)pos[i]=n+1;
    fp(i,1,n)a[i]=read()^a[i-1],cmin(pos[a[i]],i);
    fp(j,0,19)fd(i,1048575,0)if(i>>j&1)cmin(pos[i^(1<<j)],pos[i]);
    fp(i,1,n){
        x=0,res=0;
        fd(j,19,0)if(a[i]>>j&1^1){
            pos[x^(1<<j)]<=i?(x^=(1<<j),res+=(1<<(j+1))):0;
        }else res+=(1<<j);
        print(res);
    }
    return Ot(),0;
}

\(B\) \(Z\)的家乡

我永远讨厌数据结构题\(.jpg\)

如果我们记\(mx_u\)表示\(u\)这棵子树里最大的标号,那么\(u\)的子树\(v\)的遍历顺序就是按\(mx_v\)从大到小排序之后的顺序

我们可以记一个\(f_u\)表示\(u\)这个子树中遍历顺序的对应的值,那么\(f_u\)就是所有子树的\(f_v\)排序之后合并起来,再加上自己的值。如果每次暴力往上跳,随机数据下树高和儿子个数都是期望\(O(\log n)\)的,可以\(AC\)

然而这里数据并不随机

我们发现每一次加入点的过程都类似于\(LCT\)\(access\),而且只有\(access\)时候虚边转为实边的那几个地方需要更新答案

由虚边转为实边,就是说这一棵子树的遍历顺序要到最前面

我们可以维护一个\(splay\),对于每一个在\(LCT\)上父亲边由实边转为虚边的点,把它们在\(splay\)上整个子树拆下来,然后重新插进去,插进去的时候使它们新的值最大

并不建议看代码理解因为我的代码已经丑到自己都看不下去了

//minamoto
#include<bits/stdc++.h>
#define R register
#define ll long long
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
char buf[1<<21],*p1=buf,*p2=buf;
inline char getc(){return p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?EOF:*p1++;}
int read(){
    R int res,f=1;R char ch;
    while((ch=getc())>'9'||ch<'0')(ch=='-')&&(f=-1);
    for(res=ch-'0';(ch=getc())>='0'&&ch<='9';res=res*10+ch-'0');
    return res*f;
}
char sr[1<<21],z[20];int C=-1,Z=0;
inline void Ot(){fwrite(sr,1,C+1,stdout),C=-1;}
void print(R int x){
    if(C>1<<20)Ot();if(x<0)sr[++C]='-',x=-x;
    while(z[++Z]=x%10+48,x/=10);
    while(sr[++C]=z[Z],--Z);sr[++C]='\n';
}
const int N=2e5+5,P=998244353,Base=257933,inv=612171754;
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R int x,R int y){
    R int res=1;
    for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
    return res;
}
int st[N],ss[N],bin[N],top,n;
struct LCT{
    struct node{
        node *ch[2],*fa;int sz,tag,id;
        node(){}
        inline void ppd(R int x){sz+=x,tag+=x;}
        inline void pd(){if(tag)ch[0]->ppd(tag),ch[1]->ppd(tag),tag=0;}
        inline bool is(){return fa->ch[0]!=this&&fa->ch[1]!=this;}
        inline bool re(){return fa->ch[1]==this;}
        void update(){if(!is())fa->update();pd();}
        void rotate(){
            node *y=fa,*z=y->fa;int d=re();
            if(!y->is())z->ch[y->re()]=this;
            fa=z,ch[d^1]->fa=y,y->ch[d]=ch[d^1],ch[d^1]=y,y->fa=this;
        }
    }pool[N],*p[N],*null;int num;
    LCT(){null=&pool[num++],null->fa=null->ch[0]=null->ch[1]=null,null->sz=null->tag=null->id=0;}
    inline node *newnode(){pool[num].fa=pool[num].ch[0]=pool[num].ch[1]=null;return &pool[num++];}
    inline void Pre(R int n){fp(i,1,n)p[i]=newnode(),p[i]->id=i;}
    //指针别xjb指!
    //人工开个NULL! 
    void splay(node *x){
        x->update();
        for(R node *y=x->fa,*z=y->fa;!x->is();y=x->fa,z=y->fa){
            if(!y->is())(x->re()^y->re())?x->rotate():y->rotate();
            x->rotate();
        }
    }
    void access(node *x){
        node *t=null;
        while(x!=null){
            node *now=t;
            while(now->ch[0]!=null)now->pd(),now=now->ch[0];
            st[++top]=now->id,ss[top]=now->sz;
            splay(x);
            x->ch[1]=t,t->fa=x,t=x,x=x->fa;
        }
        ++t->tag,++t->sz;
    }
}A;
struct Splay{
    struct node{
        node *ch[2],*fa;int sz,res,id;
        inline bool re(){return fa->ch[1]==this;}
        inline bool is(){return fa->ch[0]!=this&&fa->ch[1]!=this;}
        void upd(){
            res=(ch[0]->res+1ll*id*bin[ch[0]->sz]+1ll*bin[ch[0]->sz+1]*ch[1]->res)%P;
            sz=ch[0]->sz+ch[1]->sz+1;
        }
        void rotate(){
            node *y=fa,*z=y->fa;int d=re();
            if(!y->is())z->ch[y->re()]=this;
            fa=z,ch[d^1]->fa=y,y->ch[d]=ch[d^1],ch[d^1]=y,y->fa=this,y->upd();
        }
    }pool[N],*null,*p[N],*S,*T,*rt;int num;
    Splay(){null=&pool[num++],null->fa=null->ch[0]=null->ch[1]=null,null->sz=null->res=null->id=0;}
    inline node *newnode(){pool[num].fa=pool[num].ch[0]=pool[num].ch[1]=null;return &pool[num++];}
    void Pre(R int n){
        fp(i,1,n)p[i]=newnode(),p[i]->id=i,p[i]->sz=1,p[i]->res=i;
        S=newnode(),T=newnode(),rt=S,S->sz=T->sz=1;
        S->ch[1]=T,T->fa=S,T->upd(),S->upd();
    }
    //指针别xjb指!
    //人工开个NULL! 
    void splay(node *x,node *c){
        if(c==null)rt=x;
        for(R node *y=x->fa;x->fa!=c;y=x->fa){
            if(y->fa!=c)(x->re()^y->re())?x->rotate():y->rotate();
            x->rotate();
        }
        x->upd();
    }
    node *get(int x){
        node *now=rt;
        while(now!=null){
            int sz=now->ch[0]->sz+1;
            if(sz==x)return now;
            else if(sz>x)now=now->ch[0];
            else now=now->ch[1],x-=sz;
        }
    }
    int rk(int x){
        splay(p[x],null);
        return p[x]->ch[0]->sz+1;
    }
    void insert(node *x,int k){
        node *t1=get(k+1),*t2=get(k+2);
        splay(t1,null),splay(t2,t1);
        t2->upd(),t1->upd();
        t2->ch[0]=x,x->fa=t2,t2->upd(),t1->upd();
    }
    inline int calc(){return mul(rt->res,inv);}
    void solve(){
        fd(i,top,3){
            int tr=rk(st[i]),tl=tr-ss[i];
            node *cl=get(tl),*cr=get(tr+1);
            splay(cl,null),splay(cr,cl);
            node *tmp=cr->ch[0];
            tmp->fa=cr->ch[0]=null;
            cr->upd(),cl->upd(),insert(tmp,0);
        }
    }
}B;
int x;
int main(){
//  freopen("testdata.in","r",stdin);
    n=read(),bin[0]=1;
    fp(i,1,n+2)bin[i]=mul(bin[i-1],Base);
    A.Pre(n),B.Pre(n),B.insert(B.p[1],0);
    fp(i,2,n){
        top=0,x=read(),A.p[i]->fa=A.p[x],A.access(A.p[i]);
        B.solve();
        B.insert(B.p[i],0);
        print(B.calc());
    }
    return Ot(),0;
}

\(C\) 战棋游戏

做一道题学一大堆的经历不管什么时候都是很有趣的呢……

前置芝士

环的染色方案数

我们现在要对一个环进行染色,要求两两颜色不同,设点的个数为\(n\),颜色个数为\(c\),求方案数

\(f_i\)表示\(i\)个点的染色方案数。我们可以钦定一个开头,设为\(1\),并顺时针一次记为\(2,3,...,n\)

如果\(1\)号点和\(n-1\)号点颜色不同,那么方案数为\(f_{n-1},\)\(n\)号点可以选的方案数就是\((c-2)\)

如果\(1\)号点和\(n-1\)号点颜色相同,那么方案数为\(f_{n-2}\),即等价于\(n-2\)个点的环再插一个进去,此时\(n\)号点的颜色方案数为\(c-1\)

所以我们可以求得递推公式

\[f_n=(c-2)f_{n-1}+(c-1)f_{n-2}\]

初值为\(f_0=1,f_1=c\)

根据数列的特征方程(因为我数学课老师讲这个的时候我快睡着了所以我也不是很清楚所以具体百度),我们可以求得\(f_n\)的通项公式为

\[f_n=(c-1)^n+(-1)^n(c-1)\]

如果强制环的两个端点相同的话,方案显然就是\(f_{n-1}\)

\(O(n^2)\)多项式\(Ln\)和多项式\(Exp\)

其实这里是可以\(O(n\log n)\)搞的然而代码实在太长而,且这里数据范围够小所以我们可以\(O(n^2)\)做,如果您耐心够好而且对自己的多项式码力有自信完全可以跳过这一节

经过\(shadowice\)巨巨一个下午的调教我终于会了

对于多项式\(Ln\),我们需要暴力的就是它求逆的过程,即我们需要暴力计算一个形如

\[F(x)={P(x)\over Q(x)}\]

的柿子

根据\(Ln\)的使用条件,得有\([x^0]Q(x)=1\)

我们令\(T(x)=Q(x)-1\),则\(Q(x)=T(x)+1\),即

\[F(x)={P(x)\over T(x)+1}\]

\[F(x)(T(x)+1)=P(x)\]

\[F(x)=P(x)-F(x)T(x)\]

因为有\([x^0]T(x)=0\),所以我们就可以直接暴力卷积把\(F(x)\)给一项一项卷出来了,卷完之后积分一下就可以求出\(Ln\)

然后是多项式\(Exp\),即要求

\[F(z)=e^{G(z)}\]

\[F'(z)=G'(z)F(z)\]

没了

题解

我们很明显可以以骑士为关键点拆成若干部分,那么不同部分之间就是条链,影响方案数的只有两个端点颜色是否相同了

ps:我们代码里预处理的方案数没有算上两个端点的方案,所以两端相同的方案要除以\(c\),两端不同的方案要除以\(c(c-1)\)

我们\(2^k\)爆搜集合,表示强制\(s\)中的所有点为相同颜色,看看是否合法,如果合法的话记录总的贡献(即每一个点到它对应的下一个点的那条链上的方案数)

然后我们集合并卷积一下,求出\(f_{s,i}\)表示只允许\(s\)这个集合中有相同的颜色,且钦定的颜色相同的点的个数为\(i\)的总贡献

我们把\(f_s\)看成一个多项式,那么\({f_{s}}^c\)就代表选\(c\)种颜色的方案数,它的第\(k\)项就是总的方案

然而显然这样会算上一些不合法的情况,所以我们还得对\(s\)进行一个容斥

代码全是抄\(std\)的,因为这代码实在是太难写了我根本写不下去……

//minamoto
#include<bits/stdc++.h>
#define R register
#define ll long long
#define fp(i,a,b) for(R int i=(a),I=(b)+1;i<I;++i)
#define fd(i,a,b) for(R int i=(a),I=(b)-1;i>I;--i)
#define go(u) for(int i=head[u],v=e[i].v;i;i=e[i].nx,v=e[i].v)
using namespace std;
const int N=25,M=(1<<20)+5,P=1e9+7;
inline int add(R int x,R int y){return x+y>=P?x+y-P:x+y;}
inline int dec(R int x,R int y){return x-y<0?x-y+P:x-y;}
inline int mul(R int x,R int y){return 1ll*x*y-1ll*x*y/P*P;}
int ksm(R ll x,R ll y){
    R int res=1;x%=P;
    for(;y;y>>=1,x=mul(x,x))(y&1)?res=mul(res,x):0;
    return res;
}
int sz[M],f[M][N],same[N],diff[N],num[N],pos[N],g[N],h[N],inv[N],mp[N][N],vis[M],lg[M];
ll n,c,len,a[N];int k,m,iv,lim,res;
inline bool cmp(const int &x,const int &y){return a[x]<a[y];}
int main(){
//  freopen("testdata.in","r",stdin);
    scanf("%lld%d%d%lld",&n,&k,&m,&c),c%=P,iv=ksm(c,P-2),lim=(1<<k);
    fp(i,0,k-1)scanf("%lld",&a[i]),num[i]=i;
    sort(num,num+k,cmp);
    fp(i,0,k-1){
        pos[num[i]+1]=i,len=a[num[(i+1)%k]]-a[num[i]];
        if(i==k-1)len+=n;
        if(len&1){
            same[i]=mul(iv,dec(ksm(c-1,len),c-1)),
            diff[i]=mul(iv,add(ksm(c-1,len),1));
        }else{
            same[i]=mul(iv,add(ksm(c-1,len),c-1)),
            diff[i]=mul(iv,dec(ksm(c-1,len),1));
        }
    }
    for(R int i=1,u,v;i<=m;++i)scanf("%d%d",&u,&v),mp[pos[u]][pos[v]]=mp[pos[v]][pos[u]]=1;
    vis[0]=1,f[0][0]=1;
    for(R int i=2;i<lim;i<<=1)lg[i]=lg[i>>1]+1;
    fp(s,1,lim-1){
        vis[s]=1,sz[s]=sz[s>>1]+(s&1);
        int u=lg[s&-s];
        if(!vis[s^(s&-s)]){vis[s]=0;continue;}
        fp(v,u+1,k-1)if(s>>v&1)vis[s]&=!mp[u][v];
        if(!vis[s])continue;
        f[s][sz[s]]=1;
        fp(i,0,k-1)if(s>>i&1){
            int v=(i+1)%k;
            f[s][sz[s]]=mul(f[s][sz[s]],(s>>v&1)?same[i]:diff[i]);
        }
    }
    for(R int i=1;i<lim;i<<=1)
        for(R int j=0;j<lim;j+=(i<<1))
            fp(l,0,i-1)
                fp(s,0,k)f[i+j+l][s]=add(f[i+j+l][s],f[j+l][s]);
    inv[0]=inv[1]=1;
    fp(i,2,k)inv[i]=mul(P-P/i,inv[P%i]);
    fp(s,0,lim-1){
        fp(i,0,k)h[i]=f[s][i];
        --h[0];
        fp(i,0,k){
            g[i]=0;
            fp(j,0,i)g[i]=add(g[i],mul(h[i-j],g[j]));
            g[i]=dec(mul(h[i+1],i+1),g[i]);
        }
        fd(i,k,1)g[i]=mul(g[i-1],inv[i]);g[0]=0;
        fp(i,1,k)g[i]=mul(g[i],c);
        fp(i,1,k)g[i-1]=mul(g[i],i);g[k]=0;
        h[0]=1;
        fp(i,0,k-1){
            int res=0;
            fp(j,0,i)res=add(res,mul(g[i-j],h[j]));
            h[i+1]=mul(res,inv[i+1]);
        }
        ((k-sz[s])&1)?res=dec(res,h[k]):res=add(res,h[k]);
    }
    printf("%d\n",res);
    return 0;
}

转载于:https://www.cnblogs.com/bztMinamoto/p/10634601.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值