UVA12991 Game Rooms ——二阶前缀和(阶梯和)

本文介绍了一种算法,用于计算在一座大楼中,根据各层人数分布和设施设置(乒乓球桌和游泳池),从一层到另一层的最短距离总和。通过动态规划求解,探讨了如何利用二阶前缀后缀和优化计算。实例解析了计算过程和代码实现。
摘要由CSDN通过智能技术生成

输入格式

第一行给出数据组数 TTT( 1≤T≤1001\leq T\leq 1001≤T≤100)。接下来对于每组数据,首先一行给出 NNN( 2≤N≤40002\leq N\leq 40002≤N≤4000),代表该大楼一共有多少层;接下来 NNN 行第 iii 行给出两个整数 Ti,PiT_i, P_iTi​,Pi​( 1≤Ti,Pi≤1091\leq T_i, P_i\leq 10^91≤Ti​,Pi​≤109),代表第 iii 层的人数,意义同题意

输出格式

对于第 xxx 组( xxx 从 111 开始标号)数据的答案 yyy,在第 xxx 行输出 Case #x: y

样例解释

在第一层设置乒乓球桌,在第二层设置游泳池。这样 555 个人要从第一层走到第二层, 444 个人要从第二层走到第一层,距离和为 999

Translated by @Piwry

输入输出样例

输入 #1

1
2
10 5
4 3

输出 #1

Case #1: 9

解析

我们可以将大楼的设置情况看为:从底层到顶层,连续的一段楼层(长度可以为 111)设为乒乓球桌,再连续的一段设为游泳池... 这样循环

对于设置相同的连续的一段楼层中的人(且这是个极大连续段。例如这一段是 [l,r][l, r][l,r] 且设置的都是乒乓球桌,那么位置 l−1l-1l−1 以及 r+1r+1r+1 都必须设置游泳池),如果这一段设置的是乒乓球桌,显然我们就不需要考虑去打乒乓球的人的贡献,因为他们自己所在的楼层就有其想要的设施;设这一段为 [l,r][l, r][l,r],而对于去游泳的人,显然最近的游泳池就在位置 l−1l-1l−1 或 r+1r+1r+1

于是我们设状态 dp(i,0/1)dp(i, 0/1)dp(i,0/1),表示第 iii 层是乒乓桌(000)或游泳池(111),前 iii 层的最小距离和。

转移时,我们只需枚举以 iii 为右端点的极大连续段。则转移方程就为 dp(i,0/1)=dp(j,1/0)+cost(j+1,i,1/0)dp(i, 0/1)=dp(j, 1/0)+cost(j+1, i, 1/0)dp(i,0/1)=dp(j,1/0)+cost(j+1,i,1/0),其中 0≤j<i0\leq j<i0≤j<i,cost(l,r,0/1)cost(l, r, 0/1)cost(l,r,0/1) 代表 [l,r][l, r][l,r] 区间内要去最近的(l−1l-1l−1 或 r+1r+1r+1)乒乓桌(000)或游泳池(111)的距离和

关于计算 costcostcost 函数,首先有一个分界点 (l+r)>>1,在分界点右边的人显然往 r+1r+1r+1 走最近;左边的人同;分界点走左或右都可以。接着这个东西用二阶前缀后缀算一下就行了(O(1)O(1)O(1))

(感觉 costcostcost 函数的计算方式大家都会,不过这里还是讲下X)

我们先来看看我们算的大概是什么东西。考虑 [l,r][l, r][l,r] 区间内的人都往 l−1l-1l−1 走,他们走的距离和:

a[i] 是该层对应的人数)

差不多就是这样一个东西;

而二阶前缀和算的也是这样一个东西

不过我们计算时可能要从二阶前缀和中取一段,就像这样:

(其中 suf 就是后缀的意思,ssuf 就是二阶后缀(后后缀X))

如图中注译所述的,我们可以把它拆成 ssuf[l]-(l+r-1)*suf[r+1]-ssuf[r+1],于是就可以 O(1)O(1)O(1) 计算了

(讲得还是挺简略的,其实画画图就可以理解了)

代码

#include <cstdio>
#include <iostream>
#define ll long long
using std::min;

const int MAXN =4050;

inline int read(){
    int x =0; char c =getchar();
    while(c < '0' || c > '9') c =getchar();
    while(c >= '0' && c <= '9') x = (x<<3) + (x<<1) + (48^c), c =getchar();
    return x;
}

int n;
ll dp[MAXN][2];/*dp 至 i 层,该 i 层是乒乓 0/游泳 1*/
ll pre[MAXN][2], ppre[MAXN][2], suf[MAXN][2], ssuf[MAXN][2];
int a[MAXN][2];

/*新产生的贡献*/
/*[l, r] 都放 typ*/
/*并且要求该区间是一个同色极大区间*/
inline ll cost(int l, int r, bool typ){
    if(l == 1 && r == n)
        return 0x3f3f3f3f3f3f3f3f;
    else if(l == 1)
        return ppre[r][!typ];
    else if(r == n)
        return ssuf[l][!typ];
    else{
        ll ret =0;
        int mid =(l+r)>>1;/*mid 位置的人向上向下贡献都一样*/
        /*下面两行可以画图想想前前缀和和后后缀和的样子 ( 阶梯和 )*/
        ret +=ssuf[l][!typ]-suf[mid+1][!typ]*(mid-l+1)-ssuf[mid+1][!typ];
        ret +=ppre[r][!typ]-pre[mid][!typ]*(r-(mid+1)+1)-ppre[mid][!typ];
        return ret;
    }
}

int main(){
    for(int t =0, T =read(); t < T; ++t){
        n =read();
        for(int i =1; i <= n; ++i)
            a[i][0] =read(), a[i][1] =read();
        for(int i =1; i <= n; ++i){
            pre[i][0] =pre[i-1][0]+a[i][0];
            pre[i][1] =pre[i-1][1]+a[i][1];
        }
        for(int i =1; i <= n; ++i){
            ppre[i][0] =ppre[i-1][0]+pre[i][0];
            ppre[i][1] =ppre[i-1][1]+pre[i][1];
        }
        suf[n+1][0] =ssuf[n+1][0] =suf[n+1][1] =ssuf[n+1][1] =0;
        for(int i =n; i >= 1; --i){
            suf[i][0] =suf[i+1][0]+a[i][0];
            suf[i][1] =suf[i+1][1]+a[i][1];
        }
        for(int i =n; i >= 1; --i){
            ssuf[i][0] =ssuf[i+1][0]+suf[i][0];
            ssuf[i][1] =ssuf[i+1][1]+suf[i][1];
        }
        for(int i =1; i <= n; ++i){
            dp[i][0] =dp[i][1] =0x3f3f3f3f3f3f3f3f;
            for(int j =0; j < i; ++j){
                dp[i][0] =min(dp[i][0], dp[j][1]+cost(j+1, i, 0));
                dp[i][1] =min(dp[i][1], dp[j][0]+cost(j+1, i, 1));
            }
        }
        printf("Case #%d: %lld\n", t+1, min(dp[n][0], dp[n][1]));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值