题目描述
爆发肺炎疫情的H省共有 n 个地级市,为了最大限度减缓疾病蔓延,这些地级市用 1 到 n 开始编号,由 n−1 条道路相连,并且保证联通。统筹疫情防控的省会W市为根节点,编号为 1 .
为了防疫,首先需要给每个地级市安排一个重要度。具体来说,对于每个市,它的重要度是 [1,m] 中的一个整数(可以重复)。同时有一个特殊的要求:对于从根节点到叶子节点的每一条路径,路径上所有数字的最大公约数必须为 1 .
现在你需要求出合法的重要度安排方法的数量。答案对 10^9+7 取模。
输入格式
第一行两个整数 n,m ,意义如题所示。
接下来 n−1 行,每行两个整数 u,v ,表示一条道路。
输出格式
一行一个整数,表示答案
【数据范围与约定】
……
对于 100% 的数据,满足 1≤n≤10^5,1≤m≤20
观察数据范围,发现m很小,而20以下的质数有8个
dp[i][j]表示i点的子树中,二进制j代表的质数满足下列条件
满足条件的意思是,这个点向下到叶子的每一条路径都至少有一个点不含这个质数
也就是上面的位置可以填含有这个质数的数
大量冗余状态,使用记忆化搜索,需要求的是dp[1][255],每次枚举当前填写的数字,得到对孩子的j的要求,然后将各个孩子方案数相乘。
具体实现以代码注释的方式呈现
Talk is cheap,shou me the code
1 #include<bits/stdc++.h> 2 #define ll long long 3 #define mod 1000000007 4 using namespace std; 5 int n,m,f[25],p[8]={2,3,5,7,11,13,17,19}; 6 ll dp[257][100005]; 7 vector<int> g[100005]; 8 ll dfs(int now,int fa,int mask){ 9 if(dp[mask][now]!=-1)//可能重复访问的节点,进行记忆化搜索 10 return dp[mask][now]; 11 ll res=0; 12 for(int i=1;i<=m;i++){ 13 if(g[now].size()==1&&now!=1) 14 res += !(mask&f[i]);//叶节点填i是否为合法方案,如果是方案数加一,否则加零(非运算特性) 15 else{ 16 ll t=1; 17 for(int j=0;j<g[now].size();j++){ 18 int to=g[now][j]; 19 if(to==fa)continue; 20 t=t*dfs(to,now,mask&f[i])%mod;//枚举子节点并待回溯后计算答案,更新孩子所需满足的条件 21 //1: 22 } 23 res=(res+t)%mod;//某节点上i被选择的情况下对答案的贡献 24 } 25 } 26 return dp[mask][now]=res;//更新dp值并回溯 27 } 28 int main(){ 29 cin>>n>>m; 30 for(int i=1;i<=n-1;i++){ 31 int u,v; 32 cin>>u>>v; 33 g[u].push_back(v); 34 g[v].push_back(u); 35 } 36 for(int i=1;i<=20;i++) 37 for(int j=0;j<8;j++) 38 if(!(i%p[j])) 39 f[i] |= 1<<j;//预处理i有哪些质因子 ,便于转移 40 memset(dp,-1,sizeof(dp)); 41 cout<<dfs(1,0,255); 42 return 0; 43 }