码迷,mamicode.com
首页 > 其他好文 > 详细

模拟赛(一)T117903 防疫工作安排

时间:2020-01-31 20:54:29      阅读:65      评论:0      收藏:0      [点我收藏+]

标签:就会   相同   inline   进制   代码   tin   typedef   二维   允许   

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\n", dfs(1, 0, 255));//这就是当前节点为1,没有父节点(根),在每一条根到叶的路径中所有质因数都至少一次没出现的方案数
    return 0;
}

模拟赛(一)T117903 防疫工作安排

标签:就会   相同   inline   进制   代码   tin   typedef   二维   允许   

原文地址:https://www.cnblogs.com/Wild-Donkey/p/12246445.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!