Keep Connect(状压dp)

Keep Connect

[Link](F - Keep Connect (atcoder.jp))

题意

给你一个 n n n p p p,表示有一个长 n − 1 n-1 n1 1 1 1的梯子,请你依次回答删除 1 ∼ n − 1 1\sim n-1 1n1条边后依旧联通的方案数。

思路

  • 状压 d p dp dp

​ 我们按照从前往后枚举每一列来统计方案,考虑删到了第 i i i列,什么样的情况可以继续往后统计呢?1.前 i i i列是联通的 。2.前 i i i列一部分和第 i i i列上面的点连通,另一部分和第 i i i列下面的点连通。只有这样后面才是成立的,否则从第 i i i列开始就将这个梯子分开了,已经不合法了。第 i i i列的删除法会形成第 i + 1 i+1 i+1列的情况,很明显的转移关系。

​ 考虑 f [ i ] [ j ] [ 0 / 1 ] : 前 i 列 删 了 j 个 边 且 是 否 联 通 的 方 案 数 f[i][j][0/1]:前i列删了j个边且是否联通的方案数 f[i][j][0/1]:ij,注意不连通是我们上面所探讨的那种对后续状态可以转移有贡献的不连通的情况。

i i i列向第 i + 1 i+1 i+1列转移是一个 ] 三条边的选取,设 p 1 , p 2 , p 3 p1,p2,p3 p1,p2,p3分别为上下和右边的边, 0 0 0为不选, 1 1 1为选

​ 转移分为:

  • f [ i ] [ j ] [ 0 ] f[i][j][0] f[i][j][0] 由于本身不连通,因此 p 1 , p 2 p1,p2 p1,p2必须选,否则就会造成不合法的情况,因此 i + 1 i+1 i+1的联通性取决于 p 3 p3 p3选不选

    f [ i + 1 ] [ j + 1 − p 3 ] [ p 3 ] = f [ i + 1 ] [ j + 1 − p 3 ] [ p 3 ] + f [ i ] [ j ] [ 0 ] f[i+1][j+1-p3][p3]=f[i+1][j+1-p3][p3]+f[i][j][0] f[i+1][j+1p3][p3]=f[i+1][j+1p3][p3]+f[i][j][0]

  • f [ i ] [ j ] [ 1 ] f[i][j][1] f[i][j][1] 由于本身连通,因此 p 1 , p 2 p1,p2 p1,p2只需要有一个选即可,否则就会造成不合法情况(即断层),因此 p 1 , p 2 , p 3 p1,p2,p3 p1,p2,p3只要选了 ≥ 2 \ge 2 2 条就一定连通,否则不连通

    f [ i + 1 ] [ j + 3 − p 1 − p 2 − p 3 ] [ p 1 + p 2 + p 3 > = 2 ] = f [ i + 1 ] [ j + 3 − p 1 − p 2 − p 3 ] [ p 1 + p 2 + p 3 > = 2 ] + f [ i ] [ j ] [ 1 ] f[i+1][j+3-p1-p2-p3][p1+p2+p3>=2]=f[i+1][j+3-p1-p2-p3][p1+p2+p3>=2]+f[i][j][1] f[i+1][j+3p1p2p3][p1+p2+p3>=2]=f[i+1][j+3p1p2p3][p1+p2+p3>=2]+f[i][j][1]

设一下边界,即一个的情况,然后每个状态往后转移即可。

Code

#include <bits/stdc++.h>
#define x first
#define y second
#define debug(x) cout<<#x<<":"<<x<<endl;
using namespace std;
typedef long double ld;
typedef long long LL;
typedef pair<int, int> PII;
typedef pair<double, double> PDD;
typedef unsigned long long ULL;
const int N = 1e5 + 10, M = 2 * N, INF = 0x3f3f3f3f, mod = 1e9 + 7;
const double eps = 1e-8, pi = acos(-1), inf = 1e20;
int n, m, k, p;
int a[N];
int f[3010][3010][2];
int main() {
    ios::sync_with_stdio(false), cin.tie(0);
    cin >> n >> p;
    f[1][1][0] = f[1][0][1] = 1;

    for (int i = 1; i < n; i ++) 
        for (int j = 0; j < n; j ++)
            for (int k = 0; k < 2; k ++)
                if (f[i][j][k]) 
                    for (int p1 = 0; p1 < 2; p1 ++)
                        for (int p2 = 0; p2 < 2; p2 ++)
                            for (int p3 = 0; p3 < 2; p3 ++) {
                                if (!k && (!p1 || !p2)) continue;
                                if (k && !p1 && !p2) continue ;
                                if (!k) f[i + 1][j + 1 - p3][p3] = ((LL)f[i + 1][j + 1 - p3][p3] + f[i][j][k]) % p;
                                if (k) f[i + 1][j + 3 - p1 - p2 - p3][(p1 + p2 + p3) >= 2] = ((LL)f[i + 1][j + 3 - p1 - p2 - p3][(p1 + p2 + p3) >= 2] + f[i][j][k]) % p;
                            } 

    for (int i = 1; i < n; i ++)
        cout << f[n][i][1] << ' ';
    cout << '\n';
    return 0;
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值