返回顶部

YACS 2022年4月月赛乙组题解

作者:@cherish.
课程学习内容为作者从学校的PPT处摘抄,仅供自己学习参考,若需转载请注明出处:https://www.cnblogs.com/cherish-/p/16139280.html


上锁的抽屉

题目描述:有n个竖着的抽屉,一个抽屉被锁死当且仅当自己和它上面的抽屉都被锁死。问恰好锁死m个抽屉的方案数,答案对1e9 + 7取模。

思路:比较明显的dp,定义状态fi,j,k表示对于前i个抽屉,锁死j个的方案数,其中k=0,1,表示第i个抽屉是否被锁死,显然有转移方程:

fi,j,0=fi1,j,0+fi1,j,1fi,j,1=fi1,j,0+fi1,j1,1[j1]

考虑到数据范围1mn5000,故内存不够,需要使用滚动数组优化空间。

时间复杂度:O(nm)

参考代码:

void solve() {
	int n, m;
	cin >> n >> m;
	const int mod = 1e9 + 7;
	vector<vector<vector<int>>>f(2, vector<vector<int>>(m + 1, vector<int>(2, 0)));
	int op = 0;
	f[0][0][0] = 1;
	f[0][1][1] = 1;
	for (int i = 2; i <= n; ++i) {
		for (int j = 0; j <= m; ++j) {
			f[op ^ 1][j][0] = (f[op][j][0] + f[op][j][1]) % mod;
			f[op ^ 1][j][1] = f[op][j][0];
			if(j >= 1) f[op ^ 1][j][1] = (f[op ^ 1][j][1] + f[op][j - 1][1]) % mod;
		}
		op ^= 1;
	}
	int res = (f[op][m][0] + f[op][m][1]) % mod;
	cout << res << '\n';
	return;
}

生存游戏

题目描述:小爱需要坚强地渡过 d 天,每过一天要消耗一个单位的物资,如果在中途物没有物资,她就输了。一开始,小爱有c个单位的物资。游戏中有n次补给机会,第i次补给机会在第 xi天结束的时候 ,这次机会可以补给ai个单位的物资。请帮助小爱设计一个在补给最小次数的状态下渡过游戏的方法。如果无法坚持到最后,输出 Impossible

思路:比较明显的贪心,我们可以将所有小于当前可到达位置的物资放入一个优先队列中,若物资不足时,从优先队列中取出最大的一个增加到当前的物资上,若需要物资时队列为空,则说明不能到达,否则输出取数次数即可。

时间复杂度:O(nlogn)

参考代码:

void solve() {
	int n, d;
	long long c;
	cin >> n >> c >> d;
	vector<int>x(n + 2, 0), a(n + 2, 0);
	for (int i = 1; i <= n; ++i)cin >> x[i] >> a[i];
	x[++n] = d;
	priority_queue<int> heap;
	int res = 0;
	for (int i = 1; i <= n; ++i) {
		while (c < x[i] && !heap.empty()) {
			c += heap.top();
			heap.pop();
			++res;
		}
		if (c < x[i]) break;
		heap.push(a[i]);
	}
	if (c < d) cout << "Impossible" << '\n';
	else cout << res << '\n';
	return;
}

狼人游戏

题目描述:有n个人在一起玩狼人游戏,游戏中有一些玩家的身份是狼人,剩下玩家的身份是平民。狼人知道彼此之间的身份,而平民对其他人的身份信息一无所知。天亮时,每名玩家需要指证另一名玩家是狼人。狼人一定会指证平民,而平民可能指证狼人,也可能指证另一个平民。给定每名玩家的指证对象,请分析场面上最多可能有多少名狼人?注意游戏规定至少需要有一名平民。

思路:比较裸的基环树上求最大独立集,因为此处点不带权值,故考虑贪心,对于某个入度为0的点u,它指向v,那么显然是将v染黑较优,所以可以使用类似拓扑排序的方式给点打上标记,然后再计数即可。

时间复杂度:O(n)

参考代码:

