2021年中国大学生程序设计竞赛女生专场 gym103389C 连锁商店

C. 连锁商店

比特山是一个旅游胜地,它一共有 n 个景点,按照海拔高度从低到高依次编号为 1 到 n。为了更好地帮助游客们欣赏这里的风景,人们在上面搭建了 m 条缆车路线。每条缆车路线只可能把游客们从某个海拔较低的景点运送到另一个海拔较高的景点。
在每个景点都有一家纪念品连锁商店,其中第 i 个景点的商店隶属第 ci 号公司,两家连锁店 (i,j) 隶属同一公司当且仅当 ci=cj。每家公司都有新客优惠活动,其中第i家公司对于新客的优惠红包为 wi 元,一旦领取了隶属该公司的某家连锁店的一份红包,就不能再领取该公司所有分店的红包。
你正在 1 号景点,你将会搭乘缆车去往各个景点,每到一个景点,你都可以领取该景点的连锁商店的新客优惠红包(包括 1 号景点)。当然,同一家公司的红包最多只能领一次。请写一个程序,对于每个可能的终点 k,找到一条从 1 号景点出发到达 k 号景点的游览路线,使得可以领取到总金额最多的优惠红包。
Input
第一行包含两个正整数 n,m (2≤n≤36, 1≤m≤n(n−1)2),分别表示景点的数量以及缆车路线的数量。
第二行包含 n 个正整数 c1,c2,…,cn (1≤ci≤n),依次表示每个景点的商店所隶属的公司。
第三行包含 n 个正整数 w1,w2,…,wn (1≤wi≤106),依次表示每家公司的新客优惠红包的金额。
接下来 m 行,每行两个正整数 ui,vi (1≤ui<vi≤n),表示一条缆车路线,起点是景点 ui,终点是景点 vi。输入数据保证任意两个景点之间最多只有一条缆车路线,且从 1 号景点出发可以到达任意一个景点。
Output
输出 n 行,第 i 行输出一个整数,即从 1 号景点出发到达 i 号景点时,领取的优惠红包的总金额的最大值。

思路:
比赛时的心路历程非常曲折,具体就是2^36的状态太大了不适合dp,所以暴力+剪枝。
玄学剪枝 a -> b, b -> c, a -> c 这种走 a -> b -> c 一定比直接 a -> c 优。

赛后看题解,发现只存在一个或零个景点的公司(第一类公司)可以不需要考虑,只需要考虑有多个景点的公司(第二类公司)就可以了,也只需要存储这类公司的状态。
f[st][i] 表示第二类公司选取的状态为st,当前在景点i,走到的第一类公司的最大红包金额和。
最后统计结果时要加上状态代表的 第二类公司 的红包金额。

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

#define read(x) scanf("%d",&x)
#define maxn 40
#define maxs (1<<18)
#define inf (1<<30)

int n, m;
int c[maxn+5];
int w[maxn+5];
vector<int> g[maxn+5];

int cntc[maxn+5];
int mul[maxn+5], cnt=0, id[maxn+5];

int s;
int t[maxn+5];
int f[maxs+5][maxn+5];

int findw(int x) {
	x=c[x];
	if (cntc[x]>=2) return 0;
	return w[x];
}

int Count(int x) {
	int sum=0;
	for (int i=1; i<=n; i++) {
		if (x&1) {
			sum+=w[mul[i]];
		}
		x>>=1;
	}
	return sum;
}

int main() {
	read(n), read(m);
	for (int i=1; i<=n; i++) read(c[i]);
	for (int i=1; i<=n; i++) read(w[i]);
	for (int i=1; i<=m; i++) {
		int x, y;
		read(x), read(y);
		g[x].push_back(y);
	}

	for (int i=1; i<=n; i++) ++cntc[c[i]];
	for (int i=1; i<=n; i++) if (cntc[i]>=2) mul[++cnt]=i, id[i]=cnt;
	for (int i=1; i<=n; i++) t[i]=findw(i);
	s=(1<<cnt);

	memset(f,-1,sizeof(f));
	if (t[1]) f[0][1]=t[1];
	else f[1<<(id[c[1]]-1)][1]=f[0][1]=0;
	for (int st=0; st<s; st++) {
		for (int i=1; i<=n; i++) {
			if (f[st][i]==-1) continue;
			for (int j=0; j<g[i].size(); j++) {
				int y=g[i][j];
				if (t[y]) {
					f[st][y]=max(f[st][y], f[st][i]+t[y]);
				} else {
					int sty=(1<<id[c[y]]-1);
					f[sty|st][y]=max(f[sty|st][y], f[st][i]);
				}
			}
		}
	}

	for (int i=1; i<=n; i++) {
		int ans=0;
		for (int st=0; st<s; st++) {
			if(f[st][i]!=-1) ans=max(ans,f[st][i]+Count(st));
		}
		printf("%d\n", ans);
	}

	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值