返回顶部

YACS 2022年4月月赛乙组题解

上锁的抽屉

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

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

\[f_{i , j , 0} = f_{i - 1 , j , 0} + f_{i - 1 , j , 1}\\ f_{i , j , 1} = f_{i - 1 , j , 0} + f_{i - 1 , j - 1 , 1}[j \geq 1] \]

考虑到数据范围\(1 \leq m \leq n \leq 5000\),故内存不够,需要使用滚动数组优化空间。

时间复杂度:\(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\)次补给机会在第 \(x_i\)天结束的时候 ,这次机会可以补给\(a_i\)个单位的物资。请帮助小爱设计一个在补给最小次数的状态下渡过游戏的方法。如果无法坚持到最后,输出 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,定义状态\(f_{i ,j}\)表示区间[i , j]的最长匹配子序列,则易得转移方程:

\[\begin{align} f_{i , j} &= f_{i + 1 , j - 1} + 2 \; s_i = s_j\\ f_{i , j} &= \mathop{max}\limits_{k = i}^{j - 1} (f_{i , k} + f_{k + 1 , j}) \end{align} \]

上述转移方程中的\(s_i = s_j\)表示的是\(s_i\)\(s_j\)配对。

时间复杂度为:\(O(n^3)\)

参考代码:

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 @ 2022-04-13 10:58  cherish-lgb  阅读(325)  评论(0编辑  收藏  举报