T1,T2 不讲

T3: 逻辑表达式

20 分做法:|s|3 的时候能满足的字符串很少只有 a|ba&bab 四种。|s|5 的时候再加上a|b|ca|b&ca&b|ca&b&c 这四种,把八种情况分别计算就能拿到 20 分。

50 分做法:考虑 |s|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 分做法:当 xiyi 比较小的时候,我们可以把它当成一个棋盘 dp,如果这个坐标有对应的点那么代价为 0,否则代价为 1,任意点都能作为起点,枚举终点,求出代价小于 k 的时候到达终点的最长路。可以通过 171115 的测试点。

75 分做法:再考虑 k=0 的情况,只能连续选择,把每个点按照横坐标排序,然后枚举每个点 ii 只能接在坐标小于 i 且曼哈顿距离为 1 的点 j 后面,dp[i]=max(dp[j]+1)。可以通过 810 的测试点。

100 分做法:考虑到添加的 k 个点是自由的,我们可以用这 k 个点来填补曼哈顿距离大于 1 的相邻点,如果两个点之间没有相邻点,并且它们的曼哈顿距离是 d,那么需要填充 d1 个点,才能把这两个点同时选出来。整个序列最多填充 k 个点。
我们可以把填充的点数当做选择相邻两个点的代价,那么这道题就转化成在 n 个点选出总代价不超过 k 的序列,要求序列最长,就变成一个子序列问题了。
子序列的另一个要求是横坐标、纵坐标均单调不减,我们把 n 个点按 x 坐标排序,枚举当前点之前的所有点,再判断 y 坐标是否小于等于当前点即可。
使用动态规划求解子序列问题,记 dp[i][j] 表示在排序后的前 i 个点中,选出代价为 j 的子序列,能得到的序列的最长长度。
转移方程:

dp[i][j]=max{dp[t][j(d1)]+d}

其中 t 的取值范围是 1i1dt 点与 i 点的曼哈顿距离。

需要注意,最后 k 个点一定全部都会用上,如果求出序列没有用上所有的 k 个点,那么可以把剩下的 k 个点直接加在最后一个点后面。

时间复杂度:O(n2k)

代码实现
#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;
}