POJ1160 [IOI2000]邮局 [四边形不等式优化dp]

邮 局 邮局

题目描述见链接 .


正 解 部 分 \color{red}{正解部分}

F [ i , j ] F[i, j] F[i,j] 表示前 i i i 个村庄, 建立 j j j 个邮局的最优值,

状 态 转 移 状态转移 : F [ i , j ] = min ⁡ 0 ≤ k < i ( F [ k , j − 1 ] + w ( k + 1 , i ) ) F[i, j] = \min_{0 \le k < i}(F[k, j-1] + w(k+1, i)) F[i,j]=min0k<i(F[k,j1]+w(k+1,i)),

其中 w ( l , r ) w(l, r) w(l,r) 表示在 [ l , r ] [l,r] [l,r] 放置一个邮局, 管辖 [ l , r ] [l,r] [l,r] 的村庄的最小值, 显然放在 中位数 位置最优,现在考虑如何 O ( 1 ) O(1) O(1) 计算 w ( l , r ) w(l, r) w(l,r),

s u m _ l [ i ] sum\_l[i] sum_l[i] 表示 [ 1 , i ] [1, i] [1,i] 的距离前缀和, s u m _ r [ i ] sum\_r[i] sum_r[i] 表示 [ i , N ] [i, N] [i,N] 的距离后缀和, 可以 O ( N ) O(N) O(N) 预处理 .

m m m [ l , r ] [l, r] [l,r] 的中位数所在村庄编号,

w ( l , r ) = ( m − l ) × A [ m ] − ( s u m _ l [ m − 1 ] − s u m _ l [ l − 1 ] ) + ( s u m _ r [ m + 1 ] − s u m _ r [ r + 1 ] ) − ( r − m ) × A [ m ] w(l, r) = (m-l)\times A[m] - (sum\_l[m-1] - sum\_l[l-1]) + (sum\_r[m+1]-sum\_r[r+1]) - (r-m)\times A[m] w(l,r)=(ml)×A[m](sum_l[m1]sum_l[l1])+(sum_r[m+1]sum_r[r+1])(rm)×A[m]

然后经过 打表 可知, F F F 满足四边形不等式: F [ i + 1 , j + 1 ] + F [ i , j ] ≤ F [ i + 1 , j ] + F [ i , j + 1 ] F[i+1,j+1]+F[i,j] \le F[i+1, j] + F[i, j+1] F[i+1,j+1]+F[i,j]F[i+1,j]+F[i,j+1]

同时与此对应的最优决策 p [ i , j ] p[i,j] p[i,j]满足决策单调性: p [ i , j − 1 ] ≤ p [ i , j ] ≤ p [ i + 1 , j ] p[i,j-1] \le p[i, j] \le p[i+1, j] p[i,j1]p[i,j]p[i+1,j].

于是可以使用 四边形不等式 优化 O ( N 2 K ) → O ( N K ) O(N^2K) \rightarrow O(NK) O(N2K)O(NK) ,


实 现 部 分 \color{red}{实现部分}

先给出 打表/暴力 程序

#include<bits/stdc++.h>
#define reg register
typedef long long ll;

const int maxn = 3005;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

int N;
int K;
int A[maxn];
int p[maxn][maxn];

ll sl[maxn];
ll sr[maxn];
ll sum_l[maxn];
ll sum_r[maxn];
ll F[maxn][maxn];
ll w[maxn][maxn];

int main(){
        freopen("a.in", "r", stdin);
        freopen("a.out", "w", stdout);
        N = read(); K = read();
        for(reg int i = 1; i <= N; i ++) A[i] = read();
        std::sort(A+1, A+N+1);
        for(reg int i = 1; i <= N; i ++) sum_l[i] = sum_l[i-1] + A[i], sl[i] = sum_l[i] + sl[i-1];
        for(reg int i = N; i >= 1; i --) sum_r[i] = sum_r[i+1] + A[i], sr[i] = sum_r[i] + sr[i+1];
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = i; j <= N; j ++){
                        int m = i+j >> 1;
                        w[i][j] = (1ll*m-i)*A[m] - (sum_l[m-1] - sum_l[i-1]) + (sum_r[m+1] - sum_r[j+1]) - (1ll*j-m)*A[m];
                } 
        memset(F, 0x3f, sizeof F); F[0][0] = 0;
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = 1; j <= i; j ++){
                        for(reg int k = 0; k < i; k ++){
                                ll t = F[k][j-1] + w[k+1][i];
                                if(t < F[i][j]){
                                        F[i][j] = t;
                                        p[i][j] = k;
                                }
                        }
                }
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = 1; j <= i; j ++)
                        if(i!=N && j != 1 && (p[i][j-1] > p[i][j] || p[i][j] > p[i+1][j])){
                                printf("False\n");
                        }else if(i != N && j != i && (F[i+1][j+1] + F[i][j] > F[i+1][j] + F[i][j+1])){
                                printf("False\n");
                        }
        printf("%lld\n", F[N][K]);
        return 0;
}

A c c e p t Accept Accept c o d e code code

#include<cstdio>
#include<cctype>
#include<cstring>
#include<algorithm>
#define reg register
typedef long long ll;

const int maxn = 3005;

int read(){
        char c;
        int s = 0, flag = 1;
        while((c=getchar()) && !isdigit(c))
                if(c == '-'){ flag = -1, c = getchar(); break ; }
        while(isdigit(c)) s = s*10 + c-'0', c = getchar();
        return s * flag;
}

int N;
int K;
int A[maxn];
int p[maxn][305];

ll sum_l[maxn];
ll sum_r[maxn];
ll F[maxn][305];
ll w[maxn][maxn];

int main(){
        N = read(); K = read();
        for(reg int i = 1; i <= N; i ++) A[i] = read();
        std::sort(A+1, A+N+1);
        for(reg int i = 1; i <= N; i ++) sum_l[i] = sum_l[i-1] + A[i];
        for(reg int i = N; i >= 1; i --) sum_r[i] = sum_r[i+1] + A[i];
        for(reg int i = 1; i <= N; i ++)
                for(reg int j = i; j <= N; j ++){
                        int m = i+j >> 1;
                        w[i][j] = (1ll*m-i)*A[m] - (sum_l[m-1] - sum_l[i-1]) + (sum_r[m+1] - sum_r[j+1]) - (1ll*j-m)*A[m];
                } 

        memset(F, 0x3f, sizeof F); F[0][0] = 0;

        for(reg int j = 1; j <= K; j ++){
                p[N+1][j] = N-1;
                for(reg int i = N; i >= 1; i --)
                        for(reg int k = p[i][j-1]; k <= p[i+1][j]; k ++){ 
                                ll t = F[k][j-1] + w[k+1][i];
                                if(t < F[i][j]) F[i][j] = t, p[i][j] = k; 
                        }
        }
        printf("%lld\n", F[N][K]);
        return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值