zoukankan      html  css  js  c++  java
  • [计数DP] [状压DP] [JXOI2012]奇怪的道路

    写在前面: 这题我虽然把勉强能看的解释写出来了, 但是我自身感觉理解还是不够透彻, 解释也可能有疏漏和错误, 还麻烦发现的大佬提出一下QWQ


    题目描述

    小宇从历史书上了解到一个古老的文明。这个文明在各个方面高度发达,交通方面也不例外。考古学家已经知道,这个文明在全盛时期有 (n) 座城市,编号为 (1,2,cdots,n)(m) 条道路连接在这些城市之间,每条道路将两个城市连接起来,使得两地的居民可以方便地来往。一对城市之间可能存在多条道路。

    据史料记载,这个文明的交通网络满足两个奇怪的特征。

    1. 这个文明崇拜数字 (k),对于任何一条道路,设它连接的两个城市分别为 (u)(v),则必定满足 (1 le leftvert {u-v} ightvert le k)
    2. 任何一个城市都与恰好偶数条道路相连( (0) 也被认为是偶数)。

    不过,由于时间过于久远,具体的交通网络我们已经无法得知了。小宇很好奇这 (n) 个城市之间究竟有多少种可能的连接方法,于是她向你求助。

    两种可能的连接方法不同当且仅当存在一对城市,它们间的道路数在两种方法中不同。

    在交通网络中,有可能存在两个城市无法互相到达。

    方法数可能很大,你只需要输出方法数模 (10^9 + 7) 后的结果。

    输入格式

    输入共一行,有三个整数 (n,m,k)

    输出格式

    输出一个整数,表示方案数模 (10^9 + 7) 后的结果。


    这是一个计数(DP)问题.

    首先, 为了方便进行(DP), 我们设所有的边都是从下标较大的点指向下标较小的点的

    设当前点的下标为 (i) , 然后因为 (k) 为下标之差, 我们建边时只能从 (i)(left[i-k,i-1 ight]) 这个区间中建边

    考虑到题中要求每个点必须连偶数条边, 那么, 对于该条件, 每个点只有连偶数条边和奇数条边两种状态, 且 (k) 范围较小, 我们可以采用状态压缩的思想, 将状态表示如下:

    [fleft(i, j, S ight), 表示当前为第 i 个点, 已经连接的边数为 j, 且 left[i-k, i ight] 中每个点的连边奇偶状态为 S, 0 表示偶, 1 表示奇. ]

    (eg.) (i-1) 连接了 (3) 条边, (S)(1) 位 为 (1).

    也就是我们每次考虑连边时, 只考虑 (left[i-k, i ight]) 中的点互相连边, 可以写出转移方程为:

    [fleft(i, j, S ight) = sum fleft(i-1, j, S' ight) + sum fleft(i, j-1, S'' ight) ]

    在这个方程中有一个缺陷: 我们并不能确定这 j 条边加入的顺序, 所以我们最后求出来的并不是一个组合.

    为了去重( 即保证连边顺序 ), 我们考虑添加一位状态 (t), 表示 (t) 状态下点 (i) 只能连接 (left[i-k, i-t-1 ight]) 的点.

    也就是当前状态只能由第 (i-k) 个状态到第 (i-t-1) 个状态转移而来, 这样我们就可以固定边的连接顺序了.

    现在, 我们再次考虑转移:

    • 为了保证统计的正确, 我们需要倒序循环

    • (i)(i-t-1) 不连边, 状态转移到考虑 (i-t-1) 的前一位, 即:

      [fleft(i, j, S, t-1 ight) += fleft(i, j, S, t ight) ]

    • (i)(i-t-1) 连边, 则:

      [fleft(i, j+1, S', t ight) += fleft(i, j, S, t ight), \ 其中 quad S' = S oplus 2^0 oplus 2^{t-1} ]

      • 其实 (S') 的式子就是把 (i)(i-t-1) 两个位置的奇偶状态翻转.
    • 循环边界: 若 (t=0) , 则:

      [fleft(i+1, j, S'', min(i, k) ight) = fleft(i, j, S, t ight),\ 其中 quad S'' = Sll 1 ]

      • 注意: 在此种转移中, 我们要保证超出当前区间的那一位被连了偶数个边, 因为之后的状态都不会考虑这个点了, 也就是说, 我们要满足:

        [S & left(1ll k ight) = 0 ]

    这样我们就保证了刷表时更新答案的顺序, 从而达到了去重的效果.

    时间复杂度: (Oleft(nmk cdot 2^k ight))


    代码:

    # include <iostream>
    # include <cstdio>
    # define MAXN 32
    
    using namespace std;
    
    int f[MAXN][MAXN][1<<9][MAXN];
    const int MOD = 1e9+7;
    
    int main(){
        int n, m, k;
        cin>>n>>m>>k;
    
        f[1][0][0][0] = 1;
        int tmp = 1<<k;
        for(int i = 1; i <= n; i++){
            for(int j = 0; j  <= m; j++){
                for(int S = 0; S < (1<<k+1); S++){
                    for(int t = min(i-1, k); t; t-- ){
                        (f[i][j][S][t-1] += f[i][j][S][t]) %= MOD;
                        if(i > t){
                            (f[i][j+1][S^1^(1<<t)][t] += f[i][j][S][t]) %= MOD;
                        }
                    }
                    if(!(S&tmp)){
                        (f[i+1][j][S<<1][min(i, k)] += f[i][j][S][0]) %= MOD; 
                    }
                }
            }
        }
    
        cout<<f[n][m][0][0];
    
        return 0;
    }
    
  • 相关阅读:
    简单测试
    纸玫瑰
    Java 字符串编码 (保存成txt测试)
    创建 Filter
    jee中文名图片+tomcat ==> 中文乱码的另类处理(未成功)
    dom4j_01_02
    dom4j_01_01
    Java 字符串编码
    websocket

  • 原文地址:https://www.cnblogs.com/Foggy-Forest/p/13335648.html
Copyright © 2011-2022 走看看