poj 1160 Post Office(dp + wqs二分)

题意

有p个邮局,n个村庄,邮局只能建在村庄里,

求令所有寄信距离之和最短的值。、

SOLUTION:

首先可以n3的dp

dp i ,j  表示前 i 个地方建立 j 个邮局的最小花费,

在处理出在任意一个区间建立一个邮局是的最小花费(一个区间放一个邮局的话,放在中位数的位置最有)

在想如何优化dp

wqs 二分消掉一维

设f(x) = g(x) + kx,每次的设立一个邮局都要增加k的价值,看一下当前f(x)的取到最大时和x和题目要求p的关系

CODE:

#include <iostream>

#include <algorithm>

#include <cstring>

#include <cstdio>

#include <cmath>

#include <set>

#include <map>

#include <vector>

#include <stack>

#include <queue>

#include <functional>

const int INF=0x3f3f3f3f;

const int maxn=300+10;

const int mod=1e9+7;

const int MOD=998244353;

const double eps=1e-7;

typedef long long ll;

#define vi vector<int>

#define si set<int>

#define pii pair<int,int>

#define pi acos(-1.0)

#define pb push_back

#define mp make_pair

#define lowbit(x) (x&(-x))

#define sci(x) scanf("%d",&(x))

#define scll(x) scanf("%lld",&(x))

#define sclf(x) scanf("%lf",&(x))

#define pri(x) printf("%d",(x))

#define rep(i,j,k) for(int i=j;i<=k;++i)

#define per(i,j,k) for(int i=j;i>=k;--i)

#define mem(a,b) memset(a,b,sizeof(a))

using namespace std;

//dp[i][j]代表前i个村庄 需要j个邮局的最小花费

//

int n,p,a[maxn],sum[maxn],dis[maxn][maxn];
int c[maxn];
int dp[maxn];
inline int check(int mid)
{
    for(int i=1;i<=n;i++)
        dp[i]=1e16;
    dp[0]=0;
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
        {
            if(dp[i]>dp[j-1] + dis[j][i] + mid)
            {
                dp[i]=dp[j-1] + dis[j][i] + mid;
                c[i]=c[j-1]+1;
            }
        }
    }
    //for(int i=1;i<=n;i++)cout<<dp[i]<<" ";cout<<endl;
    return c[n];
}
void work()
{
    int l=-1e9,r=1e9;int ans=0;
    while(l<=r)
    {
        int mid=l+r>>1;
        if(check(mid)<=p)ans=mid,r=mid-1;
        else l=mid+1;
      //  cout<<mid<<" "<<check(mid)<<endl;
    }
    check(ans);
    long long t = dp[n] - p*ans;
    cout<<t<<endl;
}
int main()

{

    while(~scanf("%d%d",&n,&p))

    {

        rep(i,1,n)sci(a[i]);

        sort(a+1,a+n+1);

        sum[0]=0;

        rep(i,1,n)sum[i]=sum[i-1]+a[i];

        rep(i,1,n)

        {

            dis[i][i]=0;

            rep(j,i+1,n)

            {

                int mid=(i+j)/2;

                //画图可证明 坐标零点挪动为a[mid]的前缀和 可理解成每个相加 最后提出a[mid]

                //sum[j]-sum[mid]为[mid+1,j]的求和,sum[mid-1]-sum[i-1]为[i,mid-1]的求和

                dis[i][j]=(sum[j]-sum[mid])-(j-mid)*a[mid]+(mid-i)*a[mid]-(sum[mid-1]-sum[i-1]);

            }

        }

        work();


    }

    return 0;

}

  

猜你喜欢

转载自www.cnblogs.com/zhangbuang/p/11236340.html
今日推荐