标签存档: 动态规划 - 第2页

[VIJOS 1014]旅行商简化版

旅行商简化版

【背景 Background】

欧几里德旅行商(Euclidean Traveling Salesman)问题也就是货郎担问题一直是困扰全世界数学家、计算机学家的著名问题。现有的算法都没有办法在确定型机器上在多项式时间内求出最优解,但是有办法在多项式时间内求出一个较优解。

为了简化问题,而且保证能在多项式时间内求出最优解,J.L.Bentley提出了一种叫做bitonic tour的哈密尔顿环游。它的要求是任意两点(a,b)之间的相互到达的代价dist(a,b)=dist(b,a)且任意两点之间可以相互到达,并且环游的路线只能是从最西端单向到最东端,再单项返回最西端,并且是一个哈密尔顿回路。

【描述 Description】

著名的NPC难题的简化版本

现在笛卡尔平面上有n(n<=1000)个点,每个点的坐标为(x,y)(-2^31<x,y<2^31,且为整数),任意两点之间相互到达的代价为这两点的欧几里德距离,现要你编程求出最短bitonic tour。

【输入格式 Input Format】

第一行一个整数n

接下来n行,每行两个整数x,y,表示某个点的坐标。

输入中保证没有重复的两点,保证最西端和最东端都只有一个点。

【输出格式 Output Format】

一行,即最短回路的长度,保留2位小数。

【样例输入 Sample Input】

7
0 6
1 0
2 3
5 4
6 1
7 5
8 2

【样例输出 Sample Output】

25.58

【时间限制 Time Limitation】

1s

【来源 Source】

《算法导论(第二版)》 15-1

【题解 Solution】

基本DP。

这题需要补充一个条件:任何两点的x坐标都不相等。

首先按照x坐标对所有点进行排序,并从西到东依次编号。这样我们就可以进行DP了。阶段可以按照从西到东的各个点来划分。

如图,我们对E点代表的阶段进行决策,有两种决策:E连B或E连D。这样,类似B、D的两个点就可以表示一种状态。

我们用dp[i][j]表示一种状态,表示在这两个点代表的状态之后,还能得到的最小长度。则有

dp[i][j]=dp[j][i]=min{dp[i][k]+dist[j][k],dp[k][j]+dist[i][k]}

(k=max(i,j)+1)

dp[0][0]就是所求的解。边界也很好处理。时间复杂度为O(N^2)。

源码:

#include <iostream>
#include <iomanip>
#include <cstdlib>
#include <cmath>
using namespace std;
double dp[1000][1000],dist[1000][1000];
int N;
struct Dot{	long x,y;}dots[1000];
int cmp(const void *a, const void *b){
	return ((Dot*)a)->x-((Dot*)b)->x;
}
double memo(int a, int b){
	if(dp[a][b]>0)	return dp[a][b];
	if(a==N-1)	return dist[N-1][b];
	if(b==N-1)	return dist[N-1][a];
	int c=max(a,b)+1;
	return dp[b][a]=dp[a][b]=min(memo(a,c)+dist[b][c],memo(c,b)+dist[a][c]);
}
int main(){
	cin >> N;
	for(int i=0;i<N;i++)
		cin >> dots[i].x >> dots[i].y;
	qsort( dots, N, sizeof(dots[0]), cmp );
	for(int i=0;i<N;i++)
		for(int j=i+1;j<N;j++){
			double x=dots[i].x-dots[j].x,y=dots[i].y-dots[j].y;
			dist[i][j]=dist[j][i]=sqrt(x*x+y*y);
		}
	cout << fixed << setprecision(2) <<memo(0,0) << endl;
	return 0;
}

[01ACM NEEuropean][URAL1183]Brackets sequence

Brackets sequence

Time Limit: 1.0 second

Memory Limit: 16 MB

Let us define a regular brackets sequence in the following way:

  1. Empty sequence is a regular sequence.
  2. If S is a regular sequence, then (S) and [S] are both regular sequences.
  3. If A and B are regular sequences, then AB is a regular sequence.

