zoukankan      html  css  js  c++  java
  • 【算法学习笔记】42.正反DP 填充问题 SJTU OJ 1285 时晴时雨

    Description

    Taring 喜欢晴天,也喜欢雨天。

    Taring说:我想体验连续的K天的晴朗,去远足,去放歌;我还想再这K个晴天之后,再去体验连续的K天的云雨,去感受落雨时的轻语。这是令Taring最开心的事情了。其它的时间,Taring会在机房默默的编写着代码。

    当然,Taring不想在这连续的K个晴天和连续的K个雨天里被机房的事务打扰或者被自然天气的变化中断。也就是说,这K个晴天和K个雨天必须是连续的,但是他们之间并不需要时间连续。显然的,Taring如果在感受了连续的K天晴朗后,前去机房写代码,等待下雨的时候再次开始他的感悟,这样是不会影响Taring的心情的。

    Taring通过天气预报得到了最近连续 N 天中若干天的天气情况(天气情况只有“晴”和“雨”两种),但其他的天数的天气情况均是未知的。

    他想知道有多少种可能的不同的天气情况(对于两种天气情况,如果有任意一天的天气情况是不一样的,就算做不同的天气情况),使他能够完成这两项体验。

    Input Format

    输入共有两行。

    第1行有2个整数,分别表示上文中所述的N和K。

    第2行是一个长度为N的字符串,仅包含有B(清),W(雨),X(未知)三种字符。

    Output Format

    输出一个整数Ans,表示合法的天气方案数量。

    由于Ans可能很大,请将答案Mod 1,000,000,007 (109+7)

    Sample Input 1

    4 2
    XXXX
    

    Sample Output 1

    1
    

    Sample Input 2

    10 2
    XXBXXWXXXX
    

    Sample Output 2

    166
    

     

    原题:http://codeforces.com/contest/204/problem/D

    官方题解:http://codeforces.com/blog/entry/4849

    根据官方题解分析 大致思路如下:

    //b[i] 顺序DP 表示到i位置为止 没有出现k个连续的B的填充方法数

    //c[i] 顺序DP 表示到i位置位置 恰好第一次出现连续k个B的填充方法数

    //w_1[i] 逆序DP 表示从n开始到i位置 没有出现k个连续的W的填充方法数

    //w_2[i] 逆序DP 表示从n开始到i位置 出现了k个连续的W填充方法数

    //total[i] 逆序DP 表示从n位置开始到i位置结束时 一共有多少种填充方法

    第1步 求b[]

      首先 初始化 b[0] = 1; 还有S[0]='0';//这一步是为了以后的判断比较方便

      1.1 判断当前处理位置的类型

      1.1.1 如果遇到不确定位 X ,则让b[i]= 2 * b[i-1] 因为此处可以有两种填充方法, 当然可能会产生一些错误的填充 下面进行处理

      1.1.2 如果遇到的是确定位 则让b[i] = 1 * b[i-1] 因为只有一种填充方法, 当然也会产生一些错误的填充

      1.2 记录当前位置连续的B的个数 continous_b[i]

      1.3 如果 continous_b[i]>=k 和 S[i-k]不是'B'

    //来计算错误的填充数目 bad

    //由于是DP过程,我们假设b[0]~b[i-1]的计算都是正确的, 则错误的填充指的是 从i-k开始 到 i位置 恰好是连续k个B的情况

    //根据样例 则是   xxxxxWBB 的情况, 前面的xxxxx的填充数 就是 b[i-k-1] 后面的WBB是固定的部分 * 1 

        所以 bad = 1 * b[i-k-1]

        (注意特殊情况 如果i=k 也就是恰好前k个字符可以实现k个B的情况, 则让bad为1即可)

      最后让 b[i] = b[i] - bad;

    第2步 求c[]

      2.1 c[0]=0

      2.2 c[i] 其实就是第1步里的bad 不同之处就是如果不满足那个情况 则要另c[i]=0 

      //如果把c[]的计算和b[]的融合在一起 则不需要continous_b数组 只需要一个数即可

    第3步 求w_1[]

      和第1步一样的思想,不同的方向而已

    第4步 求total[]

      逆序 遇到X则累成2

    第5步 求w_2[]

      把total和w_1相减即可 //补集的思想

    第6部 求最终结果ans

      这里要注意: 我们已经得到了c[]和w[], c[]表示在i位置处 第一次出现连续k个B的填充方法数目, w[]表示后j个中 出现连续k个W的方法数目,可见 这要遍历i从1到n

      ans += c[i] * w[i+1]即可,原因在于 c[]已经完成了去重的工作!

    PS: mod时,有一个细节要注意就是 减法 的取模 要 先加一个mod再取模, 原因:余数之间的减法操作可能会导致负数的存在!

    代码如下:

    #include <iostream>
    using namespace std;
    
    const int MaxN = (int) 1e6+5;
    const int Mod = (int) 1e9+7;
    typedef unsigned long long ULL;
    char S[MaxN];
    ULL b[MaxN],c[MaxN],w[MaxN],continous_b[MaxN],total[MaxN];
    //continous_b记录的是到i位置时 恰好出现了连续的k个B的情况数目 不用也行
    
    int main(int argc, char const *argv[])
    {
        int n,k; cin>>n>>k;
        S[0]='0';//为了在计算b的时候 临界情况的判断
        cin>>(S+1);//为了下标对齐,从1开始输入
    
        //求b[] b[i]表示的是 从1开始到i位置 没有出现过连续的k个B的情况的个数
        b[0] = 1;
        
        for (int i = 1; i <= n; ++i)
        {
            
            //如果是X 由于可以填2种 所以乘2 注意:此时的b[i]是有bad filling的 
            //这个bad filling就是指, 前i个位置 有连续k个B
            //假设之前的b 全是正确的 那么这个bad filling 只有一种情况 那就是刚好填了B且满足前k个都是B
            b[i] = ( S[i]=='X' ) ? 2 * b[i-1] % Mod : 1 * b[i-1]; 
            continous_b[0]=0;
            //为了找出是否此时出现了 bad filling 我们要记录到i为止 前面出现的连续的B的个数
            switch(S[i]){
                case 'X':
                case 'B': // 可继
                    continous_b[i] = continous_b[i-1]+1;
                    break;
                case 'W': //断了
                    continous_b[i] = 0;
                    continue;//可以下一次循环了 因为肯定不是bad filling
            }
            //判断是否出现了bad filling 
            if(continous_b[i] >= k and S[i-k]!='B'){ //正好出现了连续的k个B
                //此处 右边的 1 * b[i-k-1] 中的1表示的是WBB的情况:只有一种,b[i-k-1]表示的是前i-k-1中没有连续k个B的情况
                //##### 重点 : 两者相乘就是所谓的bad fillings 
                int bad ;
                if (i > k){
                    bad = 1 * b[i-(k+1)];
                }else if(i==k){
                    bad = 1 * 1;//特殊临界情况
                }
                
                c[i] = i-k-1>=0 ? b[i-k-1] : 1 ;
    
                b[i] = (b[i] - bad + Mod) % Mod; 
            }else
                c[i] = 0;
        }//此时已经计算了 b[]和continous_b[]
    
        //求c[] ci表示的是到第i个位置 恰好出现了连续的k个B的情况数 (目的是为了去重) 也可以放在上一个循环里面做
        // c[0] = 0;
        // for (int i = 1; i <= n; ++i)
        // {
        //     if(continous_b[i]>=k and S[i-k]!='B')
        //         c[i] = i-k-1>=0 ? b[i-k-1] : 1 ;
        //     else
        //         c[i] = 0;
        // }
    
    
        //现在求w[i], w[i]表示的是从最后一个位置开始 到i截止 有k个连续的W的情况数 和第一步类似 最后加上一部补就好了
    
        //现在的w[i]还不是真正的结果
        w[n+2] = 1;//为了方便
        w[n+1] = 1;
        S[n+1] = '0';
        int continous_w = 0;
        for (int i = n; i >=0; --i)
        {
            w[i] = (S[i]=='X') ? 2 * w[i+1] % Mod : w[i+1];
            switch(S[i]){
                case 'X':
                case 'W':
                    continous_w++;
                    break;
                case 'B':
                    continous_w = 0;
                    continue;//不用判断是否有bad fullings了
            }
    
            if(continous_w >= k and S[i+k] != 'W')
                w[i] = (w[i] - w[i+k+1] + Mod) % Mod;
        }
    
        //记录全部情况的数量
        total[n+1]=1;
        for (int i = n; i >=0; --i) {
            total[i] = (S[i]=='X') ? 2 * total[i+1] % Mod : total[i+1];
        }
        
        //变换为补集
        for (int i = 1; i <= n; ++i)
            w[i] = (total[i] - w[i] + Mod) % Mod;    
        
    
        ULL ans = 0;
        for (int i = 1; i <= n-1; ++i){ 
            ans = (ans + c[i] * w[i+1]) % Mod;
        }
        cout<<ans<<endl; 
        
        // for (int i = 1; i <= n; ++i)
        // {
        //     cout<<b[i]<<" ";
        // }cout<<endl;
    
        // for (int i = 1; i <= n; ++i)
        // {
        //     cout<<c[i]<<" ";
        // }cout<<endl;
         // for (int i = 1; i <= n; ++i)
        // {
        //     cout<<w[i]<<" ";
        // }cout<<endl;
        return 0;
    }
    /*
    10 2
    XXBXXWXXXX
            3 2
    */
  • 相关阅读:
    PythonのTkinter基本原理
    使用 Word (VBA) 分割长图到多页
    如何使用 Shebang Line (Python 虚拟环境)
    将常用的 VBScript 脚本放到任务栏 (Pin VBScript to Taskbar)
    关于 VBScript 中的 CreateObject
    Windows Scripting Host (WSH) 是什么?
    Component Object Model (COM) 是什么?
    IOS 打开中文 html 文件,显示乱码的问题
    科技发展时间线(Technology Timeline)
    列置换密码
  • 原文地址:https://www.cnblogs.com/yuchenlin/p/sjtu_oj_1285.html
Copyright © 2011-2022 走看看