6.15联考题解

A:

我们尝试给每个点划分联通块
定义一个联通块的位置是它里面深度最浅的点
那么一个点要么属于他某个祖先的联通块,要么自己这里有一个联通块
于是可以做个dp
但是dp状态里要有个当前的最大值,状态数就 n 2
我们可以先二分,就不用记录当前最大的联通块大小了
然后记 f [ i ] [ 0 / 1 ] 表示联通块位置在 i i 这个位置的联通块中 没有/有 标记点,子树里其他联通块合法,这个联通块的最小大小
g [ i ] [ 0 / 1 ] 表示联通块在 i 的祖先某个位置, i i 的子树里属于这个联通块的点中 没有/有 标记点,子树里其他联通块合法,这个联通块的最小大小

复杂度 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 = 210000;

int n,K;
int s[maxn];
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int u;
int f[maxn][2],g[maxn][2];
void upd(int &a,const int &b){if(a==-1||a>b)a=b;}
void dp(const int x,const int fa)
{
    f[x][0]=f[x][1]=g[x][0]=g[x][1]=-1;
    f[x][s[x]]=g[x][s[x]]=1;

    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=fa)
    {
        dp(y,x);
        int tf[2],tg[2];
        tf[0]=f[x][0],tf[1]=f[x][1],tg[0]=g[x][0],tg[1]=g[x][1];
        f[x][0]=f[x][1]=g[x][0]=g[x][1]=-1;

        for(int i=0;i<2;i++)
        {
            if(f[y][1]!=-1)
            {
                if(tf[i]!=-1) upd(f[x][i],tf[i]);
                if(tg[i]!=-1) upd(g[x][i],tg[i]);
            }
            if(g[y][0]!=-1)
            {
                int c=g[y][0];
                if(tf[i]!=-1) upd(f[x][i],tf[i]+c);
                if(tg[i]!=-1) upd(g[x][i],tg[i]+c);
            }
            if(g[y][1]!=-1)
            {
                int c=g[y][1];
                if(tf[i]!=-1) upd(f[x][1],tf[i]+c);
                if(tg[i]!=-1) upd(g[x][1],tg[i]+c);
            }
        }
    }

    for(int i=0;i<2;i++)
    {
        if(f[x][i]>u) f[x][i]=-1;
        if(g[x][i]>u) g[x][i]=-1;
    }
}
int judge(int mid)
{
    u=mid;
    dp(1,0);
    return f[1][1]!=-1&&f[1][1]<=u;
}

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

    read(n); read(K);
    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<=K;i++)
    {
        int x; read(x);
        s[x]=1;
    }

    int l=1,r=n;
    while(l<=r)
    {
        int mid=(l+r)>>1;
        if(judge(mid)) r=mid-1;
        else l=mid+1;
    }
    printf("%d\n",r+1);

    return 0;
}

B:
n 个点的无向连通图中联通块个数的 m 次方的和

考场上推二项式展开,写了个 O ( m n l o g 2 n + T ) 的分治FFT
慢的飞起但是出题人放过了这个复杂度

然后有一种更优秀的用斯特林数推的 O ( m n l o g n + T m ) 的做法

x n 的意义是n个球,每个球选x个盒子中的一个盒子装,允许有空盒子
于是有 x n = i = 0 x { i n } ( x i ) i !

于是我们可以设 f [ i ] [ j ] 表示对于一个有 x 个联通块的图,我们定义它的价值为 v a l = ( x j ) j ! ,求所有 i 个点的图的价值和

我们考虑 ( n m ) m ! 这个东西的组合意义,即从 n 个数中有序的选出 m 组数的方案数

根据这个列出 f 的转移式

f [ i ] [ j ] = k = 1 i ( i k ) g [ k ] f [ i k ] [ j 1 ]

其中 g [ k ] 表示 k 个点的无向连通图个数,可以用图的总数减 h [ i ] i 个点的无向不连通图个数)算,可以分治FFT做到 O ( n l o g 2 ) 或者多项式求逆做到 O ( n l o g n ) ,具体做法不在此赘述

发现 f [ i ] [ j ] 的dp式只和 f [ i ] [ j 1 ] 有关,我们可以分层dp,每层用一个FFT

最终的答案 a n s = j = 0 m { j m } f [ n ] [ j ]

复杂度就可以做到 O ( m n l o g n + T m )

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 = 120005;
const int maxm = 16;
const int mod = 998244353;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,const int &b){a-=b;if(a<0)a+=mod;}

int pw(int x,int k)
{
    int re=1;
    for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
        re=(ll)re*x%mod;
    return re;
}
int inv(int x){ return pw(x,mod-2); }

int s[maxn],invs[maxn],num[maxn],dp[maxm][maxm];
void pre()
{
    s[0]=1; for(int i=1;i<maxn;i++) s[i]=(ll)s[i-1]*i%mod;
    invs[maxn-1]=inv(s[maxn-1]);
    for(int i=maxn-2;i>=0;i--) invs[i]=(ll)invs[i+1]*(i+1)%mod;

    for(int i=0;i<maxn;i++) num[i]=pw(2,(ll)i*(i-1)/2ll%(mod-1));

    dp[0][0]=1;
    for(int i=1;i<maxm;i++) for(int j=1;j<=i;j++)
        dp[i][j]=(dp[i-1][j-1]+(ll)dp[i-1][j]*j%mod)%mod;
}
int C(int i,int j){ return i<j?0:(ll)s[i]*invs[j]%mod*invs[i-j]%mod; }

