Codevs1615数据备份

来源:互联网 发布:snmp网络管理框架 编辑:程序博客网 时间:2024/05/30 04:33

http://codevs.cn/problem/1615/

思路:

方案一:O(n*k)的DP…只能得部分分。

首先我们知道,只选择相邻的点构成线段,答案一定不会变差。
因为若选择跨点连线,我们必然可以用其中相邻点构成的连线代替,且使得权值更小。
那么我们就只考虑相邻点构成的线段如何选取。
我们设dp[i][j][1/0]前i条线段选了j条线段是否选择当前线段(包括在j内)
每次用前一条线段的状态更新当前线段选取状态即可。
注意:若我们选择了一条线段,那么这条线段两边的线段都不可选,这个限制条件是我们更新的前提。

代码——DP版本

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>using namespace std;int n,k;int pos[100010],dis[100010];int dp[1010][1010][2];//dp[i][j][1/0]前i条线段选了j条线段是否选择当前线段(包括在j内) int main()            //1--选 0--不选 {    memset(dp,0X3F,sizeof(dp));//取最小值初始化极大值,这里赋0x7f(大小写无所谓)会炸掉     scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++)    scanf("%d",&pos[i]);    sort(pos+1,pos+n+1);     for(int i=1;i<=n-1;i++)    dis[i]=pos[i+1]-pos[i];//只选择相邻的点构成线段答案一定不会变差     /*    for(int i=2;i<=n;i++)    dis[i-1]=pos[i]-pos[i-1];    */    dp[1][1][1]=dis[1];    //不存在dp[1][1][0]的状态     dp[1][0][0]=0;    for(int i=2;i<=n-1;i++)//n-1条线段     {        for(int j=0;j<=min(i-1,k);j++)//枚举状态,选择数至多不会超过当前编号-1         {            dp[i][j][0]=min(dp[i-1][j][1],dp[i-1][j][0]);            //前i条线段选了j条线段不选当前线段的长度由前一状态选或不选前一条线段得到             if(j>=1)//防止负数下标            dp[i][j][1]=dp[i-1][j-1][0]+dis[i];//若选当前线段的长度,前一条线段不可选         }    }    printf("%d",min(dp[n-1][k][0],dp[n-1][k][1]));    return 0;}


方案二:贪心
有点类似于之前一次互测的problem 3
http://blog.csdn.net/qq_36693533/article/details/78358116#t4

基本思路:维护一个小根堆,堆中存所有线段,每次取出权值最小的线段加入答案。
显然有反例。
那么我们就在每次取出权值最小的线段后,将其左右两边线段的权值x1,x2相加再减去当前线段权值y1,并将x1+x2-y1加入堆中。

这样,若x1 + x2 - y1的值比堆中其他元素大,我们就不会选择x1,x2的方案。因为选择x1 + x2的方案一定不如当前选了y1,再在堆中选择权值y2尽量小的一个元素的方案优。即 x1 + x2 - y1 > y2,则x1 + x2 > y1+ y2,选择x1,x2的方案不符合我们权值尽量小的要求。

若x1 + x2 - y1的值比堆中其他元素小,我们就会取出x1 + x2 - y1,并让ans + x1 + x2 - y1,因为ans原本就加过y1,一加一减ans中就只剩下了x1和x2,即选择了x1,x2的方案而不选择y1,y2。因为选择x1 + x2的方案一定比当前选了y1,再在堆中选择权值y2尽量小的一个元素的方案优。即 x1 + x2 - y1 < y2,则x1 + x2 < y1+ y2,选择x1,x2的方案符合我们权值尽量小的要求。

由于单选一条线段会使选择线段数+1,而选择两条线段会先使选择线段数-1,再使其+2,不会存在超过k的情况。

