连锁商店 (2021CCPC女生赛)

2022/8/1 23:24:16

本文主要是介绍连锁商店 (2021CCPC女生赛),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Problem - C - Codeforces

题意

有 n ( n <= 36) 个点,每个点有颜色,每个颜色都相同的权值(为正数);有 m 条边,u -> v 且 u < v, 求从 1 号点到 i 号点的路径上,选颜色互不相同的一些点,使权值和最大

状压dp

首先可考虑 TSP 问题类似的状压dp方法,但 \(36*2^{36}\) 太大,考虑优化

可以分析一下dp需要存哪些状态:TSP 中要存已经走过的点集和当前点,因为要满足不走重复点,所以这些状态是必要的;而本题中边都满足 u < v, 不会走重复点,所以不必要存下所有走过的点集,对之后的决策有影响的就是走了哪些颜色

但是存走了哪些颜色,也是 \(2^{36}\) 级别的,但是显然如果一个颜色在所有点中只出现了一次那这个颜色是一定可以选的,因此遇到这个颜色直接选就好,它也不会对之后的选择造成影响,所以只出现一次的颜色是不必存下来的

只存下出现 2 次及以上的颜色,这样复杂度就变成了 \(n^2*2^{\frac n2}\), 就可以状压dp了

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;
#define endl "\n"

typedef long long ll;
typedef pair<int, int> PII;

const int N = 40, M = 18;
int n, m, tot;
int c[N], w[N], ww[N], cnt[N], id[N];
int f[(1 << M) + 10][N];
int ans[N];
vector<vector<int> > G(N);
void add(int u, int v)
{
	G[u].push_back(v);
}

int main()
{
	ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
	{
		cin >> c[i];
		cnt[c[i]]++;
	}
	for (int i = 1; i <= n; i++)
	{
		if (cnt[i] > 1)
			id[i] = tot++;
		else
			id[i] = -1;
	}

	for (int i = 1; i <= n; i++)
		cin >> ww[i];
	for (int i = 1; i <= n; i++)
		w[i] = ww[c[i]];
	
	for (int i = 1; i <= n; i++)
		c[i] = id[c[i]];

	for (int i = 1; i <= m; i++)
	{
		int u, v;
		cin >> u >> v;
		add(u, v);
	}

	memset(f, -1, sizeof f);
	f[0][1] = 0;
	for (int u = 1; u <= n; u++)
	{
		for (int s = 0; s < 1 << tot; s++)
		{
			if (f[s][u] == -1)
				continue;
			//更新自身,选还是不选
			if (c[u] == -1)
				f[s][u] += w[u];
			else
			{
				if (!(s >> c[u] & 1))
					f[s | (1 << c[u])][u] = max(f[s | (1 << c[u])][u], f[s][u] + w[u]);
			}
			ans[u] = max(ans[u], f[s][u]);
			//转移
			for (auto v : G[u])
				f[s][v] = max(f[s][v], f[s][u]);
		}
	}

	for (int i = 1; i <= n; i++)
		cout << ans[i] << endl;
    return 0;
}

floyd + dfs

本题有个性质是每个颜色的权值是一样的,所以可以认为第一次遇到某个颜色的时候就选择,所以可以 dfs(若每个颜色的权值不一定相同就只能按上述的状压dp)

首先先用 floyd 优化一下边(加速 dfs)

若存在 i -> k, k -> j, i -> j 三条边,明显多经过几个点会更优,所以只需要保留 i -> k -> j 一条路径即可,可删去 i -> j 这条边 (由于边数很小,可以压缩为二进制,也可以用邻接矩阵、 set 来存边删边)

优化完复杂度我也不会算。。。据说 dfs 是 O(n)

#include <iostream>
#include <algorithm>
#include <cstring>
#include <vector>
#include <cmath>

using namespace std;
#define endl "\n"

typedef long long ll;
typedef pair<int, int> PII;

ll n, m;
ll c[50], w[50], vis[50];
ll dp[50], path[50], ans[50];

void dfs(ll now, ll fa)
{
	if (!vis[c[now]])
		dp[now] = dp[fa] + w[c[now]];
	else
		dp[now] = dp[fa];
	ans[now] = max(ans[now], dp[now]);
	vis[c[now]]++;
	for (int i = 1; i <= n; i++)
	{
		if (path[now] & ((ll)1 << i))
		{
			dfs(i, now);
			vis[c[i]]--;
		}
	}
}

void solve()
{
	cin >> n >> m;
	for (int i = 1; i <= n; i++)
		cin >> c[i];
	for (int i = 1; i <= n; i++)
		cin >> w[i];
	for (int i = 1; i <= m; i++)
	{
		ll u, v;
		cin >> u >> v;
		path[u] = path[u] | ((ll)1 << v);
	}
	for (int i = 1; i <= n; i++)
		for (int j = 1; j <= n; j++)
			for (int k = 1; k <= j; k++)
				if (path[i] & ((ll)1 << k) && path[k] & ((ll)1 << j) && path[i] & ((ll)1 << j))
					path[i] -= (ll)1 << j;
	dfs(1,0);
	for (int i = 1; i <= n; i++)
		cout << ans[i] << endl;
}

int main()
{
	solve();
	return 0;
}


这篇关于连锁商店 (2021CCPC女生赛)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程