动态规划(3) 方盒 灌溉草场

方盒游戏 POJ1390
问题描述
N个方盒(box)摆成一排,每个方盒有自己的颜色。连续摆放的同颜色方盒构成
一个方盒片段(box segment)。下图中共有四个方盒片段,每个方盒片段分别有
1、4、3、1个方盒
玩家每次点击一个方盒,则该方盒所在方盒片段就会消失。若消失的方盒片段
中共有k个方盒,则玩家获得k*k个积分。
75
请问:给定游戏开始时的状态,玩家可获得的最高积分是多少?
输入:第一行是一个整数t(1<=t<=15),表示共有多少组测试数据。每组测试
数据包括两行
第一行是一个整数n(1<=n<=200),,表示共有多少个方盒
第二行包括n个整数,表示每个方盒的颜色。这些整数的取值范围是[1 n]
输出:对每组测试数据,分别输出该组测试数据的序号、以及玩家可以获得
的最高积分
样例输入
2
9
1 2 2 2 2 3 3 3 1
1
1
 样例输出
Case 1: 29
Case 2: 1
考虑新的形式:
click_box(i,j,ex_len)
表示:
大块j的右边已经有一个长度为ex_len的大块(该大块可能是在合
并过程中形成的,不妨就称其为ex_len),且j的颜色和ex_len
相同,在此情况下将 i 到j以及ex_len都消除所能得到的最高分

于是整个问题就是求:click_box(0,n-1,0)
求click_box(i,j,ex_len)时,有两种处理方法,取最优者
假设j和ex_len合并后的大块称作 Q
1) 将Q直接消除,这种做法能得到的最高分就是:
click_box(i,j-1,0) + (len[j]+ex_len) 2
2) 期待Q以后能和左边的某个同色大块合并。需要枚举可能和Q
合并的大块。假设让大块k和Q合并,则此时能得到的最大
分数是:
click_box(i,k,len[j]+ex_len) + click_box(k+1,j-1,0)
click_box(i,j,ex_len) 递归的终止条件:
i == j
(算法思想来自郭炜老师)

#include <iostream>
#include <cstring>
#include <stdlib.h>
using namespace std;
struct box
{
    int len;
    int col;
}boxs[205];
int se[205][205][205];
int kucol(int i,int j,int len)
{
    if (se[i][j][len]!= -1)//里面已经改动过的直接返回
        return se[i][j][len];
    int result=(boxs[j].len+len)*(boxs[j].len+len);
    if(i==j)
        return result;//到最后的直接清除算总分
    result=result+kucol(i,j-1,0);//不清除,再算和左边大块合并之后所得分数
    for (int k=i;k<=j-1;k++)
    {//i与j之间的分数
        if (boxs[k].col!=boxs[j].col)
            continue;
        int r=kucol(k+1,j-1,0);//从k+1到j-1之间无大块
        r=r+kucol(i,k,boxs[j].len+len);//算出i到k之间的分数,长度为j的大块+len
        result=max(r,result);
    }
    se[i][j][len]=result;//在i与j之间大块长度len 所得分数
     return result;
}
int main()
{
    int t;
    cin>>t;
    for (int m=1;m<=t;m++)
    {
        int n;
        memset(se,0xff,sizeof(se));
        cin>>n;
        int lco=0;
        int num=-1;
        for (int j=0;j<n;j++)
        {
            int c;
            cin>>c;
            if (c!=lco)
            {//颜色不一样,重成一个大块
                num++;
                boxs[num].len=1;
                boxs[num].col=c;
                lco=c;//改变lco 当前的颜色能与下一个输入的颜色比较
            }
            else
                boxs[num].len++;//大块长度++
        }
        cout<<"Case "<<m<<": "<<kucol(0,num,0)<<endl;
    }
    system("pause");
    return 0;
}

灌溉草场(POJ2373)
在一片草场上:有一条长度为L (1 <= L <= 1,000,000,L为偶数)的线
段。 John的N (1 <= N <= 1000) 头奶牛都沿着草场上这条线段吃草,每头
牛的活动范围是一个开区间(S,E),S,E都是整数。不同奶牛的活动范围可以
有重叠。
John要在这条线段上安装喷水头灌溉草场。每个喷水头的喷洒半径可以随
意调节,调节范围是 [A B ](1 <= A <= B <= 1000),A,B都是整数。要求
线段上的每个整点恰好位于一个喷水头的喷洒范围内
每头奶牛的活动范围要位于一个喷水头的喷洒范围内
任何喷水头的喷洒范围不可越过线段的两端(左端是0,右端是L )
请问, John 最少需要安装多少个喷水头。
91
灌溉草场(POJ2373)
奶牛活动范围
喷水头
在位置2和6,喷水头的喷洒范围不算重叠
92
 输入
第1行:整数N、L。
第2行:整数A、B。
第3到N+2行:每行两个整数S、E (0 <= S < E <= L) ,表示某头牛活动
范围的起点和终点在线段上的坐标(即到线段起点的距离)。
 输出:最少需要安装的多少个喷水头;若没有符合要求的喷水头安装方案