void solve() {
	int n;
	cin >> n;
	vector<int>adj(n + 1, 0), income(n + 1, 0);
	for (int i = 1; i <= n; ++i) {
		int v;
		cin >> v;
		adj[i] = v;
		income[v]++;
	}
	vector<bool>vis(n + 1, false);
	int res = 0;
	auto dfs = [&](auto&& dfs, int u, int w)->void {
		if (vis[u]) return;
		vis[u] = true;
		if (w) ++res;
		--income[adj[u]];
		if (income[adj[u]] == 0 || w == 1) dfs(dfs , adj[u], !w);
		return;
	};
	for (int i = 1; i <= n; ++i) if (!income[i]) dfs(dfs, i, 1);
	for (int i = 1; i <= n; ++i) if (!vis[i]) dfs(dfs, i, 0);
	cout << res << '\n';
        return ;
}

双倍经验:带权的基环树上的最大独立集

P2607 [ZJOI2008] 骑士

思路:可以参考T1725 天黑请闭眼 ,此处只给出参考代码

struct Edges {
	int u, v, idx;
	Edges(int _u = 0, int _v = 0, int _idx = 0):u(_u) ,v(_v) , idx(_idx){}
};
void solve() {
	int n;
	cin >> n;
	vector<int>p(n + 1, 0);
	for (int i = 1; i <= n; ++i) p[i] = i;
	auto find = [&](auto&& find, int x)->int {return x == p[x] ? x : p[x] = find(find, p[x]); };
	using PII = pair<int, int>;
	vector<vector<PII>>graph(n + 1);
	vector<int>weight(n + 1, 0);
	vector<Edges>edges;
	for (int i = 1; i <= n; ++i) {
		int u = i, v = 0;
		cin >> weight[i] >> v;
		graph[u].push_back({ v , (i << 1) - 1 });
		graph[v].push_back({ u , i << 1});
		int pu = find(find, u), pv = find(find, v);
		if (pu != pv) p[pu] = pv;
		else edges.push_back({ u , v , i << 1 });
	}
	vector<vector<long long>>f(n + 1, vector<long long>(2, 0));
	auto dfs = [&](auto&& dfs, int u, int fa, int idx)->void {
		f[u][0] = 0; f[u][1] = weight[u];
		for (auto&& [v , id] : graph[u]) {
			if (v == fa || id == idx || id == idx - 1) continue;
			dfs(dfs, v, u, idx);
			f[u][0] += max(f[v][0], f[v][1]);
			f[u][1] += f[v][0];
		}
		return;
	};
	long long res = 0;
	for (auto&& [u, v, idx] : edges) {
		dfs(dfs , u, 0, idx);
		long long ans = f[u][0];
		dfs(dfs, v, 0, idx);
		res += max(ans, f[v][0]);
	}
	cout << res << '\n';
	return;
}

匹配括号(二)

题目描述:给定一个仅由 ()[] 构成的字符串,若它不是匹配的,请求出最少删去多少括号可以使它变成匹配的。匹配定义如下:

  • 空字符串是匹配的;
  • 如果字符串 s 是匹配的,那么 (s)[s] 也是匹配的;
  • 如果字符串 st 都是匹配的,那么 st 也是匹配的。

思路:比较明显的dp,定义状态fi,j表示区间[i , j]的最长匹配子序列,则易得转移方程:

(1)fi,j=fi+1,j1+2si=sj(2)fi,j=maxk=ij1(fi,k+fk+1,j)

上述转移方程中的si=sj表示的是sisj配对。

时间复杂度为:O(n3)

参考代码:

void solve() {
	string s;
	cin >> s;
	int n = s.size();
	s = ' ' + s;
	vector<vector<int>>f(n + 1, vector<int>(n + 1, 0));
	for (int len = 2; len <= n; ++len) {
		for (int i = 1; i + len - 1 <= n; ++i) {
			int j = i + len - 1;
			if ((s[i] == '(' && s[j] == ')') || (s[i] == '[' && s[j] == ']')) f[i][j] = f[i + 1][j - 1] + 2;
			if (i == 1 && j == n)
				j = n;
			for (int k = i; k < j; ++k) 
				f[i][j] = max(f[i][j], f[i][k] + f[k + 1][j]);
		}
	}
	cout << n - f[1][n] << '\n';
	return;
}
posted @   cherish-lgb  阅读(376)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
点击右上角即可分享
微信分享提示