2023 CPC 广东省赛(B,D)

链接:The 2023 Guangdong Provincial Collegiate Programming Contest

有中文题面,题意就省略了。

B Base Station Construction

思路

本题参考了官方题解。

注意观察题目数据 n , m n, m n,m 的和都不超过 5 e 5 5e5 5e5,那么我们dp就可以从这两方面考虑,这里我们从站点而不从区间来入手。

定义dp方程: f [ i ] : f[i]: f[i]: i i i 个站点,且第 i i i 个站点必须建基站的最小花费。状态转移就是枚举上一个站点建在哪 j j j f [ i ] = min ⁡ j i − 1 f [ j ] + a [ i ] f[i] = \min_j^{i-1}f[j] +a[i] f[i]=minji1f[j]+a[i].

注意一个问题,有些转移是不合法的,当存在区间 [ l , r ] [l, r] [l,r] 使得 l > j , r < i l > j,rl>j,r<i.这样就有区间中没有站点,这是不合法的。 考虑对于每个 i i i 找到 p [ i ] : p[i]: p[i]:最远的合法转移点,用优先队列求解,易知 p [ i ] p[i] p[i] 一定是单调递增的,我们可以将区间按右端点排序,对于所有右端点小于当前点 ( r < i ) (r < i) (r<i) 的区间存入优先队列大顶堆按左端点排序,这样每次从队首取元素判断当前最远点 k k k 是否合法,如果不合法 l > k l > k l>k,就将 k = l k = l k=l.

具体实现见代码:

struct seg{
    int l, r;
    bool operator <(const seg& A){
        if(r == A.r) return l < A.l; 
        return r < A.r;
    }
}s[N];
priority_queue<pair<int, int> > q;
sort(s + 1, s + 1 + m);
while(!q.empty()) q.pop();
for(int i = 1, j = 0, k = 0; i <= n; i ++){
    while(j <= m && s[j].r < i) q.push({s[j].l, s[j].r}), j ++;
    if(!q.empty() && q.top().first > k) k = q.top().first; // 队列中都是r < i 的,若是 l > k 说明两个站点之间没有包含到该区间,这是不合法的,之间让k跳到l
    p[i] = k;
}

值得一提的是,官方题解中是用双指针求解 p [ i ] p[i] p[i] 的,时间更加优秀。

接下来的转移就很好想了,就是对于合法区间 f [ i ] = min ⁡ j = p [ i ] i − 1 f [ j ] + a [ i ] f[i] = \min_{j=p[i]}^{i-1}f[j] + a[i] f[i]=minj=p[i]i1f[j]+a[i]. 求区间最小值,可以用线段树维护,单点修改区间查询,转移时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn)。由于 p [ i ] p[i] p[i] 是单调递增的,可以用单调队列优化转移时间复杂度 O ( n ) O(n) O(n)。这里采用单调队列的方法。

最后的最后可以建立一个虚拟点来方便直接输出 a [ n + 1 ] = 0 a[n + 1] = 0 a[n+1]=0.

代码

/*
n和m之和都小于5e5 所以可以从这两方面来考虑,枚举站点和枚举区间
*/
#include 
using namespace std;

#define ll long long
const int N = 5e5 + 10;
const ll inf = 1e18;

struct seg{
    int l, r;
    bool operator <(const seg& A){
        if(r == A.r) return l < A.l; 
        return r < A.r;
    }
}s[N];

int a[N], n, m;

ll f[N], p[N]; // f:前i个站点 且第i个站点必须建立电站的最小花费(枚举上一个站点j求解) p:要求两个站点之间不能有完整的区间,pi = 最小的满足有要求的站点j
priority_queue<pair<int, int> > q;