,则输出-1。
 输入样例
2 8
1 2
6 7
3 6
 输出样例
3
从线段的起点向终点安装喷水头,令f(X)表示:所安装喷水头的喷洒范围
恰好覆盖直线上的区间[0 X]时,最少需要多少个喷水头
 显然,X应满足下列条件
 X为偶数
 X所在位置不会出现奶牛,即X不属于任何一个(S,E)
 X2A
 当X2B时,存在Y[X-2B X-2A]且Y满足上述三个条件,使得
f(X)=f(Y)+1
递推计算f(X)
 f(X) = ∝ : X 是奇数
 f(X) = ∝ : X < 2A
 f(X) = ∝ :X处可能有奶牛出没
 f(X)=1: 2AX2B 、且X位于任何奶牛的活动范围之外
 f(X)=1+min{f(Y): Y[X-2B X-2A]、Y位于任何奶牛的活动范围
之外}: X>2B
对每个X求f(X),都要遍历区间 [X-2B, X -2A]去寻找其中最小的
f(Y),则时间复杂度为:L * B = 1000000 * 1000,太慢
 快速找到[X-2B X-2A]中使得f(Y)最小的元素是问题求解速度的关
键 。
可以使用优先队列priority_queue! (multiset也可以,比
priority_queue慢一点)!
 求F(X)时,若坐标属于[X-2B, X-2A]的二元组(i,F(i))都保存在
一个priority_queue中,并根据F(i)值排序,则队头的元素就能
确保是F(i)值最小的。
在求 X点的F(x)时,必须确保队列中包含所有属于 [X-2B,X-2A]的点。
而且,不允许出现坐标大于X-2A的点,因为这样的点对求F(X)无用,如
果这样的点出现在队头,因其对求后续点的F值有用,故不能抛弃之,
于是算法就无法继续了。
 队列中可以出现坐标小于 X-2B 的点。这样的点若出现在队头,则直接将其抛弃。
 求出X点的F值后,将(X-2A+2, F(X-2A+2))放入队列,为求F(X+2)作准备队列里只要存坐标为偶数的点即可

#include <iostream>
#include <cstring>
#include <queue>
#include <stdlib.h>
using namespace std;
const int INF=1<<30;
const int MAXL=1000010;
const int MAXN=1010;
int F[MAXL];//所求喷头数
int cows[MAXL];//cows[i] 为1时表示i点有奶牛
int N,L,A,B;
struct Fx
{
    int f;int x;
    bool operator <(const Fx &a) const
    { return f>a.f;}
    Fx (int xx=0,int ff=0):x(xx),f(ff){ }
};//在优先队列中,>,表示从小到大排序
priority_queue<Fx> qFx;
int main()
{
    cin>>N>>L;
    cin>>A>>B;
    A<<=1;B<<=1;//加=号,A赋值为A*2
    memset(cows,0,sizeof(cows));
    for (int i=0;i<N;i++)
    {
        int s,e;
        cin>>s>>e;
        ++cows[s+1];//从s+1进入奶牛,开区间
        --cows[e];//从e起退出一个奶牛区
    }
    int incows=0;//表示当前点位于多少头奶牛的活动范围之内
    for (int i=0;i<=L;i++)
    {//算出每个点是否有奶牛
        F[i]=INF;
        incows+=cows[i];
        cows[i]=incows>0;//incows是1,存入1,是0存入0
    }//边界条件:f(X)=1: 2A<=X<=2B,且X位于任何奶牛的活动范围之外
    for(int i=A;i<=B;i=i+2)//初始化队列
        if (!cows[i])
        {
            F[i]=1;
             //下面求f(B+2),不允许出现坐标大于X-2A的点
            if(i<=B-A+2)//在求F[i]时,确保队列中x,x<=i-A;
                qFx.push(Fx(i,1));//i直径喷头一个压入
        }
      for (int j=B+2;j<=L;j=j+2)
        {
            if (!cows[j])
            {
                Fx fx;
                while(!qFx.empty())//不为空
                {
                    fx=qFx.top();
                    //小于X-B的点抛弃,对f(X)而言,求f(X)=min(f(Y)) + 1, Y在[Y-B, Y-A]之间
                    if (fx.x<j-B)
                        qFx.pop();
                    else break;
                }
                if(!qFx.empty())
                    F[j]=fx.f+1;
                    //状态转移方程:f(X)=min(f(Y)) + 1
            }
            if (F[j-A+2]!=INF)
            {//下一个点是X=i+2,加入下一个点所需f(Y),Y在[i+2-B, i+2-A]之间
                qFx.push(Fx(j-A+2,F[j-A+2]));
            }
        }
        if (F[L]==INF) cout<<-1<<endl;
        else cout<<F[L]<<endl;
    system("pause");
    return 0;
}

(代码来自北京大学暑期课《ACM/ICPC竞赛训练》
北京大学信息学院 郭炜)

猜你喜欢

转载自blog.csdn.net/qq_40823992/article/details/81632015