2021年中国大学生程序设计竞赛女生专场-C.连锁商店(状压 + Floyd优化)

使用深度优先搜索解决景点游览最大价值问题

https://codeforces.com/gym/103389/problem/C

题意:一共有 n n n 个景点,每个景点属于一个公司,并给出每个公司的价值,如果是第一次到这个公司就会获得该公司的价值,问从1到每一个景点分别能获得的最大价值。有 m m m 个缆车,连接着两个景点,且景点只能从小往大走

思路:用 p a t h path path 数组来存每个点的缆车接下来可以到达的点,由于最多每次都可能有36种选择,太大数组无法开,所以考虑状态压缩,用二进制来存可以去的点,只需要 2 36 2^{36} 236 就可以存。如 p a t h 1 = 10100 = 20 path_1 = 10100 = 20 path1=10100=20 表示可以 1 1 1 可以去 3 , 5 3, 5 3,5,不可去 2 , 4 2,4 2,4,可以去几从下往上第几位就是 1 1 1

//表示从景点 u 可以到 v
for (int i = 1; i <= m; i++)
{
	ll u, v;
	cin >> u >> v;
	path[u] = path[u] | ((ll)1 << v);	
	//若path[u]到 v 的那一点本身就为1,即有重复缆车,只需记录一条
}

考虑到可能有价值低的走法,如 1 − 1 - 1> 2 2 2 2 − 2 - 2> 3 3 3 1 − 1 - 1> 3 3 3 同时存在,但 1 − 1 - 1> 2 2 2 2 − 2 - 2> 3 3 3 显然比 1 − 1 - 1> 3 3 3 更优,所以利用 F l o y d Floyd Floyd 进行优化

//若 i 可以到 j 且 i 可以到 k , k 可以到 j ,那么删去 i 到 j 的边
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;

d p i dp_i dpi 表示当前走法走到 i i i 能得到的最大价值, a n s i ans_i ansi 记录答案, c i c_i ci 记录点 i i i 的公司, w i w_i wi 记录第 i i i 个公司的价值,注意 w i w_i wi 不是第 i i i 个景点的价值, f a fa fa表示到当前点的上一起点
接下来开始从初始节点 1 往下深搜遍历所有点。用 v i s vis vis 数组记录是否去过某个公司。 v i s i = = 0 vis_i==0 visi==0,那么现在的点 now 得到的价值肯定会加上当前公司的价值,即 d p n o w = d p f a + w c n o w   ;   v i s i = = 1 dp_{now} = dp_{fa} +w_{c_{now}}\ ;\ vis_i==1 dpnow=dpfa+wcnow ; visi==1,那么 d p n o w = d p f a dp_{now} = dp_{fa} dpnow=dpfa,再从该点往下遍历更后面的点

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]]++ 和 vis[c[now]]-- 
	//若写为vis[c[now]] = 1 和 vis[c[now]] = 0 ,在所有景点都是同一个公司的时候 if (!vis[c[now]]) 的判断会一直为真
	vis[c[now]]++;
	for (int i = 1; i <= n; i++)
	{
		if (path[now] & ((ll)1 << i))
		{
			dfs(i, now);
			vis[c[i]]--;
		}
	}
}

代码:

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;
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值