[ZJOI2010]基站选址 题解及思考

本文介绍了ZJOI2010基站选址问题,详细阐述了暴力解法和线段树优化的思路。线段树优化将状态转移方程简化,并利用线段树在O(n log n)时间内解决,避免了暴力方法的高复杂度。同时提醒在编程中避免使用#define定义min,以免引发不必要的递归。
摘要由CSDN通过智能技术生成

ZJOI2010]基站选址

NOI/NOI+/CTSC 胆小勿入

题目友情链接-lougu
题目友情连接-宏帆oj

一、题目及数据范围

有N个村庄坐落在一条直线上,第i(i>1)个村庄距离第1个村庄的距离为Di。需要在这些村庄中建立不超过K个通讯基站,在第i个村庄建立基站的费用为Ci。如果在距离第i个村庄不超过Si的范围内建立了一个通讯基站,那么就村庄被基站覆盖了。如果第i个村庄没有被覆盖,则需要向他们补偿,费用为Wi。现在的问题是,选择基站的位置,使得总费用最小。

40%的数据中,N<=500;
100%的数据中,K<=N,K<=100,N<=20,000,Di<=1000000000,Ci<=10000,Si<=1000000000,Wi<=10000。

二、暴力做法

这题一看就是dp(暴力也得不了分),设计dp[i][j]为前i个点均被覆盖,建了j个基站,且最后一个基站被建立在i点的最小花费
dp[i][j]=min(dp[i][j],dp[k][j-1]+cost+c[i]),cost为k到i中没被覆盖的点的w之和。

现在的难点在于求cost。我们可以让k从i-1递减,定义l[x]和r[x]为k点能被覆盖区间的左端点和右端点,则当r[x]<i时,说明它不能被i点覆盖,只能被在l[x]到x的范围被覆盖(我们此时的k在x点),所以小于l[x]-1的k点要额外支付w[x]的费用,我们可以O(n)的时间处理每个点的cost信息,时间复杂度O(n^2*k),得分50,附上代码。

#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
#define INF 2e9+5
#define min(x,y) ((x)<(y)?(x):(y))
const int MAXN = 20005, MAXK = 105;
int n,k,d[MAXN],c[MAXN],s[MAXN],w[MAXN],l[MAXN],r[MAXN];
int dp[MAXN][MAXK],cost[MAXN],ans;
int main()
{
   
    scanf("%d %d",&n,&k);
    for(int i=2; i<=n; i++)
        scanf("%d",&d[i]);
    for(int i=1; i<=n; i++)
        scanf("%d",&c[i]);
    for(int i=1; i<=n; i++)
        scanf("%d",&s[i]);
    for(int i=1; i<=n; i++)
        scanf("%d",&w[i]);
    for(int i=1; i<=n; i++)
    {
   
        l[i]=lower_bound(d+1,d+1+n,d[i]-s[i])-d;
        r[i]=lower_bound(d+1,d+1+n,d[i]+s[i])-d;
        r[i]-=(d[i]+s[i]<d[r[i]]);
    }
    for(int i=1; i<=n; i++)
    {
   
        for(int j=1; j<=k; j++)
            dp[i][j]=INF;
        dp[i][0]=dp[i-1][0]+w[i];
    }
    ans=dp[n][0];
    for(int i=1; i<=n; i++)
    {
   
        memset(cost,0,sizeof cost);
        for(int j=i-1; j>=1; j--) //这里的j就是上文的k,遍历所有k处理出cost
        {
   
            if(r[j]<i)
                cost[l[j]-1]+=w[j];
        }
        for(int j=i-1; j>=0; j--)
            cost[j]+=cost[j+1];//用后缀和统计w和
        for(int j=1; j<=k && j<=i; j++) //基站数量
        {
   
            if(j==1)
                dp[i][1]=cost[0]+c[i];
            else
                for(int p=i-1; p>=j-1; p--) //枚举k点
                    dp[i][j]=min(dp[i][j],dp[p][j-1]+cost[p]+c[i]);
            
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值