namespace NTT
{
    int N,ln;
    int id[maxn],w[maxn];
    void pre(int n)
    {
        N=1,ln=0;
        while(N<=n) N<<=1,ln++;
        for(int i=0;i<N;i++) id[i]=id[i>>1]>>1|(i&1)<<(ln-1);
        w[0]=1,w[1]=pw(3,(mod-1)/N);
        for(int i=2;i<=N;i++) w[i]=(ll)w[i-1]*w[1]%mod;
    }
    void DFT(int f[],int sig)
    {
        for(int i=1;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,tt=N/m;
            for(int i=0;i<t;i++)
            {
                int wn=sig==1?w[i*tt]:w[N-i*tt];
                for(int j=i;j<N;j+=m)
                {
                    int tx=f[j],ty=(ll)f[j+t]*wn%mod;
                    f[j]=tx; add(f[j],ty);
                    f[j+t]=tx; dec(f[j+t],ty);
                }
            }
        }
        if(sig==-1)
        {
            int invn=inv(N);
            for(int i=0;i<N;i++) f[i]=(ll)f[i]*invn%mod;
        }
    }
    int f[maxn],g[maxn],tf[maxn];
    void main(int n,int m)
    {
        pre(n+m);
        for(int i=n+1;i<N;i++) f[i]=0;
        for(int i=m+1;i<N;i++) g[i]=0;

        if(N<=16)
        {
            for(int i=0;i<N;i++) tf[i]=f[i],f[i]=0;
            for(int i=0;i<=n;i++) for(int j=0;j<=m;j++)
                add(f[i+j],(ll)tf[i]*g[j]%mod);
        }
        else
        {
            DFT(f,1); DFT(g,1);
            for(int i=0;i<N;i++) f[i]=(ll)f[i]*g[i]%mod;
            DFT(f,-1);
        }
    }
}

int n,m;
int f[maxn][maxm],g[maxn],h[maxn];

void solveg(int l,int r)
{
    if(l==r)
    {
        if(l) h[l]=(ll)h[l]*s[l-1]%mod;
        g[l]=(num[l]-h[l]+mod)%mod;
        return;
    }
    int mid=(l+r)>>1;
    solveg(l,mid);

    int tn=mid-l,tm=r-l;
    NTT::f[0]=0; for(int i=max(1,l);i<=mid;i++) NTT::f[i-l]=(ll)g[i]*invs[i-1]%mod;
    for(int i=0;i<=tm;i++) NTT::g[i]=(ll)num[i]*invs[i]%mod;
    NTT::main(tn,tm);
    for(int i=mid+1;i<=r;i++) add(h[i],NTT::f[i-l]);

    solveg(mid+1,r);
}
int T,un,um;
int op[1100][2];

int nowj;
void solvef()
{
    NTT::g[0]=0; for(int i=1;i<=un;i++) NTT::g[i]=(ll)g[i]*invs[i]%mod;
    for(int i=0;i<=un;i++) NTT::f[i]=(ll)f[i][nowj-1]*invs[i]%mod;
    NTT::main(un,un);
    for(int i=0;i<=un;i++) f[i][nowj]=(ll)NTT::f[i]*s[i]%mod;
}

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

    pre();

    scanf("%d",&T);
    for(int i=1;i<=T;i++) 
    {
        scanf("%d%d",&op[i][0],&op[i][1]);
        un=max(un,op[i][0]),um=max(um,op[i][1]);
    }

    h[0]=0,g[0]=1;
    solveg(0,un);
    /*for(int i=1;i<maxn;i++)
    {
        for(int j=1;j<i;j++) add(h[i],(ll)C(i-1,j-1)*g[j]%mod*num[i-j]%mod);
        g[i]=(num[i]-h[i]+mod)%mod;
    }*/
    for(int i=0;i<=un;i++) f[i][0]=num[i];
    //for(int j=0;j<maxm;j++) for(int k=0;k<=j;k++) add(tf[0][j],C(j,k)*f[0][k]);
    for(nowj=1;nowj<=um;nowj++) solvef();
    /*for(int i=1;i<2001;i++) for(int j=0;j<maxm;j++)
    {
        for(int k=1;k<=i;k++) add(f[i][j],(ll)C(i-1,k-1)*g[k]%mod*tf[i-k][j]%mod);
        for(int k=0;k<=j;k++) add(tf[i][j],(ll)C(j,k)*f[i][k]%mod);
    }*/

    for(int i=1;i<=T;i++) 
    {
        int ans=0;
        for(int j=0;j<=op[i][1];j++) add(ans,(ll)dp[op[i][1]][j]*f[op[i][0]][j]%mod);
        printf("%d\n",ans);
    }

    return 0;
}

C:
题解说是后缀平衡树
还套上了很多看起来很nb的技巧
不会
写不动
弃疗

猜你喜欢

转载自blog.csdn.net/l_0_forever_lf/article/details/80709953