\(T1, T2\) 不讲
T3: 逻辑表达式
\(20\) 分做法:\(|s| \leqslant 3\) 的时候能满足的字符串很少只有 a|b
,a&b
, a
,b
四种。\(|s| \leqslant 5\) 的时候再加上a|b|c
,a|b&c
,a&b|c
,a&b&c
这四种,把八种情况分别计算就能拿到 \(20\) 分。
\(50\) 分做法:考虑 \(|s| \leqslant 2000\) 的情况,我们可以建出表达式树,递归求解答案
\(85\) 分做法:对于特殊性质 \(1\),我们可以从左往右处理,看这样一个例子 0|(1|0)|(0|(1|0))
, 遇到左括号:如果 |
的左边是 \(1\) 就直接跳到对应右括号,如果 |
的左边是 \(0\) 就递归到下一层
\(100\) 分做法:本题是一道模拟题,含括号的表达式求值,一般可以使用递归或栈模拟。
中缀表达式转后缀表达式
代码实现
#include <bits/extc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
using namespace std;
struct Node {
int v, cOr, cAnd;
Node() {}
Node(int v=0, int cOr=0, int cAnd=0): v(v), cOr(cOr), cAnd(cAnd) {}
};
int getPriority(char op) {
if (op == '(') return 0;
if (op == '|') return 1;
return 2;
}
string inToPost(string s) {
stack<char> stk;
string res;
for (int c : s) {
if (c == '(') stk.push('(');
else if (c == ')') {
while (stk.top() != '(') {
res += stk.top();
stk.pop();
}
stk.pop();
}
else if (c == '&' or c == '|') {
while (stk.size() and getPriority(c) <= getPriority(stk.top())) {
res += stk.top();
stk.pop();
}
stk.push(c);
}
else res += c;
}
while (stk.size()) {
res += stk.top();
stk.pop();
}
return res;
}
int main() {
string s;
cin >> s;
string t = inToPost(s);
stack<Node> stk;
for (char c : t) {
if (c == '|') {
auto [v1, cOr1, cAnd1] = stk.top(); stk.pop();
auto [v2, cOr2, cAnd2] = stk.top(); stk.pop();
stk.emplace(v2|v1, cOr2+(v2==1?1:cOr1), cAnd2+(v2==1?0:cAnd1));
}
else if (c == '&') {
auto [v1, cOr1, cAnd1] = stk.top(); stk.pop();
auto [v2, cOr2, cAnd2] = stk.top(); stk.pop();
stk.emplace(v2&v1, cOr2+(v2==0?0:cOr1), cAnd2+(v2==0?1:cAnd1));
}
else stk.emplace(c-'0', 0, 0);
}
auto [v, cOr, cAnd] = stk.top();
cout << v << '\n';
cout << cAnd << ' ' << cOr << '\n';
return 0;
}
T4:上升点列
\(40\) 分做法:\(k = 0\) 时,可用类似最长上升子序列的做法处理
\(50\) 分做法:当 \(x_i\) 和 \(y_i\) 比较小的时候,我们可以把它当成一个棋盘 \(dp\),如果这个坐标有对应的点那么代价为 \(0\),否则代价为 \(1\),任意点都能作为起点,枚举终点,求出代价小于 \(k\) 的时候到达终点的最长路。可以通过 \(1 \sim 7\) 和 \(11 \sim 15\) 的测试点。
\(75\) 分做法:再考虑 \(k = 0\) 的情况,只能连续选择,把每个点按照横坐标排序,然后枚举每个点 \(i\),\(i\) 只能接在坐标小于 \(i\) 且曼哈顿距离为 \(1\) 的点 \(j\) 后面,\(dp[i] = \max(dp[j] +1)\)。可以通过 \(8 \sim 10\) 的测试点。
\(100\) 分做法:考虑到添加的 \(k\) 个点是自由的,我们可以用这 \(k\) 个点来填补曼哈顿距离大于 \(1\) 的相邻点,如果两个点之间没有相邻点,并且它们的曼哈顿距离是 \(d\),那么需要填充 \(d-1\) 个点,才能把这两个点同时选出来。整个序列最多填充 \(k\) 个点。
我们可以把填充的点数当做选择相邻两个点的代价,那么这道题就转化成在 \(n\) 个点选出总代价不超过 \(k\) 的序列,要求序列最长,就变成一个子序列问题了。
子序列的另一个要求是横坐标、纵坐标均单调不减,我们把 \(n\) 个点按 \(x\) 坐标排序,枚举当前点之前的所有点,再判断 \(y\) 坐标是否小于等于当前点即可。
使用动态规划求解子序列问题,记 dp[i][j]
表示在排序后的前 \(i\) 个点中,选出代价为 \(j\) 的子序列,能得到的序列的最长长度。
转移方程:
其中 \(t\) 的取值范围是 \(1 \sim i-1\),\(d\) 是 \(t\) 点与 \(i\) 点的曼哈顿距离。
需要注意,最后 \(k\) 个点一定全部都会用上,如果求出序列没有用上所有的 \(k\) 个点,那么可以把剩下的 \(k\) 个点直接加在最后一个点后面。
时间复杂度:\(O(n^2k)\)
代码实现
#include <bits/stdc++.h>
#define rep(i, n) for (int i = 0; i < (n); ++i)
#define x first
#define y second
using namespace std;
using P = pair<int, int>;
int dp[505][105];
inline void chmax(int& x, int y) { if (x < y) x = y; }
int main() {
int n, k;
cin >> n >> k;
vector<P> ps(n);
rep(i, n) cin >> ps[i].x >> ps[i].y;
sort(ps.begin(), ps.end());
int ans = 0;
rep(i, n) {
rep(j, k+1) {
dp[i][j] = j+1;
rep(t, i) if (ps[t].y <= ps[i].y) {
int d = (ps[i].x-ps[t].x) + (ps[i].y-ps[t].y);
int nj = j-d+1;
if (nj >= 0) chmax(dp[i][j], dp[t][nj]+d);
}
}
chmax(ans, dp[i][k]);
}
cout << ans << '\n';
return 0;
}