2024 牛客多校 2
T2
MST
考虑对询问点集的大小根号分治。若询问点集大小超过根号,则这样的询问次数不会超过根号,可以直接枚举所有边来找到所有导出子图中的边。若询问点集大小不超过根号,则可以直接平方枚举两个点检查中间是否有边。找到所有边之后直接跑 kruskal 即可。设阈值为 \(B\),则平方枚举的总复杂度为 \(\mathcal{O}(qB)\)。
T3
Red Walking on Grid
一定存在一种最优策略从一个红连通块的最左边出发,然后不向左地走下去。因此直接对着这个过程 dp 即可,\(dp[i][0 / 1]\) 表示到了第 \(i\) 列,当前在上一行还是下一行的最大收益,转移分讨当前列的状态即可。
代码
#include <iostream>
using namespace std;
const int inf = 0x7ffffff;
int n;
string str[2];
int dp[1000005][2];
int main() {
cin >> n;
cin >> str[0] >> str[1];
str[0] = ' ' + str[0], str[1] = ' ' + str[1];
int ans = 0;
for (int i = 1; i <= n; i++) {
if (str[0][i] == 'R' && str[1][i] == 'R') {
dp[i][0] = max(dp[i - 1][0] + 1, dp[i - 1][1] + 2);
dp[i][1] = max(dp[i - 1][1] + 1, dp[i - 1][0] + 2);
} else if (str[0][i] == 'R' && str[1][i] == 'W') {
dp[i][0] = dp[i - 1][0] + 1;
dp[i][1] = 0;
} else if (str[1][i] == 'R' && str[0][i] == 'W') {
dp[i][1] = dp[i - 1][1] + 1;
dp[i][0] = 0;
} else
dp[i][0] = dp[i][1] = 0;
ans = max(ans, max(dp[i][0], dp[i][1]) - 1);
}
cout << ans << "\n";
return 0;
}
T4
Taking Candies
首先发现一定存在一种策略使得每次拍卖只会有两人各出价一次。因为所有信息全部透明,因此若 \(C\) 出价之后 \(E\) 还要出价 \(x\),那显然 \(E\) 不如一开始就出价 \(x\)。然后拍卖必然是从大往小拍。
然后考虑 dp。肯定是倒着做,我们设 \(g(i, w)\) 表示要在倒数第 \(i\) 轮时获得 \(w\) 个糖果至少需要多少钱。然后考虑转移:
设 E 当前有 \(x\) 块钱,当前正在拍的糖果盒有 \(a\) 块糖。则他至多出的价就是 \(u = x - g(i - 1, w - a)\)(\(w < a\) 的话可以直接把 \(w - a\) 视为 \(0\))。如果出的价再高,那 C 就可以放弃这一轮,然后 E 剩的钱就会不够 \(g(i - 1, w - a)\),然后就似了。然后 E 出的价必然也不会低于 \(u\),因为在 C 不要的情况下,E 剩的钱只要足够 \(g(i - 1, w - a)\) 就可以了,剩更多其实没有意义。在 C 要价的情况下,发现 E 出的越多,赚得越多。所以最优情况下 E 必然就是出 \(u\) 这么多钱。
然后在这一轮 E 出价 \(u\) 的情况下,C 如果不要价那 E 直接就合法了,因此 C 必然会要价。那么这轮结束之后 E 就还剩 \(x + u + 1\) 这么多钱,然后没有获得这一轮的糖果。在这样的情况下发现 \(x + u + 1\) 必须大于等于 \(g(i - 1, w)\),也就是 \(2x - g(i - 1, w - a) \ge g(i - 1, w)\)。取 \(x\) 最小,也就是 \(\min x = g(i, w) = \lceil \frac{g(i - 1, w - a) + g(i - 1, w) - 1}{2} \rceil = \lfloor \frac{g(i - 1, w - a) + g(i - 1, w)}{2} \rfloor\)。这里如果 \(g(i - 1, w)\) 不是可达的状态,可以把它的值视为两人钱数和 \(+1\),这代表 E 出价直接把 C 压死,来强制获得这盒糖。
这样就有了一个暴力 dp。注意到这个 dp 第二维的定义域很大,但是值域却很小,而且还是单调的,因此可以维护每一个 dp 值对应的定义域区间(或者右端点)。然后就做完了。
代码
#include <iostream>
#include <algorithm>
#include <string.h>
#include <cassert>
#define int long long
using namespace std;
const int inf = 0x3f3f3f3f3f3f3f3f;
int g[105][100005];
int a[100005];
pair<int, int> f[100005];
struct node {
int x, t, b;
} tmp[100005];
int sz, tsz;
int n, x, y, s;
signed main() {
cin >> n >> x >> y;
for (int i = 1; i <= n; i++) cin >> a[i], s += a[i];
sort(a + 1, a + n + 1);
f[++sz] = make_pair(0, 0), f[++sz] = make_pair(x + y + 1, s);
for (int i = 1; i <= n; i++) {
tsz = 0;
for (int j = 1; j <= sz; j++) {
tmp[++tsz] = (node) { f[j].second, 1, f[j + 1].first };
if (f[j].second + a[i] <= s)
tmp[++tsz] = (node) { f[j].second + a[i], 0, f[j + 1].first };
}
sort(tmp + 1, tmp + tsz + 1, [](node a, node b) { return a.x < b.x; });
int dp0 = 0, dp1 = 0;
sz = 0;
for (int j = 1; j <= tsz; j++) {
if (tmp[j].x == tmp[j + 1].x) {
if (!sz || (f[sz].first) != (dp0 + dp1) / 2)
f[++sz] = make_pair((dp0 + dp1) / 2, tmp[j].x);
else
f[sz].second = tmp[j].x;
tmp[j].t ? (dp1 = tmp[j].b, dp0 = tmp[j + 1].b) : (dp1 = tmp[j + 1].b, dp0 = tmp[j].b);
++j;
} else {
if (!sz || (f[sz].first) != (dp0 + dp1) / 2)
f[++sz] = make_pair((dp0 + dp1) / 2, tmp[j].x);
else
f[sz].second = tmp[j].x;
tmp[j].t ? (dp1 = tmp[j].b) : (dp0 = tmp[j].b);
}
}
}
for (int i = sz; i; i--) {
if (f[i].first <= x) {
cout << f[i].second << "\n";
break;
}
}
// g[0][0] = 0;
// for (int i = 1; i <= s; i++) g[0][i] = x + y + 1;
// for (int i = 1; i <= n; i++) {
// cout << i << "\n";
// for (int j = 0; j <= s; j++) {
// g[i][j] = (g[i - 1][max(0ll, j - a[i])] + g[i - 1][j]) / 2;
// cout << j << " " << g[i][j] << "\n";
// }
// }
// for (int i = s; ~i; i--) {
// if (g[n][i] <= x) {
// cout << i << "\n";
// break;
// }
// }
return 0;
}
T5
GCD VS XOR
输出 \(x - lowbit(x)\) 即可。是二的整次幂则无解。
代码
#include <iostream>
#define int long long
using namespace std;
signed main() {
int tc;
cin >> tc;
while (tc--) {
int x;
cin >> x;
if (x == (x & (-x)))
cout << "-1\n";
else {
cout << (x - (x & (-(x))));
cout << "\n";
}
}
return 0;
}
T8
Instructions Substring
只需要求出一个位置往后第一次走到目标位置是在哪一次操作之后。设是第 \(x\) 次,则答案加上 \(n - x + 1\)。然后做前缀和一样的东西,拿 map 记一下执行完每个后缀的操作之后的位置即可。
T9
Red Playing Cards
最终取出的所有区间必然是要么不交要么包含。如果不考虑包含的情况就只要做一个简单 dp。考虑包含的情况就是先对每个区间求出其内部选区间的最大收益,然后再做刚才那个 dp。递归做即可。
代码
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=3005;
int n;
int a[N<<1];
int dp[N];
int v[N][N],qu[N][N<<1];
void solve(int l,int r,int val){
int &res=dp[val];
if(~res) return;
if(l==r) return res=val*2,void();
for(int i=l+1;i<r;i++){
qu[val][i]=qu[val][i-1]+val;
if(!v[val][a[i]]) v[val][a[i]]=i;
else{
solve(v[val][a[i]],i,a[i]);
if(a[i]<val) continue;
qu[val][i]=max(qu[val][i],qu[val][v[val][a[i]]-1]+dp[a[i]]);
}
}
res=qu[val][r-1]+2*val;
return;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
cin>>n;
for(int i=1;i<=2*n;i++) cin>>a[i];
memset(dp,-1,sizeof dp);
solve(0,2*n+1,0);
cout<<dp[0]<<endl;
return 0;
}

浙公网安备 33010602011771号