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;
}
posted @ 2025-02-09 18:55  forgotmyhandle  阅读(37)  评论(0)    收藏  举报