\(T1, T2\) 不讲

T3: 逻辑表达式

\(20\) 分做法:\(|s| \leqslant 3\) 的时候能满足的字符串很少只有 a|ba&bab 四种。\(|s| \leqslant 5\) 的时候再加上a|b|ca|b&ca&b|ca&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\) 的子序列,能得到的序列的最长长度。
转移方程:

\[dp[i][j] = \max\{dp[t][j-(d-1)]+d\} \]

其中 \(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;
}