ll que[N];
void solve(){
    cin >> n;
    for(int i = 1; i <= n; i ++) cin >> a[i];

    a[++ n] = 0; // 虚拟站点

    cin >> m;
    for(int i = 1; i <= m; i ++){
        cin >> s[i].l >> s[i].r;
    }
    
    sort(s + 1, s + 1 + m);

    while(!q.empty()) q.pop();
    for(int i = 1, j = 0, k = 0; i <= n; i ++){
        while(j <= m && s[j].r < i) q.push({s[j].l, s[j].r}), j ++;
        if(!q.empty() && q.top().first > k) k = q.top().first; // 队列中都是r < i 的,若是 l > k 说明两个站点之间没有包含到该区间,这是不合法的,之间让k跳到l
        p[i] = k;
    }

    int tot = 0, top = 0;
    f[0] = 0;
    for(int i = 1; i <= n; i ++){ // 求的是合法区间中p[i] ~ i - 1的最小值,由于p[i]单调递增可以用单调队列维护
        while(top <= tot && que[top] < p[i]) top ++;
        f[i] = f[que[top]] + a[i];
        while(tot >= top && f[que[tot]] > f[i]) tot --;
        que[++ tot] = i;
    }

    for(int i = 1; i <= n; i ++) que[i] = 0;
    cout << f[n] << "\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int t;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

D New Houses

思路

容易知道,对于不喜欢一起住的人即 a i < b i a_iai<bi 的人,当迫不得已必须一起住的时候,价值就会减少 b i − a i b_i-a_i biai,当出现这种情况的时候,肯定是选减少价值最少的去一起住,这是本题唯一的算法贪心点。

接下来我们就不用思考那么多边界问题,直接大力分类讨论!具体看代码,每行几乎都有详细注释。

代码

#include 
using namespace std;

#define ll long long
const int N = 1e6 + 10;

struct node{
    int a, b;
};

bool cmp(node &A, node &B){
    return A.b - A.a < B.b - B.a;
}
void solve(){
    int n, m;
    cin >> n >> m;
    vector<node> A, B;

    ll sumA = 0, sumB = 0;
    for(int i = 1; i <= n; i ++){
        int a, b;
        cin >> a >> b;
        if(a >= b) A.push_back({a, b}), sumA += a; // 按喜好区分,将价值求和
        else B.push_back({a, b}), sumB += b;
    }

    int sizA = A.size(), sizB = B.size();

    if(sizB == 0){ // 没有喜欢单独住的
        if(sizA == 1) cout << A[0].b << "\n"; // 如果只有一个人,必须单独住
        else cout << sumA << "\n"; // 直接输出全部一起住的价值
        return ;
    }

    sort(B.begin(), B.end(), cmp); // 按如果不能独自住,按损失的最少的排序

    if(sizA == 0){ // 全是喜欢独自住的
        if(m < sizB * 2 - 1){ // 不能全部单独住
            for(auto [a, b] : B){ // 枚举有多少是必须一起住的
                sumB -= (b - a); // 将一起住的减少的值减去
                m --;
                sizB --;
                if(m >= sizB * 2) break; // 满足剩下的人可以单独住结束
            }
        } 
        cout << sumB << "\n";
        return ;
    }

    if(sizA == 1){ // 只有一个喜欢一起住的
        ll ans = sumB;
        m --;
        if(m < sizB * 2){ // 剩下的人不能全部单独住
            ans += A[0].a; // 喜欢一起住的人就能加上价值
            for(auto [a, b] : B){
                ans -= (b - a);
                m --;
                sizB --;
                if(m >= sizB * 2) break;
            }
        }
        else{ // 剩下的人可以全部单独住,分情况讨论
            ans = max(sumB + A[0].b, sumB - (B[0].b - B[0].a) + A[0].a); // 全部单独住,或者有一对一起住
        }
        cout << ans << "\n";
        return ;
    }

    // sizA > 1 && sizB > 0

    // 喜欢一起住的肯定都一起住,剩下的位置让无法独自住,选减少价值最小的一起住
    ll ans = sumA + sumB;
    m -= sizA;
    if(m < sizB * 2){
        for(auto [a, b] : B){
            ans -= (b - a);
            m --;
            sizB --;
            if(m >= sizB * 2) break;
        }
    }
    cout << ans << "\n";
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0); cout.tie(0);

    int t;
    cin >> t;
    while(t --){
        solve();
    }
    return 0;
}

你可能感兴趣的:(XCPC,动态规划,算法,动态规划)