zoukankan      html  css  js  c++  java
  • LuoGu P4996 咕咕咕

    题目描述
    小 F 是一个能鸽善鹉的同学,他经常把事情拖到最后一天才去做,导致他的某些日子总是非常匆忙。

    比如,时间回溯到了 2018 年 11 月 3 日。小 F 望着自己的任务清单:

    看 iG 夺冠;
    补月赛题的锅。
    小 F 虽然经常咕咕咕,但他完成任务也是很厉害的,他一次性可以完成剩余任务的任一非空子集。比如,他现在可以选择以下几种中的一种:

    看 iG 夺冠;
    补月赛题的锅;
    一边看 iG 夺冠的直播,一边补锅。
    当然,比赛实在是太精彩了,所以小 F 就去看比赛了。

    不过,当金雨从天而降、IG 举起奖杯之时,小 F 突然心生愧疚——锅还没补呢!于是,小 F 的内心产生了一点歉意。

    这时小 F 注意到,自己总是在某些情况下会产生歉意。每当他要检查自己的任务表来决定下一项任务的时候,如果当前他干了某些事情,但是没干另一些事情,那么他就会产生一定量的歉意——比如,无论他今天看没看比赛,只要没有补完月赛的锅,他都会在选择任务的时候产生 11 点歉意。小 F 完成所有任务后,他这一天的歉意值等于他每次选择任务时的歉意之和。

    过高的歉意值让小 F 感到不安。现在,小 F 告诉你他还有 (n) 项任务,并告诉你在 (m) 种情况中的一种 (mathrm{state}_i) 的情况下,小 F 会产生 (a_i)​ 点歉意。请你帮忙计算一下,小 F 在那一天所有可能的完成所有任务方式的歉意值之和是多少.

    由于答案可能很大,你只需要输出答案对 (998244353) 取模即可。

    输入输出格式
    输入格式:
    输入一行两个整数 (n, m) 表示有 (n) 项任务,在 (m) 种情况中下小 F 会产生歉意值。

    输入接下来 (m) 行,每行有一个长度为 (n)(0-1) 串 $ mathrm{state}_i $ 和一个歉意值 (a_i ,mathrm{state}_{i, j})(0/1) 表示第 (j) 项任务此时没做 / 已经做了。

    详情请参考样例和样例解释。

    输出格式:
    输出一行一个整数,表示小 F 在那一天所有可能的完成任务方式的歉意值之和对 (998244353) 取模的结果。

    输入输出样例
    输入样例#1:

    2 2
    00 1
    10 1
    

    输出样例#1:

    4
    

    输入样例#2:

    3 4
    000 16
    001 9
    110 4
    111 1
    

    输出样例#2:

    260
    

    说明
    样例 1 解释:
    $ 0−1 $ 串中第一个数字表示小 F 看没看比赛,第二个数字表示小 F 补没补锅。

    我们用 $ varnothing $ 表示小 F 什么都没干,$ A $ 表示小 F 看了比赛,$ B $ 表示小 F 补了锅,那么所有会产生愧疚的方式如下:

    (varnothing: 1)
    ({A}:1)
    那么所有可能的选择如下:

    (varnothing ightarrow{A} ightarrow{A,B}:2)
    (varnothing ightarrow{B} ightarrow{A,B}:1)
    (varnothing ightarrow{A,B}:1)
    所以答案是 (2 + 1 + 1 = 4)

    数据范围
    保证出现的 (mathrm{state}_i)互不相同。

    对于所有数据,有 $ 1 leq n leq 20, 1 leq m leq min(2 ^ n, 10 ^ 5), 1 leq a_i leq 10 ^ 5$

    本来想我这种蒟蒻是死活做不出来这种题的,结果听了讲评,最后是个组合数学,然后就差不多了
    其实就是让你求从 $ n $ 个 $ 0 $ 到 $ n $ 个 $ 1 $ 的的方案嘛
    然后它规定了某些01串有一定的权值,让你算,在上一个问题的基础上这些01串 出现次数*权值 的和
    然后就考虑组合数学,因为一个串中如果01个数相同,那么这些串的出现次数也一样对吧
    出现 $ i $ 个 $ 1 $ 就是 $ C_{n}^{i} $
    设 $ f_i $ 表示出现 $ i $ 个 $ 1 $ 的方案数,有一点需要注意,出现了 $ i $ 个 $ 1 $ 的方案数就是出现了 $ n - i $ 个 $ 0 $ 的方案数
    这个东西可以这么来递推:
    $ f_i = sum_{j = 1}^{i} f_{ i - j } imes C_{i}^{j}$
    这个就是组合数学的基本意义....解释一下
    你有 $ i $ 个 $ 1 $ 了,那么对于每一个 $ 1 $ 的出现次数小于 $ i $ 的状态都对你有贡献
    这是第二层循环的由来,那么贡献是多少呢?就是在 $ 1 $ 较少的状态的 $ 0 $ 位填 $ 1 $ 使得它变成另一个状态的方案数
    所以贡献是 $ f_{ i - j } imes C_{i}^{j} $
    如果一个状态中出现了 $ k $ 个 $ 1 $ , 那么它的答案贡献就是 $ f_i $ 和 $ f_{ n - i } $ 为什么?
    一个状态的方案可以用全0串到它的方案和它到全1串的方案作乘法得到(乘法原理)
    然后对于每一个有权值的状态统计贡献累加和就行了
    代码如下:

    #include <iostream>
    #include <cstdlib>
    #include <cstdio>
    #define LL long long
    
    const int N = 65 ;
    const LL mod = 998244353 ;
    
    char s[50];
    int n , m , cnt;
    LL x , f[N] , Flag ;
    LL ans , C[N][N];
    
    inline void init(){
    	C[0][0] = 1 ;
    	for (int i = 1 ; i <= n + 1 ; ++ i)
    		for (int j = 0 ; j <= i ; ++ j)
    			C[i][j] = ( C[i - 1][j - 1] + C[i - 1][j] ) % mod ;
    	f[0] = 1 ;
    	for (int i = 1 ; i <= n ; ++ i)
    		for (int j = 1 ; j <= i ; ++ j)
    			f[i] = ( f[i] + f[i - j] * C[i][j] ) % mod ;
    	return ;
    }
    
    int main(){
    	scanf ("%d%d" , & n , & m ) ; init() ;
    	while (m --){
    		cnt = 0 ;
    		scanf ("%s%lld" , s + 1 , & x );
    		for (int i = 1 ; i <= n ; ++ i) if( s[i] == '1' ) ++ cnt ; 
    		ans = ( ans + ( ( x * f[cnt] ) % mod * f[n - cnt] ) % mod ) % mod ;
    	}
    	printf("%lld
    " , ans % mod );
    	return 0;
    }
    
    May you return with a young heart after years of fighting.
  • 相关阅读:
    洛谷 1498 南蛮图腾——模拟
    bzoj 4198 [Noi2015]荷马史诗——哈夫曼树
    bzoj 1026 [SCOI2009]windy数——数位dp水题
    bzoj 1045 [HAOI2008] 糖果传递——设变量推式子
    bzoj 4521 [Cqoi2016]手机号码——数位dp
    bzoj1044 [HAOI2008]木棍分割——前缀和优化DP
    bzoj1090 [SCOI2003]字符串折叠——区间DP
    bzoj1911 [Apio2010]特别行动队——斜率优化DP
    bzoj1025 [SCOI2009]游戏——因数DP
    bzoj1207 [HNOI2004]打鼹鼠——LIS
  • 原文地址:https://www.cnblogs.com/Equinox-Flower/p/9909429.html
Copyright © 2011-2022 走看看