P2396 yyy loves Maths VII 解题报告

题目链接

双倍经验

题意

yyy 有 \(n\) 张卡片,每张卡片上都有一个数字 \(a_i\),每次 yyy 可以选择向前走 \(a_i\) 步并丢弃第 \(i\) 张卡片。当他手上没有卡片的时候他就赢了;但是有 \(m\) 个“厄运数字”,在“厄运数字”的位置有陷阱,只要 yyy 停在这个格子上他就输了(即使终点是陷阱,那么也输了)。你需要算出 yyy 能赢的方案数,答案对 \(1000000007\) 取模。

数据范围:\(1\le n\le 24\)\(0\le m\le 2\)

思路

首先考虑使用dp。

因为没有给出 \(a_i\) 的具体范围,所以不把距离设计进状态。

又发现每张牌只能用一次并且没有顺序限制,观察到 \(n\) 的范围也很小,我们可以考虑状压dp。

定义 \(dp[i]\) 表示使用了集合 \(i\) 内的卡片有多少种赢的方案。转移时,我们记 \(dis[i]\) 表示使用了集合 \(i\) 内的卡片到达的位置,显然当 \(dis[i]\) 为“厄运数字”时不能转移。否则我们枚举这次使用的卡片 \(j\),那么有转移方程:\(dp[i]=\sum_{j=1}^ndp[i | j]\)

其中 \(i | j\) 是从集合 \(i\) 中删去元素 \(j\)

时间复杂度 \(O(2^n\log n)\)

代码

#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int QWQ = (1 << 24);
int n, m, b[3], a[25], dp[QWQ], f[QWQ];
int lowbit(int x)
{
    return x & -x;
}
bool check(int x)
{
    for(int i = 1; i <= m; i ++)
    {
        if(x == b[i]) return 0;
    }
    return 1;
}
signed main()
{
    cin >> n;
    for(int i = 1; i <= n; i ++)
    {
        cin >> a[i];
        f[1 << (i - 1)] = a[i];
    }
    cin >> m;
    for(int i = 1; i <= m; i ++)
    {
        cin >> b[i];
    }
    dp[0] = 1;
    for(int i = 1; i <= (1 << n) - 1; i ++)
    {
        int qwq = i & -i;
        f[i] = f[i ^ qwq] + f[qwq];
        if(f[i] == b[1] || f[i] == b[2])
        {
            continue;
        }
        for(int j = i, k; j > 0; j ^= k)
        {
            k = j & -j;
            dp[i] += dp[i ^ k];
            dp[i] %= mod;
        }
    }
    cout << dp[(1 << n) - 1] % mod << endl;
}
posted @ 2026-06-05 12:57  Luowj  阅读(11)  评论(0)    收藏  举报