方盒游戏 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)
X2A
当X2B时,存在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: 2AX2B 、且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竞赛训练》
北京大学信息学院 郭炜)