模拟赛T117903 防疫工作安排
Posted wild-donkey
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了模拟赛T117903 防疫工作安排相关的知识,希望对你有一定的参考价值。
2020.1.30
模拟赛(一)
肝一下午和一晚上,绝对是到目前为止博客里最难的一道题!
T2 防疫工作安排
题目描述
爆发肺炎疫情的H省共有n个地级市,为了最大限度减缓疾病蔓延,这些地级市用1到n开始编号,由n?1条道路相连,并且保证联通。统筹疫情防控的省会W市为根节点,编号为1
为了防疫,首先需要给每个地级市安排一个重要度。具体来说,对于每个市,它的重要度是1,m中的一个整数(可以重复)。同时有一个特殊的要求:对于从根节点到叶子节点的每一条路径,路径上所有数字的最大公约数必须为1
现在你需要求出合法的重要度安排方法的数量。答案对 1e9+7取模。
输入格式
第一行两个整数n,m,意义如题所示。
接下来n?1行,每行两个整数u,v,表示一条道路。
输出格式
一行一个整数,表示答案。
输入输出样例
输入
3 2
1 2
1 3
输出
5
说明/提示
【样例解释】
1、2、3 号结点分别可以是 [1,2,2],[1,1,2],[1,2,1],[1,1,1],[2,1,1],一共 5 种方案。
【数据范围与约定】
对于40%的数据,满足1≤n≤100
对于额外20%的数据,满足1≤m≤5
对于额外20%的数据,满足树成为一条链。
对于100%的数据,满足1≤n≤10^5^,1≤m≤20
题解
因为n个点由n-1条路连通,所以不难发现这是一棵树。根节点是1(省会)
我们要保证从根到叶的每一条路径上至少有两数互质。
因为m很小,只有20,那么就可以从分析m入手:
两数互质,也就是两数没有相同质因数,20以下的数可能有的质因数有8个:2,3,5,7,11,13,17,19
而且20以下的数至多有2个质因数(2·3·5=30>20)
那么只要判断这八个质因数的有无,就能判断两数是否互质了
为了表示质因数的有无,就要用2进制表示:00001001(9)就表示2,7两个质数
开一个数组f存每个数的质因数,如f~12~=00000110
设dp~i,j~表示(以j为根的子树的所有根叶路径)满足(i表示的质数至少有一次没出现)
那么最终答案就是dp~255,1~(255表示2~19所有8个质数,1表示根节点)
根据乘法原理,在当前节点确定后,其子树的方案数互不影响,所以当前节点的树的方案数为其子树方案数相乘
写出状态转移方程:(方程过于复杂,比着代码才写出来,直接看代码吧)
[
dp[i][j]=(dp[i and f[1]][son1]*dp[i and f[1]][son2]*···*f[i and f[1]][sonlast])+(dp[i and f[2]][son1]*···)+···+(dp[i and f[m]][son1]*···)
]
边界就是叶节点,只要枚举其可行的数字就好,方案不超过20
因为偷懒实力不允许,这次就用老师的标程作注好了:(为了看着舒服,改了改码风(改得面目全非))
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <vector>
#include <cstring>
using namespace std;
typedef long long LL;
vector<int> g[100005];//向量容器,可以用来存树(每个元素(每个向量)存储每个节点的相邻节点(父或子))
int p[8] = { 2, 3, 5, 7, 11, 13, 17, 19 }, f[25], m, u, v, n;//p存储代号表示的质数,f存储20以内的数的质因数情况
LL dp[257][100005];//第一维是质因数情况,第二维是节点,所存的数字是指该节点的子树上所有节点的质因数中,这些质因数满足至少有一次没出现(就是当前节点子树至少一对数互质)
inline LL dfs(int now, int fa, int mask) {//now:当前节点,fa:当前节点的父亲,mask:表示质因数情况,返回值就是(保证mask表示的质数都满足((在now节点为根的子树上所有根叶路径上)都至少有一次没出现))的方案数对1e9+7的取模
if (dp[mask][now] != -1)//有存储(之前推算到过)避免重复计算
return dp[mask][now];//直接返回
LL res = 0;//累计结果
for (int i = 1; i <= m; i++)//枚举当前节点的数字
if (g[now].size() == 1 && now != 1)//只有一个连通节点且不是根节点(叶子),因为只要枚举它本身就好,所以答案就一定小于20(不需要取模),满足条件的充要条件就是((now节点的数字)的质因数中)不含有(mask表示的质数)
res += !(mask & f[i]);//(上一行可知这是个叶节点)与运算后,如果结果不为0,说明(now的数字i的质因数中,有mask表示的质数)也就是说now当前的数字i不满足(以now为根的子树中(所有根叶路径中(mask表示的质数至少有一次没出现)))的条件,i放在这里的now中不合适;反之,结果为0,说明i可以放在now中,这种情况的方案数就会加!(0)也就是1
else {//不是叶节点,那就考虑其子树
LL t = 1;//t来记录now为i时子树的方案数
for (auto to : g[now]) {//auto的用法:作为数组的元素,从该数组第一个到最后一个依次循环(语文水平捉急),这个语句也可以用正常的for循环代替:to相当于g[now][i](传值调用,原数组不动)
if (to == fa)
continue;//枚举和当前节点连通的节点,如果是父亲,那就跳过,因为是从根到叶递归,所以只看儿子
t = t * dfs(to, now, mask & f[i]) % 1000000007;//(根节点确定,子树间互不影响,故遵循乘法原理,把各子树的结果相乘)子树的结果:以子树为根,now为父,mask的情况有些复杂:因为当前的t要求的是以now为根的子树上的根叶路径符合mask的质数(至少一次不出现)条件,并且有f[i]的质数在now出现,所以对子树的要求就是使(f[i]和mask表示的质数)的交集至少有一次不出现,这样,就能保证本层递归结果符合要求,所以用与运算找出交集
}
res = (res + t) % 1000000007;//将(now取各种i的值)的各种方案数累加至res里
}
return dp[mask][now] = res;//返回并赋值
}
int main() {
scanf("%d%d", &n, &m);
for (int i = 0; i < n - 1; i++) {//建树
scanf("%d%d", &u, &v);
g[u].push_back(v);//新加入一条边
g[v].push_back(u);//双向边
}
for (int i = 1; i <= 20; i++)//枚举1-20的数字
for (int j = 0; j < 8; j++)//枚举8个质数
if (!(i % p[j]))//可以整除(含有该质因数)
f[i] |= 1 << j;//含有该质因数,对应的二进制位就是1
memset(dp, -1, sizeof(dp));//打个标记,区分已经遍历到的节点和空白节点
printf("%lld
", dfs(1, 0, 255));//这就是当前节点为1,没有父节点(根),在每一条根到叶的路径中所有质因数都至少一次没出现的方案数
return 0;
}
以上是关于模拟赛T117903 防疫工作安排的主要内容,如果未能解决你的问题,请参考以下文章