CSP 总结
CSP-J2022
A 乘方
直接把 \(a=1\) 特判掉,开 \(\mathrm{long~long}\) 暴力乘。
void solve() {
ll s = 1, a, b;
cin >> a >> b;
if(a == 1) {
cout << 1 << endl;
return;
}
REP(i, b) {
s *= a;
if(s > 1e9) {
cout << -1 << endl;
return;
}
}
cout << s << endl;
}
B 解密
解一个一元二次方程即可。
void solve() {
ll n, e, d, m;
cin >> n >> e >> d;
m = e * d;
ll B = n - m + 2;
ll D = B * B - 4 * n;
ll S = sqrt(D);
if(D < 0 || S * S != D) {
cout << "NO" << endl;
return;
}
if(B - S <= 0 || (B - S) % 2) {
cout << "NO" << endl;
return;
}
cout << (B - S) / 2 << " " << (B + S) / 2 << endl;
}
C 逻辑表达式
性质题。
首先题目中说与运算比或预算优先级高,这个条件可以消除掉。
假如有式子 \(a|b\&c\)。
如果 \(a\) 为 \(0\),那么对结果没有影响。
如果 \(a\) 为 \(1\),那么直接短路。
所以优先级不重要。
考虑从头开始扫。
如果有或运算的短路,那么把之后连续的与运算跳过即可。
如果有与运算的短路,就也是把之后连续的与运算跳过。
void solve() {
int n;
string s;
cin >> s; n = s.size();
int res = 0, A = 0, B = 0, F = 0;
REP(i, n) {
if(F) {
if(s[i] == '(') {
int cnt = 1;
while(cnt) {
i++;
if(s[i] == '(') cnt++;
if(s[i] == ')') cnt--;
}
}
else if(F == 1 && s[i] == '|') {
F = 0;
}
else if(s[i] == ')') {
F = 0;
}
else if(F == 1 && s[i] == '&') {
A++;
}
else if(F == 2 && s[i] == '|') {
B++;
}
}
else {
if(s[i] == '1') res = 1;
if(s[i] == '0') res = 0;
if(s[i] == '&' && !res) {
F = 1;
A++;
}
if(s[i] == '|' && res) {
F = 2;
B++;
}
}
}
cout << res << endl << A << " " << B << endl;
}
D 上升点列
设置状态 \(f_{i, j}\) 为第 \(i\) 个点为连线终点,还剩 \(j\) 个自由点可以用的最长长度。
直接 \(O(n^2k)\) 转移即可。
void solve() {
int n, k;
cin >> n >> k;
vector<PII> a(n);
REP(i, n) cin >> FI(a[i]) >> SE(a[i]);
sort(ALL(a));
vector<vector<int>> f(n, vector<int>(k + 1));
REP(i, n) {
f[i][k] = 1;
REP(j, i) {
if(SE(a[j]) <= SE(a[i])) {
int len = SE(a[i]) - SE(a[j]) + FI(a[i]) - FI(a[j]) - 1;
FOR(l, 0, k) {
int r = l + len;
if(r > k) break;
chmax(f[i][l], f[j][r] + len + 1);
}
}
}
}
int ans = 0;
REP(i, n) {
FOR(j, 0, k) {
chmax(ans, f[i][j] + j);
}
}
cout << ans << endl;
}
CSP-S2022
A 假期计划
首先 \(\mathrm{bfs}\) \(O(n_2)\) 算出每两个点之间的距离。
随后考虑先经过两个点的情况,在合并成四个点的。
但是会发现可能两个不同的点经过值最大的点可能是相同的,
为了避免这种问题,直接记录最大值,次大值,次次大值即可。
void solve() {
int n, m, k;
cin >> n >> m >> k;
vector<ll> a(n);
FOR(i, 1, n - 1) cin >> a[i];
vector<vector<int>> e(n);
REP(i, m) {
int u, v;
cin >> u >> v;
u--; v--;
e[u].push_back(v);
e[v].push_back(u);
}
vector<vector<int>> d(n, vector<int>(n, INF));
REP(i, n) {
d[i][i] = 0;
queue<int> q;
q.push(i);
while(!q.empty()) {
int u = q.front();
q.pop();
for(int v : e[u]) {
if(d[i][v] > d[i][u] + 1) {
d[i][v] = d[i][u] + 1;
q.push(v);
}
}
}
}
vector<array<int, 3>> f(n);
FOR(i, 1, n - 1) {
FOR(j, 1, n - 1) {
if(i == j) continue;
if(d[0][j] > k + 1 || d[j][i] > k + 1) continue;
if(a[j] >= a[f[i][0]]) {
f[i][2] = f[i][1];
f[i][1] = f[i][0];
f[i][0] = j;
}
else if(a[j] >= a[f[i][1]]) {
f[i][2] = f[i][1];
f[i][1] = j;
}
else if(a[j] >= a[f[i][2]]) {
f[i][2] = j;
}
}
}
ll ans = 0;
FOR(i, 1, n - 1) {
FOR(j, 1, n - 1) {
if(i == j) continue;
if(d[i][j] > k + 1) continue;
REP(l, 3) {
REP(r, 3) {
if(!f[i][l] || !f[j][r]) continue;
if(f[i][l] != f[j][r] && f[i][l] != j && f[j][r] != i) {
chmax(ans, a[i] + a[j] + a[f[i][l]] + a[f[j][r]]);
}
}
}
}
}
cout << ans << endl;
}
B 策略游戏
简单的博弈论。
首先像第二个人的策略,因为第一个人会思考第二个人的策略。
假如第一个人选了正数,那么第二个人就需要选最小值,反之亦然。
然后讨论第一个人的策略。
如果第二个数组都是正数,那肯定选最大值,反之亦然。
如果有正有负,那第二个人就肯定要把最终答案变成负数,
所以要尝试正数的最小值和负数的最大值。
用六个 \(\mathrm{ST}\) 表维护。
void solve() {
int n, m, q;
cin >> n >> m >> q;
vector<int> a(n + 1), b(m + 1);
FOR(i, 1, n) cin >> a[i];
FOR(i, 1, m) cin >> b[i];
sparse_table_max<int> A1(n, a);
sparse_table_min<int> A2(n, a);
sparse_table_max<int> B1(m, b);
sparse_table_min<int> B2(m, b);
vector<int> s(n + 1);
FOR(i, 1, n) s[i] = (a[i] >= 0 ? -INF : a[i]);
sparse_table_max<int> A3(n, s);
FOR(i, 1, n) s[i] = (a[i] < 0 ? INF : a[i]);
sparse_table_min<int> A4(n, s);
REP(i, q) {
int l1, r1, l2, r2;
cin >> l1 >> r1 >> l2 >> r2;
int a1 = A1.query(l1, r1);
int a2 = A2.query(l1, r1);
int a3 = A3.query(l1, r1);
int a4 = A4.query(l1, r1);
int b1 = B1.query(l2, r2);
int b2 = B2.query(l2, r2);
ll ans = -LINF;
chmax(ans, 1ll * a1 * (a1 >= 0 ? b2 : b1));
chmax(ans, 1ll * a2 * (a2 >= 0 ? b2 : b1));
if(a3 != -INF) chmax(ans, 1ll * a3 * (a3 >= 0 ? b2 : b1));
if(a4 != INF) chmax(ans, 1ll * a4 * (a4 >= 0 ? b2 : b1));
cout << ans << endl;
}
}
C 星战
首先转换题意,题目有加边和删边操作,询问当前每个点的出度是否都为 \(1\)。
那就是每个点的入度总集合等于所有的点,
用合哈希维护即可。
void solve() {
mt19937 rd(time(0));
int n, m;
cin >> n >> m;
ll ans = 0, res = 0;
vector<int> a(n + 1);
FOR(i, 1, n) {
a[i] = rd();
ans += a[i];
}
vector<ll> f(n + 1), g(n + 1);
REP(i, m) {
int u, v;
cin >> u >> v;
f[v] += a[u];
res += a[u];
g[v] = f[v];
}
int q;
cin >> q;
REP(i, q) {
int opt, u, v;
cin >> opt;
if(opt == 1) {
cin >> u >> v;
f[v] -= a[u];
res -= a[u];
}
if(opt == 2) {
cin >> u;
res -= f[u];
f[u] = 0;
}
if(opt == 3) {
cin >> u >> v;
f[v] += a[u];
res += a[u];
}
if(opt == 4) {
cin >> u;
res += g[u] - f[u];
f[u] = g[u];
}
cout << (ans == res ? "YES" : "NO") << endl;
}
}