Loading

牛客题单_动态规划课程状压dp习题

牛客题单_动态规划课程状压dp习题

NC14732 锁

大意:

有n名居民, 他们每人有一个重要度。房间的门上可以装若干把锁。假设共有k把锁,命名为1到k。每把锁有一种对应的钥匙,也用1到k表示。钥匙可以复制并发给任意多个居民。每个居民持有若干钥匙,也就是1到k的一个子集。规定一组居民都在场时能打开房门当且仅当他们的重要度加起来至少为m。问至少需要给房间装多少把锁。即,求最小的k,使得可以适当地给居民们每人若干钥匙(即一个1到k的子集),使得任意重要度之和小于m的居民集合持有的钥匙的并集不是1到k,而任意重要度之和大于等于m的居民集合持有的钥匙的并集是1到k。

思路:

当某个集合的重要度的总和没有到达m,但是再加上任意一个人,他们的重要度就能满足要求。

我们假设这个集合缺少一把钥匙,我们求出所有的这样的集合,使得这些集合都缺少一把不同的钥匙,此时所有满足这样条件的集合数量即为所需要的锁的数量了。

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
LL n, m, dp[1 << 25], a[N];
int main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i];
    LL num = (1 << n) - 1;
    LL res = 0;
    for (LL i = 0; i <= num; i++) {
        int flag = 1;
        for (int j = 0; j < n; j++) {
            if(!(i&(1<<j))){
                dp[i + (1 << j)] = dp[i] + a[j];
                if (dp[i + (1 << j)] < m) flag = 0;
            }
        }
        if(flag&&(dp[i]<m)){
            res++;
        }
    }
    cout << res << endl;
    return 0;
}

NC15034 德玛西亚万岁

模板题,和acwing327玉米田一样

#include <bits/stdc++.h>

using namespace std;

const int N = 20 + 5;
typedef long long LL;
int n, m;
int mp[N];
LL dp[N][1 << 13];
const LL mod = 100000000;
bool check(int state) {
    if ((state >> 1) & state)
        return false;
    else
        return true;
}

int main() {
    while (cin >> n >> m) {
        memset(dp, 0, sizeof dp);
        memset(mp, 0, sizeof mp);
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < m; j++) {
                int t;
                cin >> t;
                mp[i] += (!t << j);
            }
        }
        vector<int> st;
        st.clear();
        for (int i = 0; i < (1 << m); i++)
            if (check(i)) {
                st.push_back(i);
            }

        vector<int> state[1 << 13];
        for (int i = 0; i < st.size(); ++i) {
            state[i].clear();
            for (int j = 0; j < st.size(); ++j) {
                int a = st[i], b = st[j];
                if ((a & b) == 0) state[i].push_back(j);
            }
        }
        dp[0][0] = 1;
        for (int i = 1; i <= n + 1; i++)         // 第i行
            for (int j = 0; j < st.size(); j++)  // 第j种合法方案
                if (!(st[j] & mp[i]))
                    for (int k : state[j])  // 第i-1行选择合法方案k
                        dp[i][j] = (dp[i][j] + dp[i - 1][k]) % mod;

        cout << dp[n + 1][0] << endl;  // 第n+1行选择合法方案0的情况(即000000)
    }
    return 0;
}

NC16418 宝藏

大意:

给定一个N个点M条边的无向图,要求在图中找出一颗生成树,满足树上的节点到根节点的深度d与该点连到树中的边的权值w之积的和最小。

思路:

方法1:

枚举根节点,然后求以这个点为根的最小值,利用dfs更新方案的最小值即可

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6 + 5;
typedef long long LL;
int n, m;
LL mp[15][15], dp[1 << 13], dis[15];

void dfs(int state) {
    for (int i = 0; i < n; i++) {
        if (!(state & (1 << i))) continue;
        for (int j = 0; j < n; j++) {
            if (state & (1 << j)) continue;
            if (mp[i][j] == 0x3f3f3f3f3f3f3f3f) continue;
            if(dp[state+(1<<j)]>dp[state]+dis[i]*mp[i][j]){
                int pre = dis[j];
                dis[j] = dis[i] + 1;
                dp[state + (1 << j)] = dp[state] + dis[i] * mp[i][j];
                dfs(state + (1 << j));
                dis[j] = pre;
            }

        }
    }
}

