1. 程式人生 > >Codeforces 1111 E. Tree(虛樹,DP)

Codeforces 1111 E. Tree(虛樹,DP)

cto cond using push end %d inf ces tree

題意


有一棵樹,q個詢問,每次詢問,指定一個點做樹根,再給定k個點,要求把這些點分成不超過m組的方案數,分配的限制是任意兩個有祖先關系的點不能分在同一組。題目還保證了所有的詢問的k加起來不超過1e5。

思路


如果直接在原樹上DP計數,那麽q次詢問下的DP總復雜度是平方級別的,顯然不對。
由於詢問點數的總和很少,所以考慮在虛樹上計數。(不了解虛樹的可以先學習一下,大概思想是根據詢問的點來重新建一顆包含關鍵信息,但是規模較小的樹)於是多次詢問的問題就解決了。
難點轉到考慮虛樹上的dp計數。我們按照dfs序來dp,定義f[i][j]表示遍歷到的前i個點分成j組的方案數,cnt[i]表示點i到根有多少個詢問點。

那麽對於f[i][j]:
①如果j<=cnt[i],也就說明光是把i的祖先們分開就需要用到j個組了,那麽此時點i必然無組可放,也就是說此時沒有方案行得通,於是f[i][j]=0;
②否則,f[i][j]=f[i-1][j-1]+f[i-1][j]*(j-cnt[i]).這個應該不難理解,我們是按照dfs序來dp的,dp到點i時其祖先一定已經更新完了。那麽對於新來的點i,它要麽新開一組,方案數為f[i-1][j-1],要麽加入到已有的組中,方案數為f[i-1][j]*(j-cnt[i]),(即從已有的組中扣去那些祖先所在的組)。
考慮到空間有點吃緊,可以滾動dp,當然精打細算一點,開二維數組來DP的空間也正好夠。

代碼

#include<bits/stdc++.h>
#define dd(x) cout<<#x<<" = "<<x<<" "
#define de(x) cout<<#x<<" = "<<x<<endl
#define sz(x) int(x.size())
#define fi first
#define se second
#define mp make_pair
#define pb push_back
#define All(x) x.begin(),x.end()
using namespace std;
typedef long long ll;
typedef pair<int,int> P;
typedef priority_queue<int> BQ;
typedef priority_queue<int,vector<int>,greater<int> > SQ;
const int maxn=1e5+10,INF=0x3f3f3f3f,mod=1e9+7;
vector<int> G[maxn],g[maxn];
int fa[maxn][32],dep[maxn],dfn[maxn],id;
void dfs(int u,int f)
{
    dep[u]=dep[f]+1;
    dfn[u]=++id;
    fa[u][0]=f;
    for (int i=1;i<=25;++i)
        fa[u][i]=fa[fa[u][i-1]][i-1];
    for (auto& v:G[u])
    {
        if (v==f)
            continue;
        dfs(v,u);
    }
}
int LCA(int x,int y)
{
    if (dep[x]<dep[y])
        swap(x,y);
    int d=dep[x]-dep[y];
    for (int i=0;i<=25;++i)
        if ((d>>i)&1)
            x=fa[x][i];
    if (x==y)
        return x;
    for (int i=25;i>=0;--i)
        if (fa[x][i]!=fa[y][i])
            x=fa[x][i],y=fa[y][i];
    return fa[x][0];
}
int s[maxn],top;
int a[maxn];
bool isqry[maxn];
void build(int n)
{
    top=0;
    for (int i=0;i<n;++i)
    {
        if (i-1>=0&&a[i]==a[i-1])
            continue;
        int u=a[i];
        if (!top)
        {
            s[++top]=u;
            continue;
        }
        int lca=LCA(u,s[top]);
        while (top>1&&dfn[lca]<=dfn[s[top-1]])
            g[s[top-1]].pb(s[top]),g[s[top]].pb(s[top-1]),--top;
        if (lca!=s[top])
            g[lca].pb(s[top]),g[s[top]].pb(lca),s[top]=lca;
        s[++top]=u;
    }
    while (top>1)
        g[s[top-1]].pb(s[top]),g[s[top]].pb(s[top-1]),--top;
}
ll f[500];
void cal(int u,int ff,int cnt,int m)
{
    if (isqry[u])
    {
        for (int i=m;i>=0;--i)
        {
            if (i<=cnt)
                f[i]=0;
            else
                f[i]=(f[i-1]+f[i]*(i-cnt)%mod)%mod;
        }
    }
    for (auto& v:g[u])
    {
        if (v==ff)
            continue;
        cal(v,u,cnt+isqry[u],m);
    }
    g[u].clear();
    isqry[u]=0;
}
int main()
{
    int n,q;
    cin>>n>>q;
    for (int i=1;i<n;++i)
    {
        int u,v;
        scanf("%d%d",&u,&v);
        G[u].pb(v);
        G[v].pb(u);
    }
    dfs(1,0);
    while (q--)
    {
        int k,m,rt;
        scanf("%d%d%d",&k,&m,&rt);
        a[0]=rt;
        for (int i=1;i<=k;++i)
        {
            scanf("%d",&a[i]);
            isqry[a[i]]=1;
        }
        sort(a,a+k+1,[&](int x,int y){return dfn[x]<dfn[y];});
        build(k+1);
        memset(f,0,sizeof(f));
        f[0]=1;
        cal(rt,0,0,m);
        ll ans=0;
        for (int i=1;i<=m;++i)
            ans=(ans+f[i])%mod;
        printf("%lld\n",ans);
    }
    return 0;
}

Codeforces 1111 E. Tree(虛樹,DP)