YACS 2022年4月月赛乙组题解
上锁的抽屉
题目描述:有\(n\)个竖着的抽屉,一个抽屉被锁死当且仅当自己和它上面的抽屉都被锁死。问恰好锁死\(m\)个抽屉的方案数,答案对1e9 + 7
取模。
思路:比较明显的dp
,定义状态\(f_{i , j , k}\)表示对于前\(i\)个抽屉,锁死\(j\)个的方案数,其中\(k = 0 , 1\),表示第\(i\)个抽屉是否被锁死,显然有转移方程:
考虑到数据范围\(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]
也是匹配的; - 如果字符串
s
与t
都是匹配的,那么st
也是匹配的。
思路:比较明显的dp
,定义状态\(f_{i ,j}\)表示区间[i , j]
的最长匹配子序列,则易得转移方程:
上述转移方程中的\(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;
}
作者:cherish.
出处:https://home.cnblogs.com/u/cherish-/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。