For example, all of the following sequences of characters are regular brackets sequences:

(), [], (()), ([]), ()[], ()[()]

And all of the following character sequences are not:

(, [, ), )(, ([)], ([(]

Some sequence of characters ‘(‘, ‘)’, ‘[', and ']‘ is given. You are to find the shortest possible regular brackets sequence, that contains the given character sequence as a subsequence. Here, a string a1a2…an is called a subsequence of the string b1b2…bm, if there exist such indices 1 ≤ i1 < i2 < … < in ≤ m, that aj=bij for all 1 ≤ j ≤ n.

Input

The input file contains at most 100 brackets (characters ‘(‘, ‘)’, ‘[' and ']‘) that are situated on a single line without any other characters among them.

Output

Write to the output file a single line that contains some regular brackets sequence that has the minimal possible length and contains the given sequence as a subsequence.

Sample

Input Ouput
([(] ()[()]

Problem Author: Andrew Stankevich
Problem Source: 2001-2002 ACM Northeastern European Regional Programming Contest

Solution

终于过了这题……WA了两次。第一次是判断括号匹配的栈写错了,WA#9;第二次是没有考虑输入文件是空串的情况,WA#13并持续一周 -_-|||

而且这次也写垃圾了,长达80多行。好像原来的ural的内存限制是1000K?这样说我开一个100*100的string数组真是件奢侈的事情呢……

#include <iostream>
#include <fstream>
#include <string>
#include <cassert>
using namespace std;
ifstream fin("bracket.in");
ofstream fout("bracket.out");
string S;
inline bool isregular(int i,int j)	//判断原串中的子串S[i...j]是否规则
{
	if(j<i)	return true;
	else if(j==i)	return false;
	char stack[100];	int p=0;
	for(int k=i;k<=j;k++)
	{
		if(p==0)
		{
			if(S[k]=='('||S[k]=='[')	stack[p++]=S[k];
			else return false;
		}
		else{
			if(( stack[p-1]=='('&&S[k]==')' )||( stack[p-1]=='['&&S[k]==']' ))	p--;
			else if(S[k]==')'||S[k]==']')	return false;
			else	stack[p++]=S[k];
		}
	}
	return (p==0);
}
string s[100][100];	//[i][j]:按照最少原则添加括号规则化S[i...j]后得到的串
string memo(int i, int j)	//记忆化搜索
{
	if(s[i][j]!="")	return s[i][j];
	if(isregular(i,j))	//若S[i...j]本身就已经是规则的
	{
		s[i][j].assign(S,i,j-i+1);
		return s[i][j];
	}
	if(i==j)	//若当前串仅由一个字符组成
	{
		if(S[i]=='('||S[i]==')')	s[i][j]="()";
		else if(S[i]=='['||S[i]==']')	s[i][j]="[]";
		else assert(0);
		return s[i][j];
	}
	string ans,tmp,tmp2;
	unsigned size=UINT_MAX;
	if(( S[i]=='('&&S[j]==')' )||( S[i]=='['&&S[j]==']' ))	//若S[i...j]首尾配对,则只需使S[i+1...j-1]规则就得到一个解
	{
		ans=S[i]+memo(i+1,j-1)+S[j];
		size=ans.size();
	}
	else if(( S[i]=='('&&S[j]!=')' )||( S[i]=='['&&S[j]!=']' ))	//S[i...j]首尾不配对,与以上类似
	{
		ans=S[i]+memo(i+1,j);
		if(S[i]=='(')	ans=ans+')';
		else if(S[i]=='[')	ans=ans+']';
		else assert(0);
		size=ans.size();
	}
	else if(( S[i]!='('&&S[j]==')' )||( S[i]!='['&&S[j]==']' ))
	{
		ans=memo(i,j-1)+S[j];
		if(S[j]==')')	ans='('+ans;
		else if(S[j]==']')	ans='['+ans;
		else assert(0);
		size=ans.size();
	}
	for(int k=i;k<j;k++)	//规则化S[i...k]和S[k+1...j]后合并得到的串
		if(size>memo(i,k).size()+memo(k+1,j).size())
		{
			ans=memo(i,k)+memo(k+1,j);
			size=memo(i,k).size()+memo(k+1,j).size();
		}
	return (s[i][j]=ans);
}
int main()
{
	fin >> S;
	if(S.size()==0)
	{
		fout << endl;
		return 0;
	}
	fout << memo(0,S.size()-1) << endl;
	return 0;
}

[IOI2000国家队原创题] 艺术馆的火灾

艺术馆的火灾

Fire of the Art Gallery

IOI2000 National Training Team Originals

Author: 张辰

背景描述

这幢古老的建筑是一个艺术馆,它珍藏着上百件绘画、雕塑以及其他艺术品,就连建筑本身也是一件艺术。但是,岁月并不在乎它的精致与美丽,时光在渐渐剥夺着这幢木屋的生命。终于,在一个月色昏暗的夜晚,它着火了。

艺术馆是一幢两层的小楼,每一层有N个房间,每个房间中收藏的艺术品的价值都可以用一个正整数来表示。下面是一个N=4的例子。

在这个例子中,二层楼的第四个房间中艺术品的价值最大,为60。而一层楼的第四个房间中艺术品的价值仅为20。

在消防队员火速赶到的时候,火势已经蔓延了整个建筑,消防队员们观察每个房间中的火势,将它们分别用一个正整数来表示。在上面的例子中,各房间中的火势可能如下。

你可以看到,二层楼的第四个房间中火势最强,为70。而一层楼的第三个房间中火势较弱,为20。

由于火情紧急,消防队员们准备使用一种新型的灭火器。这种灭火器只能发射K次,每次发射将覆盖一个矩形的区域(矩形的高度可以是1也可以是2)。它的威力巨大,所到之处不但火焰会被扑灭,就连同在一处的艺术品也难以幸免。因此,如何善用这种灭火器成了最大的问题。

一个例子:如果灭火器的一次发射覆盖了下图阴影所示的2×2矩形区域,那么这四个房间的火势和艺术品价值都将成为0。

任务说明

给出艺术馆每层的房间数N和灭火器的发射次数K,以及每个房间中艺术品的价值和火势,你需要决定灭火器每次应该怎样发射(也可以不发射),才能将这次火灾的损失降到最低限度。这个损失用你所摧毁的艺术品的总价值,加上剩余的火势总值(这些火焰将需要消防队员们亲身去扑灭)来衡量。

输入数据

输入文件的第一行有两个整数N(1 <= N <= 100)、K(1 <= K <= 10),分别表示艺术馆中每层的房间数和灭火器的发射次数。

接下来的两行每行有N个整数,其中第4-i行的第j个整数Vi,j表示的是第i层第j个房间中艺术品的价值(1 <= i <= 2,1 <= j <= N,1 <= Vi,j <= 10000)。

再接下来的两行每行也有N个整数,其中第6-i行的第j个整数Fi,j表示的是第i层第j个房间中的火势(1 <= i <= 2,1 <= j <= N,1 <= Fi,j <= 10000)。

输出数据

你的输出文件应该有K行,每行有四个整数L1、R1、L2、R2,表示你的灭火器的一次行动。如果灭火器这次不发射,那么这四个整数都为0;否则这次发射所覆盖的矩形区域的左下角是第L1层的第R1个房间,右上角是第L2层的第R2个房间。

注意:你的每次发射所覆盖的矩形区域必须位于小楼之内,并且矩形的面积至少为0。即1 <= L1 <= L2 <= 2,1 <= R1 <= R2 <= N。

输入输出样例

INPUT.TXT OUTPUT.TXT 样例图示
4 2

40 50 30 60

30 30 40 20

50 40 50 70

40 50 20 30

1 1 1 2

2 3 2 4

摧毁艺术品:30+60+30+30=150

剩余火势:50+40+20+30=140

最小损失:150+140=290

评分标准

每个测试点都有一定的时间限制。你的程序只有在规定的时限内输出解,并且按照你的方案来发射灭火器可以将损失降到最小,你才会得到相应测试点的分值;否则这个测试点不得分。

注意:本题的解不一定是唯一的。即对于一个测试数据,可能有多种发射灭火器的方案都可以使损失达到最小。在这种情况下,你只要输出最优方案中的任意一种即可。

解题报告

自己独立做出来的好有成就感啊……^_^

做这题的过程还相当传奇。话说那天生物课没拿书,直接被老师赶出去到物理办公室门口站着。岂知那里却安静、光线明亮,清风吹拂,透过窗口可以看见墙上大片翠绿的藤蔓植物在风中生起层层波浪,让人心旷神怡,真是个绝佳的自习的所在。当时手里也没书,没事干,就想起了这道本来一看来源就已经准备直接看题解的题目,和老师要了纸笔,想啊想啊就把这题搞出来了。-_-|||

不扯了,下面说说这题的做法(和官方解法不太一样~)。

这题的模型可以看成是一个2*N的矩形表格中每格存有有序数对(V,F),现在要从表格中选出一些格子,要求:

1.(选出格子中的V)+(未选中格子中的F)值最小;

2.这些格子能组成的最少完整矩形个数不大于定值K。

在第2个要求中我们强调“最少”和“完整”的矩形,这样才能得到最优解。因此,我们可以设法求出一种满足条件的格子选择,然后再按照最少原则用贪心算法划分矩形,且规定划分得到的矩形互不相交(因为相交时得到的解一定不优于此)。以样例数据为例,先选出(1,1),(1,2),(2,3),(2,4)四个格子,然后再把它们组合成(1,1,1,2)和(2,3,2,4)两个完整矩形。这已经是这四个格子所能组成的最少矩形数目了。(下文中无特殊说明,提到的“矩形”都指满足“最少”和“完整”要求的。)

由于表格的高已经确定为2这个很小的值,我们可以根据每一列的损失数来划分阶段。我们用(i,j,k)来表示一个状态。其中i表示当前状态为第i列,用于阶段划分;k表示此次决策前已经得到的矩形个数,用以在状态转移时限制矩形的个数;j表示要决策列的前一列中的灭火器使用情况,用于在状态转移时更新矩形的数目(后面我们可以看到当前列的决策是否导致新矩形的产生完全取决于前一列的灭火器使用情况)。

选择不同的递推方向会导致不同的状态定义和转移方程。这里我们选择顺推,因为每次决策都会唯一对应一个状态后继,这样转移方程写起来会简单许多。

我们的状态定义为:dp(i,j,k)表示前i列已经有k个矩形、第i列的状态为j之后还能获得的最小损失。转移方程为:dp(i,j,k)=min{dp(i+1,jj,kk)+loss(i+1,jj)}。其中jj表示决策第i+1行灭火器的使用情况。kk为因此得到的矩形数目,即若决策jj导致了矩形个数的增加,则kk=k+1,否则kk=k。loss(i,j)为第i列灭火器使用情况为j时的损失。

每一列的灭火器使用情况(j的取值)有4种:0.楼上楼下都不使用灭火器;1.楼上使用;2.楼下使用;3.上下都用。如果我们画两列格子看看,就会发现矩形数目不增加当且仅当jj=0或j==jj。这样我们就可以通过转移方程求出解dp(0,0,0)。

——但是等等!


这就是我第一次写时出现的疏忽(这让我第一次WA了4个点并调试了整整一个晚上)。我们没有注意到当j=3,jj=1(或2,这里以1为例)时可能有以下两种情况:

其中前一种如我们所想,jj决策导致了矩形数目的增加,但是后一种情况却不是这样。虽然j=3,jj=1,满足矩形数目增加的条件,但是矩形数目实际并没有增加。这是由于更早些的决策已经使第i行的上下两层分属两个矩形。因此我们需要为上下都使用的决策留出两个j值以示区别。这部分细节请看代码。

这样得到的状态定义和转移方程明显满足最有子结构和无后效性。我们可以据此求出最小损失。

这道题并不要求输出最小损失,而是要求输出灭火器每次使用的覆盖范围。这我们就需要构造最优解。我的方法很朴素。在dp过程中维护一个数组build[i][j][k],其值为状态(i,j,k)做出的最优决策jj。dp结束后,我们就可以递归地在一个bool数组上标记出那些使用了灭火器的格子。最后用贪心(细节见代码)把它们组合成矩形输出,这样问题就解决了。

这个算法思路比较简单,编程复杂度与官方解相当。至于时空复杂度,由于我没有系统学习过相关理论,不好乱说,ms是空间复杂度比官方解高,但是时间复杂度比官方解低?

#include <iostream>
#include <fstream>
#include <cassert>
using namespace std;
ifstream fin("Input.txt");
ofstream fout("Output.txt");
int room_n,shoot_k,v[3][101],f[3][101];

/*dp(memoization)部分*/
int dp[101][5][11];//[i][j][k]:前i列已经最少有k个完整矩形、第i列的状态为j之后还能获得的最小损失
bool checked[101][5][11];
int memo(int i,int j, int k);
int loss(int i, int j);	//memo调用的子过程,返回第i行决策为j时的净损失

/*最优解的构造部分*/
int build[101][5][11];	//记录(i,j,k)状态下要得到最优解,i+1列的状态
bool room[3][101];
void paint(int i, int j, int k);//在room中标出状态(i,j,k)后的灭火器使用情况

/*最优解的输出部分*/
int counter;//记录发射次数
void output(int i);	//输出矩形

int main()
{
	/*输入部分*/
	fin >> room_n >> shoot_k;
	for(int i=2;i>=1;i--)
		for(int j=1;j<=room_n;j++)
			fin >> v[i][j];
	for(int i=2;i>=1;i--)
		for(int j=1;j<=room_n;j++)
			fin >> f[i][j];
	/*计算部分*/
	unsigned min_loss=memo(0,0,0);
	paint(0,0,0);

	/*输出部分*/
	for(int i=1;i<=room_n;i++)	output(i);
	for(int j=shoot_k-counter;j>=1;j--)
		fout << "0 0 0 0\n";
	return 0;
}

//
int memo(int i,int j, int k)
{
	if(checked[i][j][k])	return dp[i][j][k];
	if(i==room_n)	return 0;
	dp[i][j][k]=INT_MAX;
	for(int jj=0,kk,ans;jj<=3;jj++)	//枚举第i+1行状态
	{
		if(jj==3&&(j==4||j==1||j==2))	jj++;	//上下都用时的状态修正
		kk=k+!(jj==0||jj==j||j==4);
		if(kk<=shoot_k)
			ans=memo(i+1,jj,kk)+loss(i+1,jj);
		else ans=INT_MAX;
		if(dp[i][j][k]>ans)
		{
			dp[i][j][k]=ans;
			build[i][j][k]=jj;
		}
	}
	assert(dp[i][j][k]<INT_MAX);
	checked[i][j][k]=true;
	return dp[i][j][k];
}
int loss(int i, int j)
{
/*状态j的定义:
	0:上层下层都不使用灭火器;
	1:上使用;2:下使用;3,4:上下都用
	其中3表示与此列相连的使用灭火器的房间最少组成1个完整矩形
	4表示与此列相连的使用灭火器的房间已经最少组成2个完整矩形*/
	switch(j)
	{
		case 0:
			return (f[1][i]+f[2][i]);	//上下都不用
		case 1:
			return(v[2][i]+f[1][i]);	//上用
		case 2:
			return(v[1][i]+f[2][i]);	//下用
		case 3:	//上下都用
		case 4:
			return(v[1][i]+v[2][i]);
	}
	assert(0);
}
//
void paint(int i, int j, int k)
{
	if(i==room_n)	return;
	assert(checked[i][j][k]);
	int jj=build[i][j][k];
	int kk=k+!(jj==0||jj==j||j==4);
	switch(jj)
	{
		case 0:
			break;	//上下都不用
		case 1:
			room[2][i+1]=1; break;	//上用
		case 2:
			room[1][i+1]=1; break;	//下用
		case 3:
		case 4:
			room[1][i+1]=room[2][i+1]=true; break;	//上下都用
		default:
			assert(0);
	}
	paint(i+1,jj,kk);
}
//
void output(int i)
{
	counter++;
	int ii;
	if(room[2][i]&&!room[1][i])	//上用
	{
		fout << 2 << ' ' << i << ' ';
		for(ii=i;ii<=room_n&&room[2][ii];ii++)	room[2][ii]=0;
		fout << 2 << ' ' << ii-1 << endl;
	}
	else if(room[1][i]&&!room[2][i])	//下用
	{
		fout << 1 << ' ' << i << ' ';
		for(ii=i;ii<=room_n&&room[1][ii];ii++)	room[1][ii]=0;
		fout << 1 << ' ' << ii-1 << endl;
	}
	else if(room[1][i]&&room[2][i])	//上下都用
	{
		fout << 1 << ' ' << i << ' ';
		for(ii=i;ii<=room_n&&room[1][ii]&&room[2][ii];ii++)	room[1][ii]=room[2][ii]=0;
		fout << 2 << ' ' << ii-1 << endl;
	}
	else counter--;	//上下都不用
}
//

[NOI97]积木游戏(Game)

积木游戏(NOI’97)

SERCOI 最近设计了一种积木游戏。每个游戏者有N块编号依次为1 ,2,…,N的长方体积木。对于每块积木,它的三条不同的边分别称为”a边”、“b边”和”c边”,如下图所示:

游戏规则如下:

  1. 从N块积木中选出若干块,并将它们分成M(l<=M<=N)堆,称为第1堆,第2 堆…,第M堆。每堆至少有1块积木,并且第K堆中任意一块积木的编号要大于第K+1堆中任意一块积木的编号(2<=K<=M)。
  2. 对于每一堆积木,游戏者要将它们垂直摞成一根柱子,并要求满足下面两个条件:
    • 除最顶上的一块积木外,任意一块积木的上表面同且仅同另一块积木的下表面接触,并且要求下面的积木的上表面能包含上面的积木的下表面,也就是说,要求下面的积木的上表面的两对边的长度分别大于等于上面的积木的两对边的长度。
    • 对于任意两块上下表面相接触的积木,下面的积木的编号要小于上面的积木的编号。

最后,根据每人所摞成的M根柱子的高度之和来决出胜负。

请你编一程序,寻找一种摞积木的方案,使得你所摞成的M根柱子的高度之和最大。

输入输出

输入文件是INPUT.TXT。文件的第一行有两个正整数N和M(1<=M<=N<=100),分别表示积木总数和要求摞成的柱子数。这两个数之间用一个空格符隔开。接下来N行依次是编号从1到N的N个积木的尺寸,每行有三个1至1000之间的整数,分别表示该积木a边,b边和c边的长度。同一行相邻两个数之间用一个空格符隔开。

输出文件是OUTPUT.TXT。文件只有一行,为一个整数,表示M根柱子的高度之和。

样例

INPUT.TXT

4 2

10 5 5

8 7 7

2 2 2

6 6 6

OUTPUT.TXT

24

题解

好郁闷,这题我竟然连写带调花了将近一天。。。看来我的基本功的确非常不够。

这题对我的启发还是非常大的。lrj书中讨论了递推方向的选择问题,这是我不曾考虑过的。我以前所有的dp题目都是逆推的,状态表示都是“到此情况下为止能获得的最大/最小值”,我觉得这样表示非常自然。但这题行不通。每一个状态都有很多前趋状态,转移方程写起来非常困难。若顺推则容易得多,因为顺推的情况下每一个状态只对应一个后继。但是顺推的思想让我非常不适应(可见我非常菜)。“此情况以后能获得的最值”,这种表示我仔细体会了很长时间才理解并适应,感觉受益匪浅。

当一个问题满足dp所需的一切但是方程写起来非常麻烦,不妨换个递推方向试试吧。

代码(若看不到请设法绕过gfw……orz):

#include <fstream>
using namespace std;
ifstream fin("Input.txt");
ofstream fout("Output.txt");
int N,M;
struct demension{
	unsigned a,b,c;
}demen[101];

int dp[101][101][101][3];
inline int h(int a, int k)
{
	//memo调用的子过程,返回积木a第k面朝上时的高度
	assert(a>0&&a<=N);
	switch(k)
	{
		case 0:	return demen[a].c;
		case 1:	return demen[a].b;
		case 2:	return demen[a].a;
	}
	assert(0);
}
inline bool canput(int a, int k, int aa, int kk)
{
	//memo调用的子过程,返回a的k面能否容纳aa的kk面
	int i,j,ii,jj;
	switch(k)
	{
		case 0:
			i=demen[a].a;
			j=demen[a].b;
			break;
		case 1:
			i=demen[a].a;
			j=demen[a].c;
			break;
		case 2:
			i=demen[a].b;
			j=demen[a].c;
			break;
		default:
			assert(0);
	}
	if(i<j)	swap(i,j);
	switch(kk)
	{
		case 0:
			ii=demen[aa].a;
			jj=demen[aa].b;
			break;
		case 1:
			ii=demen[aa].a;
			jj=demen[aa].c;
			break;
		case 2:
			ii=demen[aa].b;
			jj=demen[aa].c;
			break;
		default:
			assert(0);
	}
	if(ii<jj)	swap(ii,jj);
	return (i>=ii&&j>=jj);
	//这里的题意我开始理解错了,不需要j>=ii。这个错误让我花了一下午调试
}
int memo(int i, int a, int b, int k)
{
	/*已经用前a块积木摆成了i根柱子,顶面积木b的的面k朝上
	之后还能获得的最大高度(决策是否使用a+1块积木、如何使用)*/
	if(dp[i][a][b][k]!=0)	return dp[i][a][b][k];
	if(a==N)	//边界条件
		if(i==M)	return 0;
		else	return INT_MIN;
	int ans=memo(i,a+1,b,k);	//不使用第a+1块积木
	if(i<M)
		for(int kk=0;kk<=2;kk++)
			if(ans<memo(i+1,a+1,a+1,kk)+h(a+1,kk))
				ans=memo(i+1,a+1,a+1,kk)+h(a+1,kk);	//新起一堆
	if(i>0)//这个条件的疏忽让我调试了一早上
		for(int kk=0;kk<=2;kk++)
			if(canput(b,k,a+1,kk)&&ans<memo(i,a+1,a+1,kk)+h(a+1,kk))
				ans=memo(i,a+1,a+1,kk)+h(a+1,kk);	//放在前一堆上
	dp[i][a][b][k]=ans;
	return ans;
}

int main()
{
	fin >> N >> M;
	for(int i=1;i<=N;i++)
		fin >> demen[i].a >> demen[i].b >> demen[i].c;
	demen[0].a=demen[0].b=demen[0].c=1001;
	fout << memo(0,0,0,0) << endl;
	return 0;
}

[NOI99]棋盘分割(Chessboard Division)

棋盘分割(NOI99)
Chessboard Division
Chess.{pas|bas|c}
Chess.exe

将一个8×8的棋盘进行如下分割:将原棋盘割下一块矩形棋盘并使剩下部分也是矩形,再将剩下的部分继续如此分割,这样割了(n-1)次后,连同最后剩下的矩形棋盘共有n块矩形棋盘。(每次切割都只能沿着棋盘格子的边进行)

原棋盘上每一格有一个分值,一块矩形棋盘的总分为其所含各格分值之和。现在需要把棋盘按上述规则分割成n块矩形棋盘,并使各矩形棋盘总分的均方差最小。

请编程对给出的棋盘及n,求出均方差σ的最小值。

输入
第1行为一个整数n(1
第2行至第9行每行为8个小于100的非负整数,表示棋盘上相应格子的分值。每行相邻两数之间用一个空格分隔。

输出
仅一个数,为σ(四舍五入精确到小数点后三位)。

样例输入
3
1 1 1 1 1 1 1 3
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 1
1 1 1 1 1 1 1 0
1 1 1 1 1 1 0 3

样例输出
1.633

题解

现在NOI的题做起来还是很费事……那个sigma的式子的化简则完全是参照lrj的牛书完成的,好失败……

中间调试了相当一段时间,都是由于些弱智错误……囧

#include <fstream>
#include <cassert>
#include <cmath>
#include <iomanip>
using namespace std;
ifstream fin("chess.in");
ofstream fout("chess.out");
int N;

unsigned score[8][8][8][8];
bool scored[8][8][8][8];
unsigned get_score(int x1, int y1, int x2, int y2)
{
   assert(x1<=x2&&y1<=y2);
   if(scored[x1][y1][x2][y2])   return score[x1][y1][x2][y2];
   scored[x1][y1][x2][y2]=true;
   unsigned sum=0;
   for(int x=x1;x<=x2;x++)
      for(int y=y1;y<=y2;y++)
         sum+=score[x][y][x][y];
   score[x1][y1][x2][y2]=sum;
   return sum;
}

unsigned dp[8][8][8][8][15];
bool checked[8][8][8][8][15];
unsigned memo(int x1, int y1, int x2, int y2, int n)
{
   assert(x1<=x2&&y1<=y2);
   if(checked[x1][y1][x2][y2][n])
      return dp[x1][y1][x2][y2][n];
   checked[x1][y1][x2][y2][n]=true;
   if(n==1)
   {
      dp[x1][y1][x2][y2][n]
                =get_score(x1,y1,x2,y2)*get_score(x1,y1,x2,y2);
      return dp[x1][y1][x2][y2][n];
   }

   unsigned ans=INT_MAX;
   for(int x=x1,m;x<x2;x++)
   {
      m=memo(x+1,y1,x2,y2,n-1);
      if(m<INT_MAX)
         ans=min(ans,get_score(x1,y1,x,y2)*get_score(x1,y1,x,y2)
            +memo(x+1,y1,x2,y2,n-1));
      m=memo(x1,y1,x,y2,n-1);
      if(m<INT_MAX)
         ans=min(ans,memo(x1,y1,x,y2,n-1)
            +get_score(x+1,y1,x2,y2)*get_score(x+1,y1,x2,y2));
   }
   for(int y=y1,m;y<y2;y++)
   {
      m=memo(x1,y+1,x2,y2,n-1);
      if(m<UINT_MAX)
         ans=min(ans,get_score(x1,y1,x2,y)*get_score(x1,y1,x2,y)
            +memo(x1,y+1,x2,y2,n-1));
      m=memo(x1,y1,x2,y,n-1);
      if(m<UINT_MAX)
         ans=min(ans,memo(x1,y1,x2,y,n-1)
            +get_score(x1,y+1,x2,y2)*get_score(x1,y+1,x2,y2));
   }
   dp[x1][y1][x2][y2][n]=ans;
   return ans;
}

int main()
{
   fin >> N;
   for(int y=0;y<=7;y++)
      for(int x=0;x<=7;x++)
      {
         fin >> score[x][y][x][y];
         scored[x][y][x][y]=true;
      }
   double x_=double(get_score(0,0,7,7))/N;
   fout << fixed << setprecision(3)
      << sqrt(double(memo(0,0,7,7,N))/N-x_*x_) << endl;
   return 0;
}

标签云

豆瓣