int main() {
    cin >> n >> m;
    memset(mp, 0x3f, sizeof mp);
    memset(dp, 0x3f, sizeof dp);
    while (m--) {
        int x, y;
        LL w;
        cin >> x >> y >> w;
        x--, y--;
        mp[x][y] = mp[y][x] = min(mp[x][y], w);
    }
    LL res = 1e18;
    for (int i = 0; i < n; i++) {
        memset(dp, 0x3f, sizeof dp);
        memset(dis, 0, sizeof dis);
        dis[i] = 1;
        dp[1 << i] = 0;
        dfs(1 << i);
        res = min(res, dp[(1 << n) - 1]);
    }
    cout << res << endl;
}

方法2:

dp[i] [j]表示:当前选择点的状态为i,且树的高度等于j的生成树 ,枚举i的所有子集S作为前j - 1层的点,剩余点作为第j层的点,求出第j层的所有点到S的最短边,将这些边权和乘以j,直接加到dp [S] [j - 1]上,即可求出f [i] [j]。

#include <algorithm>
#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 12, M = 1 << 12, INF = 0x3f3f3f3f;

int n, m;
int d[N][N];
int f[M][N], g[M];

int main() {
    scanf("%d%d", &n, &m);

    memset(d, 0x3f, sizeof d);
    for (int i = 0; i < n; i++) d[i][i] = 0;

    while (m--) {
        int a, b, c;
        scanf("%d%d%d", &a, &b, &c);
        a--, b--;
        d[a][b] = d[b][a] = min(d[a][b], c);
    }

    for (int i = 1; i < 1 << n; i++)
        for (int j = 0; j < n; j++)
            if (i >> j & 1) {
                for (int k = 0; k < n; k++)
                    if (d[j][k] != INF) g[i] |= 1 << k;
            }

    memset(f, 0x3f, sizeof f);
    for (int i = 0; i < n; i++) f[1 << i][0] = 0;

    for (int i = 1; i < 1 << n; i++)
        for (int j = (i - 1); j; j = (j - 1) & i)//枚举子集
            if ((g[j] & i) == i) {
                int remain = i ^ j;
                int cost = 0;
                for (int k = 0; k < n; k++)
                    if (remain >> k & 1) {
                        int t = INF;
                        for (int u = 0; u < n; u++)
                            if (j >> u & 1) t = min(t, d[k][u]);
                        cost += t;
                    }

                for (int k = 1; k < n; k++)
                    f[i][k] = min(f[i][k], f[j][k - 1] + cost * k);
            }

    int res = INF;
    for (int i = 0; i < n; i++) res = min(res, f[(1 << n) - 1][i]);

    printf("%d\n", res);
    return 0;
}

NC17061 多彩的树

大意:

有一棵树包含 N 个节点,节点编号从 1 到 N。节点总共有 K 种颜色,颜色编号从 1 到 K。第 i 个节点的颜色为 Ai。
Fi 表示恰好包含 i 种颜色的路径数量。请计算:

\((∑_{i=1}^{K}(Fi×131^i))mod(10^9+7)\)

思路:

因为颜色只有10种,那么直接状压枚举选择的颜色,然后去dfs找只包含这些颜色的联通块,对于每个联通块,求出这个颜色状态下全部的连通块节点数,把路径数全部累加,并且加上路径数为0的全部只选取一个节点的涂法,即可更新出每个颜色的答案。

最后计算答案的时候,要减去子集的答案,因为被重复记录了

#include <bits/stdc++.h>

#define int long long
using namespace std;

int const N = 2e5 + 10;
int n, m, T;
int a[N], dp[N], vis[N], cnt;
const int mod = 1e9 + 7;
vector<int> mp[N];

