【考试总结】2022-03-20


签到

枚举哪些位置超出了限制,注意这里需要将 \(n\)\(1\),那么可以直接列出答案的计算表达式

\[\sum_{S\subseteq U}(-1)^{|S|}\binom{n+m+(c-1)|S|-\sum\limits_{x\in S} b^{x}}{m} \]

如果我们可以保证 \(A=n+m+(c-1)|S|\ge \sum\limits_{x\in S} b^{x}\),那么组合数是关于 \(\sum\limits_{x\in S} b^x\) 的多项式

枚举 \(|S|\) 以及 \(\sum\limits_{x\in S} b^x\)\(A\)\(\rm LCP\)(十进制)及后面一位的数字

这里想要得到答案,还需要在低位求出关于 \(\sum b^i\) 的多项式在所有 \(S\) 中的每个次幂的和

\(\rm DP\) 来做:设 \(f_{k}[i][j]\) 表示考虑了 \(\{1,2\dots i\}\) 这些数字并选出了 \(j\)\(b^x\) 并对第 \(k\) 次幂的自变量求和,转移使用二项式定理合并即可

注意需要写高精度除法,但是进制转换只用做一次,那么使用模拟竖式的方法是可行的

Code Display
int Bas=10;
struct node{
    vector a;
    inline int size(){return a.size();}
    inline void adjust(){
        int siz=a.size();
        for(int i=0;i=Bas){
            a.emplace_back(a[siz-1]/Bas);
            a[siz-1]%=Bas;
            ++siz;
        }
        while(siz>1&&!a[siz-1]) a.pop_back(),--siz;
        return ;
    }
    inline int toint(int Mod=mod){
        int siz=a.size(),res=0;
        for(int i=siz-1;i>=0;--i) res=(res*Bas+a[i])%Mod;
        return res;
    }
    inline void init(string x){
        vector num[2]; num[0].resize(x.size());
        int cur=0;
        int len=x.size();
        for(int i=len-1;i>=0;--i) num[cur][i]=x[i]-'0';
        reverse(num[cur].begin(),num[cur].end());
        while(num[cur].size()>1||num[cur][0]>0){
            num[cur^1].clear();
            int tmp=0,siz=num[cur].size();
            bool flag=0;
            for(int i=siz-1;i>=0;--i){
                tmp=tmp*10+num[cur][i];
                if(tmp>=Bas) flag=1;
                if(flag) num[cur^1].emplace_back(tmp/Bas),tmp%=Bas;
            }
            a.emplace_back(tmp);
            cur^=1;
            reverse(num[cur].begin(),num[cur].end());
            siz=num[cur].size(); num[cur].emplace_back(0);
            for(int i=0;i>str_n;
    n.init(str_n); n=n-1;
    pw[0][0]=1; rep(i,1,2000) pw[0][i]=1;
    for(int i=1;i<=2000;++i){
        pw[i][0]=1;
        pw[i][1]=mul(pw[i-1][1],b);
        for(int j=2;j<=2000;++j) pw[i][j]=mul(pw[i][j-1],pw[i][1]);
    }
    dp[0][0][0]=1;
    for(int k=0;k<=m;++k){
        for(int i=1;i<=m;++i){
            for(int j=0;j<=i;++j){
                int sum=dp[i-1][j][k];
                if(j) for(int t=0;t<=k;++t) ckadd(sum,mul(C[k][t],mul(dp[i-1][j-1][t],pw[i][k-t])));
                dp[i][j][k]=sum;
            }
        }   
    }
    for(int S=0;S<=m;++S){
        node A,n_m,n_S,n_c; 
        n_m.init(m);
        n_S.init(S);
        n_c.init(abs(1-c));
        if(c<0&&n+n_m >tmp(m);
        for(int i=0;iS) break;
                vector pwh(1+m);
                pwh[0]=1;
                for(int i=1;i<=m;++i) pwh[i]=mul(pwh[i-1],hig);
                int tmp=0;
                for(int k=0;k<=m;++k){
                    int sumx=0;
                    for(int t=0;t<=k;++t) ckadd(sumx,mul(C[k][t],mul(pwh[k-t],dp[lcp-1][S-cnt][t])));
                    ckadd(tmp,mul(poly[k],sumx));   
                }
                ckadd(sum,tmp);
            }
            if(A.a[lcp]==1) ++cnt,ckadd(hig,pw[lcp][1]);
            if(A.a[lcp]>1) break;
        }
        if(S&1) ckdel(ans,sum); else ckadd(ans,sum); 
    }
    print(ans); putchar('\n');
    return 0;
}

数据结构

将树拍到 \(\rm DFS\) 序上考虑,序列上的带权中点一定在带权重心的子树内,写倍增判定即可

卡常,所以用了 zkw线段树

我还有一个非常麻烦的使用 新重心在两个原来重心的路径上 的结论的方法,比这个垃圾到不知道哪里去了

Code Display
vector G[N];
int n;
int fa[N],dep[N],siz[N],top[N],son[N],dfn[N],ord[N],tim;
int bz[N][20];
inline void dfs2(int x,int topf){
    top[x]=topf; ord[dfn[x]=++tim]=x; if(son[x]) dfs2(son[x],topf);
    for(auto t:G[x]) if(!dfn[t]) dfs2(t,t);
}
inline void dfs1(int x,int fat){
    dep[x]=dep[fa[x]=fat]+(siz[x]=1);
    bz[x][0]=fat;
    for(int i=1;bz[x][i-1];++i) bz[x][i]=bz[bz[x][i-1]][i-1];
    for(auto t:G[x]) if(t!=fat){
        dfs1(t,x); siz[x]+=siz[t];
        if(siz[t]>siz[son[x]]) son[x]=t;
    } return ;
}
ll nec;
struct Seg{
    #define ls p<<1
    #define rs p<<1|1
    int lim;
    ll sum[N<<2],tag[N<<2];
    inline void build(int n){
        lim=1;
        while(lim<=(n+1)) lim<<=1;
        return ;
    }
    inline void upd(int l,int r,ll v){
        l+=lim-1; r+=lim+1;
        int lc=0,rc=0;
        for(int len=1;l!=r-1;l>>=1,r>>=1,len<<=1){
            sum[l]+=lc*v; sum[r]+=rc*v;
            if(!(l&1)) sum[l^1]+=len*v,tag[l^1]+=v,lc+=len;
            if(r&1) sum[r^1]+=len*v,tag[r^1]+=v,rc+=len;
        }
        while(l){
            sum[l]+=lc*v; sum[r]+=rc*v;
            l>>=1; r>>=1;
        }
        return ;
    }
    inline ll Query(int l,int r){
        ll res=0,rc=0,lc=0;
        l+=lim-1; r+=lim+1;
        for(int len=1;l!=r-1;r>>=1,l>>=1,len<<=1){
            res+=tag[l]*lc+tag[r]*rc;
            if(r&1) res+=sum[r^1],rc+=len;
            if(!(l&1)) res+=sum[l^1],lc+=len;
        }
        while(l){
            res+=tag[l]*lc+tag[r]*rc;
            l>>=1; r>>=1;
        }
        return res;
    }
    #undef ls
    #undef rs
    #undef lson
    #undef rson
}T;
signed main(){
    freopen("yyl.in","r",stdin); freopen("yyl.out","w",stdout);
    n=read(); 
    rep(i,1,n-1){
        int u=read(),v=read();
        G[u].push_back(v); G[v].emplace_back(u);
    }
    int Q=read();
    dfs1(1,0); dfs2(1,1); T.build(n);
    while(Q--){
        if(read()-1){
            int u=read(),v=read(),w=read();
            while(top[u]!=top[v]){
                if(dep[top[u]]dep[v]) swap(u,v);
            T.upd(dfn[u],dfn[v],w);
        }else{
            int x=read(),w=read();
            T.upd(dfn[x],dfn[x]+siz[x]-1,w);
        }
        nec=T.sum[1]/2+1; //找到第一个大于等于 nec 的点即可 
        int l=1,r=n-1,pos=n;
        while(l<=r){
            int mid=(l+r)>>1;
            if(T.Query(1,mid)>=nec) pos=mid,r=mid-1;
            else l=mid+1;
        }
        int ans=ord[pos];
        for(int i=19;~i;--i) if(bz[ans][i]){
            int tar=bz[ans][i];
            if(T.Query(dfn[tar],dfn[tar]+siz[tar]-1)<=T.sum[1]/2) ans=bz[ans][i];
        }
        if(T.Query(dfn[ans],dfn[ans]+siz[ans]-1)<=T.sum[1]/2) ans=fa[ans];
        print(ans);
    }
    return 0;
}

爆搜

观察到 \(n\) 个点的无向图最多有 \(\frac n2\) 组匹配,将 \((2i,2i+1)\) 连一条虚边,那么所有的合法匹配一定将点集划分成了若干个环和链

使用虚边将 \((2i,2i+1)\) 绑定成一个虚点,使用 \(\Theta(2^{\frac n2}n^2)\) 的状压 \(\rm DP\) 来求每个集合表示成链/环的权值之和,剩下的工作是一个集合幂级数 \(\exp\)

\(\rm DP\) 链添加一维记录链尾,转移时因为 \((2i,2i+1)\) 被绑定,添加一组虚点的时候可以据此得到新的链尾,注意每条链会被计算正反两次

计算环的方案数时从环上最大标号的点开始 \(\rm DP\),加入的一对虚点标号不超过当前集合最大值即可,也要记录环尾,最后通过环尾和集合内最大值是否有边判定是否合法即可

Code Display
const int N=40;
int n,m,C,G[N][N],inv[N],ifac[N],fac[N];
int chain[1<<18],cyc[1<<18],dp[1<<18][40];

inline int in(int S,int id){
    return S>>(id>>1)&1;
}
struct FPS{
    int p[19]; FPS(){memset(p,0,sizeof(p));}
    FPS operator +(const FPS &a)const{
        FPS res; 
        rep(i,0,m) res.p[i]=add(p[i],a.p[i]);
        return res;
    }
    FPS operator -(const FPS &a)const{
        FPS res; 
        rep(i,0,m) res.p[i]=del(p[i],a.p[i]);
        return res;
    }
}f[1<<18];
inline void Exp(int *f){
    vector g(m+1);
    g[0]=1;
    for(int i=1;i<=m;++i){
        for(int j=0;j>1))][k^1],mul(C,dp[i][j]));
            }
        }
    }
    for(int i=1;i>1))][k^1],mul(C,dp[i][j]));
            }
        }
    }
    for(int i=1;i>1;
        for(int k=0;k>1;
        for(int k=0;k

相关