好题集 (13) - LG P3349 [ZJOI2016] 小星星

题目传送门

题意:给一棵结构固定的树和无向联通图,要求对树重新标号,使得这棵树是给定的图的子图且新的标号是一个排列,求方案数。\(n\le 17,m\le\frac{n(n-1)}{2}\)

\(f_{i,j,\text{mask}}\)表示树上的 \(i\) 号点重新标号为 \(j\),子树 \(i\) 内的新编号集合为 \(\text{mask}\) 的方案数。DP 时需枚举 \(\text{mask}\) 的子集以合并子树答案,因此第二维产生的复杂度为:

\[\begin{align*} \sum\limits_{\text{mask}\in\{1,2,\cdots,n\}}2^{|\text{mask}|}&=\sum\limits_{L=0}^n{n\choose L}1^{n-L}2^L\\ &=(1+2)^n\\ &=3^n \end{align*} \]

然后还要枚举树上的 \(O(n)\) 条边和图里的 \(O(n^2)\) 个点对,总复杂度 \(O(n^3\cdot 3^n)\),过不了。

前面的 \(O(n^3)\) 有点难搞,所以考虑优化 \(O(3^n)\) 的底数。

尝试去掉状态定义中的 \(\text{mask}\)\(\text{mask}\) 的本质目的是避免重复,去掉后,可以强制令所有树上点都来自于某一集合,并用容斥达到一样的效果。每次枚举 \(\{1,2,\cdots,n\}\) 的子集 \(S\),令树上所有点都是 \(S\) 中的元素而不一定用完 \(S\) 中的所有元素,然后做一遍 \(O(n^3)\) 的 DP。

没有了 \(\text{mask}\)\(f_{i,j}\) 的定义变为树上的 \(i\) 号点重新标号为 \(j\in S\) 的方案数,转移时只需枚举儿子,然后判断连边是否在图中存在,相乘累加即可。也就是说,这样转移:

\[f_{u,x}=\prod_{v\in\text{son}_u}\Big(\sum\limits_{y\in S,(x,y)\in E_{\text{图}}}f_{v,y}\Big) \]

\(g(S)\) 为枚举到的 \(S\) 的方案数,则由二项式反演得到答案:

\[\sum\limits_{S\subseteq\{1,2,\cdots,n\}}^n(-1)^{n-|S|}g(S) \]

\(g(S)\) 可以这样得出:

\[g(S)=\sum_{x\in S} f_{1,x} \]

然后做完了。由于只枚举了树上的点集,总复杂度降到 \(O(n^3\cdot 2^n)\),就可以过了。

#ifndef ONLINE_JUDGE
#pragma GCC optimize("O3,Ofast,unroll-loops")
#endif
//#pragma GCC target("sse,sse2,sse3,ssse3,sse4,popcnt,abm,mmx,avx,avx2,tune=native")
#include<iostream>
#include<vector>
#define int long long
using namespace std;

const int N=1e3+5;

int n,m;int tot,res;
int f[N][N];
bool flg[N][N];
vector<int>vec;

namespace OIfast{
    
    char buf[1<<21],*p1,*p2,*top,buffer[1<<21];
    #define getchar()(p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<21,stdin),p1==p2)?0:*p1++)
    #define gc getchar()
    
    inline int read(){
        static int n;n=0;static char c(gc);
        while(!isdigit(c))c=gc;
        while(isdigit(c))n=(n<<3)+(n<<1)+(c^48),c=gc;
        return n;
    }
    
}using namespace OIfast;

namespace graph{
    
    vector<int>e[N];
    
    inline void dfs(int u,int fa){
        for(auto v:e[u]){
            if(v==fa)continue ;
            dfs(v,u);
        }
        for(auto x:vec){
            f[u][x]=1;
            for(auto v:e[u]){
                if(v==fa)continue ;
                int sum=0;
                for(auto y:vec){
                    if(!flg[x][y])continue ;
                    sum+=f[v][y];
                }
                f[u][x]*=sum;
            }
        }
        return ;
    }
    
}using namespace graph;

signed main(){
    n=read(),m=read();
    for(int i=1;i<=m;++i){
        int u=read(),v=read();
        flg[u][v]=flg[v][u]=1;
    }
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        e[u].push_back(v),e[v].push_back(u);
    }
    for(int mask=0;mask<(1<<n);++mask){
        vec.clear();tot=0;
        for(int i=1;i<=n;++i)if((mask>>(i-1))&1)++tot,vec.push_back(i);
        dfs(1,0);for(auto x:vec)res+=((n-tot)&1?-1:1)*f[1][x];
    }
    return printf("%lld\n",res),0;
}

提交记录

posted @ 2026-06-25 20:58  DX3906_ourstar  阅读(7)  评论(0)    收藏  举报