int qmi(int a, int k, int p) {
    int res = 1 % p;  // res记录答案, 模上p是为了防止k为0,p为1的特殊情况
    while (k) {  // 只要还有剩下位数
        if (k & 1)
            res = res * a % p;  // 判断最后一位是否为1,如果为1就乘上a,模上p,
                                // 乘法时可能爆int,所以变成long long
        k >>= 1;        // 右移一位
        a = a * a % p;  // 当前a等于上一次的a平方,取模,平方时可能爆int,所以变成long
                        // long
    }
    return res;
}

void dfs(int now, int fa, int state) {
    if (!(state & (1 << a[now]))) return;
    if (vis[now]) return;
    cnt++;
    vis[now] = 1;
    for (int i = 0; i < mp[now].size(); i++) {
        int ne = mp[now][i];
        if (ne == fa) continue;
        dfs(ne, now, state);
    }
}

signed main() {
    cin >> n >> m;
    for (int i = 0; i < n; i++) cin >> a[i],a[i]--;
    for (int i = 0; i < n - 1; i++) {
        int x, y;
        cin >> x >> y;
        x--, y--;
        mp[x].push_back(y), mp[y].push_back(x);
    }
    int num = (1 << m) - 1;
    for (int i = 0; i <= num; i++) {
        memset(vis, 0, sizeof vis);
        for (int j = 0; j < n; j++) {
            cnt = 0;
            if (vis[j]) continue;
            dfs(j, -1, i);
            dp[i] = (dp[i] + cnt+(cnt * (cnt - 1) / 2) % mod) % mod;
        }
    }
    int res = 0;
    for (int i = 0; i <= num; i++) {
        for (int j = (i - 1)&i; j; j = (j - 1) & i) {
            dp[i] = (dp[i] - dp[j]) % mod;
        }
        res = (res + (dp[i] * qmi(131, __builtin_popcount(i), mod)) % mod) % mod;
    }
    cout << (res+mod)%mod;
    return 0;
}

NC17890 方格填色

大意:

给一个m x n的方格,Applese想要给方格填上颜色,每个格子可以是黑色或者白色。他要求左右相邻两格不能同为白色且相邻两列不能全为黑色。

求满足条件的方案数。

思路:

矩阵快速幂,贴个AC代码吧,先咕咕咕了

#include <bits/stdc++.h>
#define p 1000000007
using namespace std;

struct statement
{
	long long f[32][32];
	statement(){memset(f, 0, sizeof(f));}
};

long long maxn;

statement operator*(const statement &a, const statement &b)
{
	statement ans;
	for (int lmid = 0; lmid <= maxn; lmid++)
		for (int rmid = 0; rmid <= maxn; rmid++)
		{
			if ((lmid != 0 || rmid != 0) && ((lmid & rmid) == 0))
			{
				for (int l = 0; l <= maxn; l++)
					for (int r = 0; r <= maxn; r++)
					{
						ans.f[l][r] += 1LL * a.f[l][lmid] * b.f[rmid][r] % p;
						ans.f[l][r] %= p;
					}
			}
		}
	return ans;
}

statement fast_pow(statement x, long long y)
{
	statement ans;
	for (int i = 0; i <= maxn; i++)
		ans.f[i][i] = 1;
	while (y > 0)
	{
		if (y & 1)
			ans = ans * x;
		x = x * x;
		y >>= 1;
	}
	return ans;
}

int main()
{
	long long n, m;
	cin >> m >> n;
	maxn = (1LL << m) - 1;
	statement ans;
	for (int i = 0; i <= maxn; i++)
		ans.f[i][i] = 1;
	ans = fast_pow(ans, n - 1);
	long long res = 0;
	for (int l = 0; l <= maxn; l++)
		for (int r = 0; r <= maxn; r++)
			(res += ans.f[l][r]) %= p;
	cout << res << endl;
	return 0;
}

NC20485 [ZJOI2009]多米诺骨牌

轮廓线DP,先咕咕咕了...

posted @ 2021-03-05 17:15  dyhaohaoxuexi  阅读(104)  评论(0编辑  收藏  举报