其他分享
首页 > 其他分享> > 连锁商店 (2021CCPC女生赛)

连锁商店 (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;
}

标签:now,女生,int,ll,50,2021CCPC,连锁商店,include,dp
来源: https://www.cnblogs.com/hzy717zsy/p/16541417.html