另外 还有一些细节处理:
我们需要建立used数组保证我们在选择一条线段后,不会单独选择其相邻的线段。
每次仅会取出used为0的线段。
那么我们又如何记录x1 + x2 - y1呢?
为了方便处理,我们可以把这个值看作一条线段的值,线段的位置就选y1的位置,used也就相应为0了。
那….还有一个问题。
我们用y1的位置代表x1 + x2 - y1的位置,那如果成功用选择x1,x2代替了选择y1,y2的方案,那么我们怎么标记x1左边,x2右边的线段不可用(used=1)啊?(y1位置不用考虑,因为堆中已经没有y1对应线段了,同理如下)。
链表!
用链表维护线段相邻关系,每次取一条线段(这里的线段也表示x1 + x2 - y1对应线段)后,将其前驱与后继都向后移一位,这样原本y1连接的前驱后继就变成了pre[y1]和nxt[y1],下一次若取到x1,x2,直接将其位置的前驱和后继used设为1即可。

另外,边界部分即编号为0和n的线段的权值要赋极大值,原因如下:
当且仅当我们在之前选择了1号线段,才会有0号和2号线段被同时选择的方案,而这种方案一定是不优的,因为这种情况下2号线段的长度一定比1号线段长,且选择2号线段还会影响3号线段的选择,完全可以用1号线段代替2号线段。所以要把0号线段的权值赋极大值防止这种方案被选。

补充一点:需要注意的是,取到x1,x2的线段后,我们会把pre[x1] - nxt[x2] - x1 - x2 + y1压入堆中,即选择y1,不选x1,x2,选择x1前一条线段,选择x2后一条线段的方案。对答案的影响如之前所述。关于这个操作的实现,还需要在每次取出一条线段后,更新其dis值为前驱dis值+后继dis值-当前dis值,代码实现比较精明请仔细体会。

代码——贪心版本

#include<iostream>#include<cstdio>#include<algorithm>#include<cstring>#include<cmath>#include<queue>using namespace std;int n,k,ans;int pos[1000010],dis[1000010];int pre[1000010],nxt[1000010];bool used[1000010];struct ini{    int A,Z;//位置,长度 };ini u;bool operator < (ini a,ini b){    return a.Z>b.Z;}priority_queue<ini>q;int main(){    scanf("%d%d",&n,&k);    for(int i=1;i<=n;i++)    scanf("%d",&pos[i]);    sort(pos+1,pos+n+1);    for(int i=1;i<=n-1;i++)    {        dis[i]=pos[i+1]-pos[i];        q.push((ini){i,dis[i]});        pre[i]=i-1;        nxt[i]=i+1;    }    dis[0]=dis[n]=1e9;    pre[0]=n+1,nxt[n]=1;    /*    for(int i=2;i<=n;i++)    {        dis[i]=pos[i]-pos[i-1];        q.push((ini){i,dis[i]});        pre[i]=i-1;        nxt[i]=i+1;    }    dis[1]=dis[n+1]=1e9;    pre[1]=n+1,nxt[n+1]=1;    */    for(int i=1;i<=k;i++)    {        ini k=q.top();        q.pop();        while(!q.empty()&&used[k.A])//保证已经选过的线段(或合于一条线段上两条线段)的左右两条线段不会被选        k=q.top(),q.pop();          //并使得其左右线段相加-当前线段的答案可能被选         ans+=k.Z;        q.push((ini){k.A,dis[pre[k.A]]+dis[nxt[k.A]]-dis[k.A]});        dis[k.A]=dis[pre[k.A]]+dis[nxt[k.A]]-dis[k.A];        used[pre[k.A]]=used[nxt[k.A]]=1;        pre[k.A]=pre[pre[k.A]],nxt[k.A]=nxt[nxt[k.A]];        nxt[pre[k.A]]=k.A,pre[nxt[k.A]]=k.A;    }    printf("%d",ans);    return 0;}


我还是画一下图简述算法过程吧:

这里写图片描述

如图
若格子长度蓝色<红色<黑色,那么我们会先取出蓝色,再取出红色部分作为答案,再取出黑色+蓝色部分作为答案。

原创粉丝点击