CSP模拟22
火批专场。
骨架、灌伤、虚化、闪光
只为碎银几两
看世人慌慌张张只为碎银几两
偏偏这碎银几两
能解万种惆怅
世人啊匆匆忙忙
徒为碎银几两
奈何这碎银几两
让人心神荡漾
A. 骨架
考虑点的贡献异常麻烦,我们可以把点的贡献转化为边的贡献。
对于一条边,我们有如下几点:
- 伴随着所有的点被删掉,所有的边也会被删掉;
- 一条边连接的两个结点之一被删掉时,这条边就被删掉了;
- 当一条边被删掉时,它产生的贡献是没有被删掉的结点的权值。
所以,每条边产生的贡献之和就是我们要求的答案,我们要使得这个答案最小。
那我们只需要让每条边产生的贡献最小就可以了,每次删掉的是这条边连接的两个点中权值较大的那个。
我们也可以直接按照点权排序,从大到小依次删除。
#include <bits/stdc++.h>
using namespace std;
const int N = 2005000;
int n,m;
int val[N];
long long ans;
int main() {
ios::sync_with_stdio(false);
cin >> n >> m;
for(int i = 1;i <= n; i++)
cin >> val[i];
for(int i = 1,u,v;i <= m; i++) {
cin >> u >> v;
ans += min(val[u],val[v]);
}
cout << ans;
return 0;
}
B. 灌伤
首先考虑把括号序列改写成由 \(1\) 和 \(-1\) 构成的序列,把 \(\texttt{(}\) 看作 \(1\),\(\texttt{)}\) 看作 \(-1\)。
如果一个括号序列是匹配的,那么它的任意前缀的权值和均为非负,且整个序列的权值为 \(0\)。
如果一个 \(a\) 串能够在后面接一个 \(b\) 串使得形成了更长的括号序列,那么这个 \(a\) 串的权值一定是非负的。\(a\) 串最后可以有多出来的 \(\texttt{(}\),也可以没有多出的部分,但不能有多出的 \(\texttt{)}\)。
我们设这个 \(a\) 串的权值为 \(x\),设 \(b_i\) 的最小前缀和为 \(y\)。
-
假设 \(x+y<0\),那么说明在 \(a\) 和 \(b\) 拼接成的新串中存在前缀和小于 \(0\) 的前缀,即右括号数量多于左括号,不能再继续往后边加串了。我们造成的贡献是从 \(b\) 串开头到第一个小于 \(0\) 位置之前的部分中,前缀和为 \(-x\) 的位置的个数。也就是说,这里只更新答案,不继续进行转移。
-
若 \(x+y\geq0\),说明还能继续向后匹配,我们在更新答案的同时还需要继续向后转移。
考虑状压,开个桶存一下每种前缀和值的数量和第一次出现 \(-x-1\) 之前出现 \(-x\) 的数量,map
会被卡,可以用 unordered_map
,内部是用哈希实现(不过可能有人丧心病狂对着模数卡)。
Code
#include <bits/stdc++.h>
using namespace std;
#define lim(x) (1 << x - 1)
const int N = 33;
int n;
int ans = INT_MIN >> 2,sum;
string st[33];
int MinPre[lim(21) + 114],val[N];
int Val[lim(21) + 114];
int dp[lim(21) + 114];
unordered_map<int,int> bucket[N];
unordered_map<int,int> UpdateBucket[N];
bool legal[lim(21) + 114];
int main() {
cin >> n;
for(int i = 1;i <= n; i++) {
cin >> st[i];
sum = 0;
for(int j = 0;j < st[i].size(); j++) {
if(st[i][j] == '(')
sum += 1;
else
sum -= 1;
MinPre[i] = min(MinPre[i],sum);
bucket[i][sum] ++;
if(bucket[i][sum] == 1)
UpdateBucket[i][sum] = bucket[i][sum + 1];
}
val[i] = sum;
}
legal[0] = 1;
for(int s = 0;s < lim(n + 1); s++) {
sum = 0;
for(int i = 1;i <= n; i++)
if(s & lim(i))
sum += val[i];
Val[s] = sum;
// 状态的权值
}
for(int s = 0;s < lim(n + 1); s++) {
if(!legal[s])// 不合法
Val[s] = -1;
if(Val[s] < 0) {
dp[s] = 0;
continue;
}
sum = Val[s];
for(int i = 1;i <= n; i++) {
if(s & lim(i))
continue;
if(sum + MinPre[i] < 0) // 只更新不转移
ans = max(ans,dp[s] + UpdateBucket[i][-sum - 1]);
else {// 更新且转移
legal[s | lim(i)] = 1;
dp[s | lim(i)] = max(dp[s | lim(i)],dp[s] + bucket[i][-sum]);
}
}
}
for(int s = 0;s < lim(n + 1); s++)
ans = max(ans,dp[s]);
cout << ans;
return 0;
}
C. 虚化
斜率 + 矩阵优化 DP,不会,咕了 \(\dots\)
D. 闪光
好像用到点分治,先去学。