常规DP专题练习

POJ2279 Mr. Young's Picture Permutations

题意

Language:Default
Mr. Young's Picture Permutations
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 2513 Accepted: 960

Description

Mr. Young wishes to take a picture of his class. The students will stand in rows with each row no longer than the row behind it and the left ends of the rows aligned. For instance, 12 students could be arranged in rows (from back to front) of 5, 3, 3 and 1 students.

X X X X X

X X X

X X X

X

In addition, Mr. Young wants the students in each row arranged so that heights decrease from left to right. Also, student heights should decrease from the back to the front. Thinking about it, Mr. Young sees that for the 12-student example, there are at least two ways to arrange the students (with 1 as the tallest etc.):

 1  2  3  4  5     1  5  8 11 12

6 7 8 2 6 9

9 10 11 3 7 10

12 4

Mr. Young wonders how many different arrangements of the students there might be for a given arrangement of rows. He tries counting by hand starting with rows of 3, 2 and 1 and counts 16 arrangements:

123 123 124 124 125 125 126 126 134 134 135 135 136 136 145 146

45 46 35 36 34 36 34 35 25 26 24 26 24 25 26 25

6 5 6 5 6 4 5 4 6 5 6 4 5 4 3 3

Mr. Young sees that counting by hand is not going to be very effective for any reasonable number of students so he asks you to help out by writing a computer program to determine the number of different arrangements of students for a given set of rows.

Input

The input for each problem instance will consist of two lines. The first line gives the number of rows, k, as a decimal integer. The second line contains the lengths of the rows from back to front (n1, n2,..., nk) as decimal integers separated by a single space. The problem set ends with a line with a row count of 0. There will never be more than 5 rows and the total number of students, N, (sum of the row lengths) will be at most 30.

Output

The output for each problem instance shall be the number of arrangements of the N students into the given rows so that the heights decrease along each row from left to right and along each column from back to front as a decimal integer. (Assume all heights are distinct.) The result of each problem instance should be on a separate line. The input data will be chosen so that the result will always fit in an unsigned 32 bit integer.

Sample Input

1
30
5
1 1 1 1 1
3
3 2 1
4
5 3 3 1
5
6 5 4 3 2
2
15 15
0

Sample Output

1
1
16
4158
141892608
9694845

Source

分析

参照《进阶指南》上面的做法。

常规DP专题练习

时间复杂度\(O(\prod_k N_k)\)

代码

然而这种方法在POJ本站上是过不了的,要么CE要么MLE。

#include<iostream>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; int n[6],k;
void work(){
for(int i=1;i<=k;++i) read(n[i]);
while(k<5) n[++k]=0;
ll f[n[1]+1][n[2]+1][n[3]+1][n[4]+1][n[5]+1];
memset(f,0,sizeof f);
f[0][0][0][0][0]=1;
for(int i=0;i<=n[1];++i)
for(int j=0;j<=n[2];++j)
for(int k=0;k<=n[3];++k)
for(int l=0;l<=n[4];++l)
for(int m=0;m<=n[5];++m){
if(i<n[1]) f[i+1][j][k][l][m]+=f[i][j][k][l][m];
if(j<n[2]&&i>j) f[i][j+1][k][l][m]+=f[i][j][k][l][m];
if(k<n[3]&&j>k) f[i][j][k+1][l][m]+=f[i][j][k][l][m];
if(l<n[4]&&k>l) f[i][j][k][l+1][m]+=f[i][j][k][l][m];
if(m<n[5]&&l>m) f[i][j][k][l][m+1]+=f[i][j][k][l][m];
}
printf("%lld\n",f[n[1]][n[2]][n[3]][n[4]][n[5]]);
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
while(read(k)) work();
return 0;
}

再分析

参照five20的题解。

杨表由有限的方格组成。

对于一个正整数,给定一个整数分拆λ(10=1+4+5),则对应一个杨表(注意这是一个递降的过程,也就是说下面一行的方格数要大于等于上一行的方格数)。

常规DP专题练习

一个(1,4,5)分拆表示的杨表

杨表与整数分拆λ一一对应。

给定一个杨表,一共有n个方格。那么把1到n这n个数字填到这个杨表中,使得每行从左到右都是递增的,每列从下到上也是递增的。如图

常规DP专题练习

一个杨表的表示

【勾长】对于杨表中的一个方格v,其勾长hook(v)等于同行右边的方格数加上同列上面的方格数,再加上1(也就是他自己)。

【勾长公式】用表示杨表个数,则

常规DP专题练习

对于分拆10 = 5 + 4 + 1 的应的杨表. 因此共有常规DP专题练习种方法。

公式题。求出同行右边的方格数+同列上面的方格数+1。唯一注意的地方是最后除的时候要防止溢出。

时间复杂度\(O(Nk)\)

代码

#include<iostream>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=31;
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
int n,cnt,num[N],sum[N];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
while(read(n)){
for(int i=1;i<=n;++i) read(num[i]);
cnt=0;
for(int i=1;i<=n;++i)for(int j=1;j<=num[i];++j){
sum[++cnt]=num[i]-j+1;
for(int k=i+1;k<=n;++k){
if(num[k]>=j) ++sum[cnt];
else break;
}
}
ll x=1,y=1,t;
for(int i=1;i<=cnt;++i){
x*=i,y*=sum[i];
t=gcd(x,y),x/=t,y/=t;
}
printf("%lld\n",x/y);
}
return 0;
}

CH5101 LCIS

题意

5101 LCIS 0x50「动态规划」例题

描述

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。

小沐沐说,对于两个数列A和B,如果它们都包含一段位置不一定连续的数,且数值是严格递增的,那么称这一段数是两个数列的公共上升子序列,而所有的公共上升子序列中最长的就是最长公共上升子序列了。

奶牛半懂不懂,小沐沐要你来告诉奶牛什么是最长公共上升子序列。不过,只要告诉奶牛它的长度就可以了。数列A和B的长度均不超过3000。

输入格式

第一行N,表示A,B的长度。

第二行,串A。

第三行,串B。

输出格式

输出长度。

样例输入

4
2 2 1 3
2 1 2 3

样例输出

2

数据范围与约定

  • 1<=N<=3000,A,B中的数字不超过2^31-1
        </article>

分析

LCIS,同HDU上的Greatest Common Increasing Subquence。当时做的时候我怎么都想不出来\(O(n^2)\)做法,原来是状态没设对。

\(F[i,j]\)表示\(A_1\sim A_i\)与\(B_1\sim B_j\)的以\(B_j\)结尾的LCIS长度。不妨设\(A0=B_0=-\infty\)

当\(A_i\ne B_j\)时,\(F[i,j]=F[i-1,j]\)

当\(A_i=B_j\)时,$$F[i,j]=\max_{0\le k<j,B_k<B_j=A_i}{F[i-1,k]}+1$$,而这个循环显然可以维护最大值做到\(O(1)\)转移。

时间复杂度\(O(n^2)\)

代码

亲测用readTLE,用scanfAC。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=3001;
int n,a[N],b[N],f[N][N];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(n);
for(rg int i=1;i<=n;++i) scanf("%d",a+i);
for(rg int i=1;i<=n;++i) scanf("%d",b+i);
for(rg int i=1;i<=n;++i){
rg int val=0;
for(rg int j=1;j<=n;++j){
f[i][j]=a[i]==b[j]?val+1:f[i-1][j];
if(b[j]<a[i]) val=std::max(val,f[i-1][j]);
}
}
rg int ans=0;
for(rg int i=1;i<=n;++i) ans=std::max(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}

POJ3666 Making the Grade

题意

Language:Default
Making the Grade
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 11192 Accepted: 5201

Description

A straight dirt road connects two fields on FJ's farm, but it changes elevation more than FJ would like. His cows do not mind climbing up or down a single slope, but they are not fond of an alternating succession of hills and valleys. FJ would like to add and remove dirt from the road so that it becomes one monotonic slope (either sloping up or down).

You are given N integers A1, ... , AN (1 ≤ N ≤ 2,000) describing the elevation (0 ≤ Ai ≤ 1,000,000,000) at each of N equally-spaced positions along the road, starting at the first field and ending at the other. FJ would like to adjust these elevations to a new sequence B1, . ... , BN that is either nonincreasing or nondecreasing. Since it costs the same amount of money to add or remove dirt at any position along the road, the total cost of modifying the road is

|A1 - B1| + |A2 - B2| + ... + |AN - BN |

Please compute the minimum cost of grading his road so it becomes a continuous slope. FJ happily informs you that signed 32-bit integers can certainly be used to compute the answer.

Input

* Line 1: A single integer: N
* Lines 2..N+1: Line i+1 contains a single integer elevation: Ai

Output

* Line 1: A single integer that is the minimum cost for FJ to grade his dirt road so it becomes nonincreasing or nondecreasing in elevation.

Sample Input

7
1
3
2
4
5
3
9

Sample Output

3

Source

分析

以《进阶指南》上的分析为准,结论是B中的数都在A中出现过。

\(F[i,j]\)表示完成前\(i\)个数的构造,其中\(B_i=j\)时,\(S\)的最小值。

\[F[i,j]=\min_{0\le k\le j}\{F[i-1,k]+|A_i-j|\}
\]

决策集合只增多不减少,转移可以维护成\(O(1)\)。时间复杂度\(O(N^2)\)

代码

虽然说要做两遍,但是做一遍算出来也是对的?

#include<iostream>
#include<algorithm>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=2001;
int n,a[N],b[N],f[N][N];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(n);
for(int i=1;i<=n;++i) b[i]=read(a[i]);
std::sort(b+1,b+n+1);
int m=std::unique(b+1,b+n+1)-b-1;
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;++i){
int temp=f[i-1][0];
for(int j=1;j<=m;++j){
temp=std::min(temp,f[i-1][j]);
f[i][j]=temp+abs(a[i]-b[j]);
}
}
int ans=1<<30;
for(int i=1;i<=m;++i) ans=std::min(ans,f[n][i]);
printf("%d\n",ans);
return 0;
}

CH5102 Mobile Service

题意

5102 Mobile Service 0x50「动态规划」例题

描述

一个公司有三个移动服务员,最初分别在位置1,2,3处。

如果某个位置(用一个整数表示)有一个请求,那么公司必须指派某名员工赶到那个地方去。某一时刻只有一个员工能移动,且不允许在同样的位置出现两个员工。从 p 到 q 移动一个员工,需要花费 c(p,q)。这个函数不一定对称,但保证 c(p,p)=0。

给出N个请求,请求发生的位置分别为 p_1~p_N。公司必须按顺序依次满足所有请求,目标是最小化公司花费,请你帮忙计算这个最小花费。N≤1000,位置是1~200的整数。

输入格式

第一行有两个整数L,N(3<=L<=200, 1<=N<=1000)。L是位置数;N是请求数。每个位置从1到L编号。下L行每行包含L个非负整数。第i+1行的第j个数表示c(i,j) ,并且它小于2000。最后一行包含N个数,是请求列表。一开始三个服务员分别在位置1,2,3。

输出格式

一个数M,表示最小服务花费。

样例输入

5 9
0 1 1 1 1
1 0 2 3 2
1 1 0 4 1
2 1 5 0 1
4 2 3 4 0
4 2 4 1 5 4 3 2 1

样例输出

5
        </article>

分析

用\(F[i,x,y]\)表示完成了前\(i\)个请求,另外两个员工在\(x,y\)时的最小花费。

枚举转移即可,注意特判掉员工位置相同的情况。可以假设\(p_0=3\),这样初始化为\(F[0,1,2]=0\)

时间复杂度\(O(NL^2)\)

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using std::min; co int L=201,N=1001,INF=0x3f3f3f3f;
int l,n,c[L][L],p[N],f[N][L][L];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(l),read(n);
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j) read(c[i][j]);
memset(f,0x3f,sizeof f);
p[0]=3,f[0][1][2]=0;
for(int i=1;i<=n;++i){
read(p[i]);
for(int j=1;j<=l;++j)
for(int k=1;k<=l;++k)if(f[i-1][j][k]<INF){
if(j!=p[i]&&k!=p[i])
f[i][j][k]=min(f[i][j][k],f[i-1][j][k]+c[p[i-1]][p[i]]);
if(j!=p[i]&&p[i-1]!=p[i])
f[i][j][p[i-1]]=min(f[i][j][p[i-1]],f[i-1][j][k]+c[k][p[i]]);
if(k!=p[i]&&p[i-1]!=p[i])
f[i][p[i-1]][k]=min(f[i][p[i-1]][k],f[i-1][j][k]+c[j][p[i]]);
}
}
int ans=INF;
for(int i=1;i<=l;++i)
for(int j=1;j<=l;++j) ans=min(ans,f[n][i][j]);
printf("%d\n",ans);
return 0;
}

CH5103 传纸条

题意

5103 传纸条 0x50「动态规划」例题

描述

给定一个 N*M 的矩阵A,每个格子中有一个整数。现在需要找到两条从左上角 (1,1) 到右下角 (N,M) 的路径,路径上的每一步只能向右或向下走。路径经过的格子中的数会被取走。两条路径不能经过同一个格子。求取得的数之和最大是多少。N,M≤50。

输入格式

第一行有2个用空格隔开的整数n和m,表示有n行m列(1<=n,m<=50)。

接下来的n行是一个n*m的矩阵,每行的n个整数之间用空格隔开。

输出格式

一个整数,表示答案。

样例输入

3 3
0 3 9
2 8 5
5 7 0

样例输出

34

数据范围与约定

  • 30%的数据满足:1<=m,n<=10 

    100%的数据满足:1<=m,n<=50

来源

CCF NOIP2008 T3

        </article>

分析

这种只能向右和向下走,只能在同时走到相同的格子,所以维护当前在哪个格子即可。容易设出状态\(F[x_1,y_1,x_2,y_2]\),但是这些状态并不都合法。只有\(x_1+y_1=x_2+y_2\)时状态才合法,这启示我们设立新的状态。

由于同一时间两条线路走的步数是相同的,而步数和行数就可以确定位置,所以可以用\(F[i,x_1,x_2]\)表示走了\(i\)步,第一条线走到了\(x_1\)行,第二条线走到了\(x_2\)行,已经取得的数的最大值。

那么枚举两条线路往哪走转移即可,注意判断走到了相同的格子。

时间复杂度\(O((n+m)n^2)\)

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using std::max; co int N=51;
int n,m,a[N][N],f[N*2][N][N];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(n),read(m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) read(a[i][j]);
for(int i=0;i<n+m-2;++i)
for(int j=1;j<=n&&j<=i+1;++j)
for(int k=1;k<=n&&k<=i+1;++k){
if(j==k){ // both right or down
f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+a[j][i+3-j]);
f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+a[j+1][i+2-j]);
}
else{
f[i+1][j][k]=max(f[i+1][j][k],f[i][j][k]+a[j][i+3-j]+a[k][i+3-k]);
f[i+1][j+1][k+1]=max(f[i+1][j+1][k+1],f[i][j][k]+a[j+1][i+2-j]+a[k+1][i+2-k]);
}
if(j+1==k) f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+a[j+1][i+2-j]); // j down, k right
else f[i+1][j+1][k]=max(f[i+1][j+1][k],f[i][j][k]+a[j+1][i+2-j]+a[k][i+3-k]);
if(k+1==j) f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+a[j][i+3-j]); // j right,k down
else f[i+1][j][k+1]=max(f[i+1][j][k+1],f[i][j][k]+a[j][i+3-j]+a[k+1][i+2-k]);
}
printf("%d\n",f[n+m-2][n][n]);
return 0;
}

CH5104 I-country

题意

5104 I-country 0x50「动态规划」例题

描述

在 N*M 的矩阵中,每个格子有一个权值,要求寻找一个包含 K 个格子的凸连通块(连通块中间没有空缺,并且轮廓是凸的,如书中图片所示),使这个连通块中的格子的权值和最大。求出这个最大的权值和,并给出连通块的具体方案。本题有SPJ,输出任意一种方案即可。N,M≤15,K≤225。

According to top-secret A-country plans, I-country is divided into N*M equal squares, each square contains some oil resources. They want to occupy all the territory of I-country, but the UN (United Nations) will allow them to occupy only K squares. Of course, A-country want to get control over as many oil as possible, but, they will have to guard all their territory. So, they need their territory to be easy-controlled, i.e. from any square to any it must be possible to get moving only along two directions (selected from the next list: left, right, up, down; for different squares pairs of directions may differ). 

You are to write a program, which determinies, what squares will be occupyed by A-country. If there are several solutions, you may output any.

输入格式

On the first line of input there are 3 integer numbers N,M,K (1<=N,M<=15, 0<=K<=N*M). Next N lines contains M integers each, which are the number of oil resource on that square. Each of this numbers lies in range of 0 to 1000.

输出格式

On the first line of output, write string "Oil : X", where integer number X --- the maximal number of oil which can be controlled by A-country. Next you should output K pairs of numbers --- coordinates of the squares which will be occupied by A-country. The first coordinate is number of row (top to bottom, starting from 1), second is number of column (left to right, starting from 1).

样例输入

2 3 4 
10 20 30 
40 2 3

样例输出

Oil : 100 
1 1 
1 2 
1 3 
2 1

来源

SGU167

本题校验器(SPJ)

01 #include <iostream>
02 #include <cstdio>
03 #include <cstdlib>
04 #include <cstring>
05 #include <algorithm>
06 using namespace std;
07 //一些定义
08 const int ACCEPT = 0;
09 const int WRONG_ANSWER = 1;
10 //fstd 标准输出 fout 选手输出 fin 标准输入
11 FILE *fstd,*fout,*fin;
12 int n, m, k, a[20][20], v[20][20], ans, val;
13  
14 //执行比较操作。
15 bool DoCompare(){
16     fscanf(fin, "%d%d%d", &n, &m, &k);
17     for (int i = 1; i <= n; i++)
18         for (int j = 1; j <= m; j++) fscanf(fin, "%d", &a[i][j]);
19     fscanf(fstd, "%*s%*s%d", &ans);
20     fscanf(fout, "%*s%*s%d", &val);
21     // 答案不对
22     if (val != ans) return false;
23     for (int i = 1; i <= k; i++) {
24         int x, y; fscanf(fout, "%d%d", &x, &y);
25         // 格子越界或者有重复
26         if (x < 1 || y < 1 || x > n || y > m || v[x][y]) return false;
27         v[x][y] = 1;
28         val -= a[x][y];
29     }
30     // 输出的格子加起来不等于答案
31     if (val) return false;
32     // 检查凸性
33     for (int i = 1; i <= n; i++) {
34         int cnt = 0, l = m, r = 1;
35         for (int j = 1; j <= m; j++)
36             if (v[i][j]) cnt++, l = min(l, j), r = max(r, j);
37         if (cnt == 0) continue;
38         // 输出的格子在一行里不连续
39         if (r - l + 1 != cnt) return false;
40     }
41     for (int j = 1; j <= m; j++) {
42         int cnt = 0, l = n, r = 1;
43         for (int i = 1; i <= n; i++)
44             if (v[i][j]) cnt++, l = min(l, i), r = max(r, i);
45         if (cnt == 0) continue;
46         // 输出的格子在一列里不连续
47         if (r - l + 1 != cnt) return false;
48     }
49     return true;
50 }
51  
52 int main(int argc, char* argv[])
53 {
54     if(argc!=4){
55         printf("参数不足 %d",argc);
56         return -1;
57     }
58  
59     //打开文件
60     if(NULL==(fstd=fopen(argv[1],"r"))){
61         return -1;
62     }
63     if(NULL==(fout=fopen(argv[2],"r"))){
64         return -1;
65     }
66     if(NULL==(fin=fopen(argv[3],"r"))){
67         return -1;
68     }
69  
70     if(DoCompare()){
71         return ACCEPT;
72     }else{
73         return WRONG_ANSWER;
74     }
75 }
        </article>

分析

参照shellpicker的题解。

pre:在网格中,凸多边形可以按行(row)分解成若干段连续的区间 [ l , r ] ,且左端点纵坐标的值(col)满足先减后增,右端点纵坐标先增后减。

阶段:根据这个小发现,可以将阶段设置成每一行,因此,解决这个问题一共需要N个阶段。

状态:除了阶段外,表示每一个状态还需要记录下当前阶段下一共选了多少个网格,当前行选择的区间 [ l , r ] ,和相对于上一行来说端点选择的单调性。(0表示单调递增,1表示单调递减)

因此,状态可以表示成为dp[i][j][l][r][x][y]

状态转移方程:分成四种情况进行讨论,详见代码。

第二个要实现的是路径输出,可以额外使用与状态大小相同的数组来记录下当前状态是从哪个状态转移而来的,最后从末状态经过一次递归即可得到路径。

时间复杂度\(O(n^2 m^5)\)

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=16,K=226;
int n,m,k,f[N][K][N][N][2][2],a[N][N],b[N][N];
int cl[N][K][N][N][2][2],cr[N][K][N][N][2][2];
int dx[N][K][N][N][2][2],dy[N][K][N][N][2][2];
#define _f f[i][j][l][r][x][y]
#define _cl cl[i][j][l][r][x][y]
#define _cr cr[i][j][l][r][x][y]
#define _dx dx[i][j][l][r][x][y]
#define _dy dy[i][j][l][r][x][y]
il void work(int i,int j,int l,int r,int x,int y,int w,int L,int R,int X,int Y){
if(w<_f) return;
_f=w,_cl=L,_cr=R,_dx=X,_dy=Y;
}
void print(int i,int j,int l,int r,int x,int y){
if(!j) return;
print(i-1,j-(r-l+1),_cl,_cr,_dx,_dy);
for(int j=l;j<=r;++j) printf("%d %d\n",i,j);
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(n),read(m),read(k);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j)
b[i][j]=b[i][j-1]+read(a[i][j]);
memset(f,0xcf,sizeof f);
for(int i=1;i<=n;++i)
for(int j=1;j<=k;++j)
for(int l=1;l<=m;++l)
for(int r=l;r<=m;++r){
if(r-l+1>j) break;
int w=b[i][r]-b[i][l-1];
for(int x=0;x<2;++x)
for(int y=0;y<2;++y)
work(i,j,l,r,x,y,w,m,0,x,y);
for(int p=l;p<=r;++p)
for(int q=p;q<=r;++q) // both expand
work(i,j,l,r,1,1,f[i-1][j-(r-l+1)][p][q][1][1]+w,p,q,1,1);
for(int p=1;p<=l;++p)
for(int q=r;q<=m;++q){ // both contract
work(i,j,l,r,0,0,f[i-1][j-(r-l+1)][p][q][0][0]+w,p,q,0,0);
work(i,j,l,r,0,0,f[i-1][j-(r-l+1)][p][q][1][0]+w,p,q,1,0);
work(i,j,l,r,0,0,f[i-1][j-(r-l+1)][p][q][0][1]+w,p,q,0,1);
work(i,j,l,r,0,0,f[i-1][j-(r-l+1)][p][q][1][1]+w,p,q,1,1);
}
for(int p=l;p<=r;++p) // l expand, r contract
for(int q=r;q<=m;++q){
work(i,j,l,r,1,0,f[i-1][j-(r-l+1)][p][q][1][0]+w,p,q,1,0);
work(i,j,l,r,1,0,f[i-1][j-(r-l+1)][p][q][1][1]+w,p,q,1,1);
}
for(int p=1;p<=l;++p)
for(int q=l;q<=r;++q){ // l contract, r expand
work(i,j,l,r,0,1,f[i-1][j-(r-l+1)][p][q][0][1]+w,p,q,0,1);
work(i,j,l,r,0,1,f[i-1][j-(r-l+1)][p][q][1][1]+w,p,q,1,1);
}
}
int ans=0,ai,al,ar,ax,ay;
for(int i=1;i<=n;++i)
for(int l=1;l<=m;++l)
for(int r=l;r<=m;++r)
for(int x=0;x<2;++x)
for(int y=0;y<2;++y)
if(ans<f[i][k][l][r][x][y])
ans=f[i][k][l][r][x][y],ai=i,al=l,ar=r,ax=x,ay=y;
printf("Oil : %d\n",ans);
print(ai,k,al,ar,ax,ay);
return 0;
}

CH5105 Cookies

题意

5105 Cookies 0x50「动态规划」例题

描述

圣诞老人共有M个饼干,准备全部分给N个孩子。每个孩子有一个贪婪度,第 i 个孩子的贪婪度为 g[i]。如果有 a[i] 个孩子拿到的饼干数比第 i 个孩子多,那么第 i 个孩子会产生 g[i]*a[i]的怨气。给定N、M和序列g,圣诞老人请你帮他安排一种分配方式,使得每个孩子至少分到一块饼干,并且所有孩子的怨气总和最小。1≤N≤30, N≤M≤5000, 1<=gi<=10^7。

输入格式

第一行两个整数N,M,第二行N个整数g1~gN。

输出格式

第一行一个整数表示答案,第二行N个整数表示每个孩子分到的饼干数。本题有SPJ,若有多种方案,输出任意一种均可。

样例输入

样例输入1
3 20
1 2 3 样例输入2
4 9
2 1 5 8

样例输出

样例输出1
2
2 9 9 样例输出2
7
​2 1 3 3

来源

ITMO

本题校验器(SPJ)

01 #include <iostream>
02 #include <cstdio>
03 #include <cstdlib>
04 #include <cstring>
05 #include <algorithm>
06 using namespace std;
07 //一些定义
08 const int ACCEPT = 0;
09 const int WRONG_ANSWER = 1;
10 //fstd 标准输出 fout 选手输出 fin 标准输入
11 FILE *fstd,*fout,*fin;
12 int n, m, ans, val, c[32], g[32];
13  
14 //执行比较操作。
15 bool DoCompare(){
16     fscanf(fin, "%d%d", &n, &m);
17     for (int i = 1; i <= n; i++) fscanf(fin, "%d", &g[i]);
18     fscanf(fstd, "%d", &ans);
19     fscanf(fout, "%d", &val);
20     // 答案不对
21     if (ans != val) return false;
22     for (int i = 1; i <= n; i++) {
23         fscanf(fout, "%d", &c[i]);
24         // 每个孩子分到正整数块饼干
25         if (c[i] <= 0) return false;
26         m -= c[i];
27     }
28     // 饼干没有分完
29     if (m) return false;
30     // 检查方案的怨气值是否等于输出的值
31     for (int i = 1; i <= n; i++) {
32         int cnt = 0;
33         for (int j = 1; j <= n; j++) if (c[j] > c[i]) cnt++;
34         val -= cnt * g[i];
35     }
36     return val == 0;
37 }
38  
39 int main(int argc, char* argv[])
40 {
41     if(argc!=4){
42         printf("参数不足 %d",argc);
43         return -1;
44     }
45  
46     //打开文件
47     if(NULL==(fstd=fopen(argv[1],"r"))){
48         return -1;
49     }
50     if(NULL==(fout=fopen(argv[2],"r"))){
51         return -1;
52     }
53     if(NULL==(fin=fopen(argv[3],"r"))){
54         return -1;
55     }
56  
57     if(DoCompare()){
58         return ACCEPT;
59     }else{
60         return WRONG_ANSWER;
61     }
62 }
        </article>

分析

参照ssl_xjq_逐风之刃的题解。

这道题没有出现子结构,但是可以发现,贪婪度大的必然分的会多,所以其实是按贪婪度单调不上升的所以子结构可以通过贪心得到,但是直接推很难高效地解决问题,那应该怎么做,可以等价转换成

若第i个孩子的饼干>1,等价分配j−i个饼干给前i个孩子,相对顺序不变,怨气总和不变

若第i个孩子只有一个饼干,那么枚举之前的多少孩子也获得一个饼干

孩子也获得一个饼干

\[f[i][j]=min\{f[i][j-i],min_{k=0}^i\{f[k][j-i+k]+k*(sum[i]-sum[k])\}\}
\]

时间复杂度\(O(N^2M)\)

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=31,M=5001;
int f[N][M],a[N][M],b[N][M],g[N],c[N],s[N],ans[N];
il bool cmp(int a,int b) {return g[a]>g[b];}
void print(int n,int m){
if(n==0) return;
print(a[n][m],b[n][m]);
if(a[n][m]==n)
for(int i=1;i<=n;++i) ++ans[c[i]];
else
for(int i=a[n][m]+1;i<=n;++i) ans[c[i]]=1;
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
int n=read<int>(),m=read<int>();
for(int i=1;i<=n;++i) read(g[i]),c[i]=i;
std::sort(c+1,c+n+1,cmp);
memset(f,0x3f,sizeof f);
f[0][0]=0;
for(int i=1;i<=n;++i){
s[i]=s[i-1]+g[c[i]];
for(int j=i;j<=m;++j){
f[i][j]=f[i][j-i],a[i][j]=i,b[i][j]=j-i;
for(int k=0;k<i;++k)
if(f[i][j]>f[k][j-(i-k)]+(s[i]-s[k])*k)
f[i][j]=f[k][j-(i-k)]+(s[i]-s[k])*k,a[i][j]=k,b[i][j]=j-(i-k);
}
}
printf("%d\n",f[n][m]);
print(n,m);
for(int i=1;i<=n;++i) printf("%d ",ans[i]);
return puts(""),0;
}

POJ1015 Jury Compromise

题意

Language:Default
Jury Compromise
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 33761 Accepted: 9115 Special Judge

Description

In Frobnia, a far-away country, the verdicts in court trials are determined by a jury consisting of members of the general public. Every time a trial is set to begin, a jury has to be selected, which is done as follows. First, several people are drawn randomly from the public. For each person in this pool, defence and prosecution assign a grade from 0 to 20 indicating their preference for this person. 0 means total dislike, 20 on the other hand means that this person is considered ideally suited for the jury.

Based on the grades of the two parties, the judge selects the jury. In order to ensure a fair trial, the tendencies of the jury to favour either defence or prosecution should be as balanced as possible. The jury therefore has to be chosen in a way that is satisfactory to both parties.

We will now make this more precise: given a pool of n potential jurors and two values di (the defence's value) and pi (the prosecution's value) for each potential juror i, you are to select a jury of m persons. If J is a subset of {1,..., n} with m elements, then D(J ) = sum(dk) k belong to J

and P(J) = sum(pk) k belong to J are the total values of this jury for defence and prosecution.

For an optimal jury J , the value |D(J) - P(J)| must be minimal. If there are several jurys with minimal |D(J) - P(J)|, one which maximizes D(J) + P(J) should be selected since the jury should be as ideal as possible for both parties.

You are to write a program that implements this jury selection process and chooses an optimal jury given a set of candidates.

Input

The input file contains several jury selection rounds. Each round starts with a line containing two integers n and m. n is the number of candidates and m the number of jury members.

These values will satisfy 1<=n<=200, 1<=m<=20 and of course m<=n. The following n lines contain the two integers pi and di for i = 1,...,n. A blank line separates each round from the next.

The file ends with a round that has n = m = 0.

Output

For each round output a line containing the number of the jury selection round ('Jury #1', 'Jury #2', etc.).

On the next line print the values D(J ) and P (J ) of your jury as shown below and on another line print the numbers of the m chosen candidates in ascending order. Output a blank before each individual candidate number.

Output an empty line after each test case.

Sample Input

4 2
1 2
2 3
4 1
6 2
0 0

Sample Output

Jury #1
Best jury has value 6 for prosecution and value 4 for defence:
2 3

Hint

If your solution is based on an inefficient algorithm, it may not execute in the allotted time.

Source

在遥远的国家佛罗布尼亚,嫌犯是否有罪,须由陪审团决定。陪审团是由法官从公众中挑选的。先随机挑选n 个人作为陪审团的候选人,然后再从这n 个人中选m 人组成陪审团。选m 人的办法是:控方和辩方会根据对候选人的喜欢程度,给所有候选人打分,分值从0 到20。为了公平起见,法官选出陪审团的原则是:选出的m 个人,必须满足辩方总分D和控方总分P的差的绝对值|D-P|最小。如果有多种选择方案的|D-P| 值相同,那么选辩控双方总分之和D+P最大的方案即可。

输出:

选取符合条件的最优m个候选人后,要求输出这m个人的辩方总值D和控方总值P,并升序输出他们的编号。

分析

参照aidway的题解。

算法:三维dp

定义dp[i,j,k]为前i个候选人中选择j个陪审员取得最小辩控差为k时最大的辩控和 ,我们要求的是dp[n,m,min_diff]

如何得到dp[i,j,k]呢?

考虑i-1时,为了得到i的情况:

1.如果dp[i-1][j-1][k-difference[i]]存在,而且dp[i-1][j-1][k-difference[i]]+sum[i]>dp[i,j,k],

那么,我们选择第i个候选人,即 dp[i,j,k] = dp[i-1][j-1][k-difference[i]]+sum[i]

2.如果 dp[i-1][j-1][k-difference[i]]不存在,或者说不可达,那么我们取dp[i,j,k] = dp[i-1,j,k]

即dp[i][j][k] = max{dp[i-1,j,k] , dp[i-1][j-1][k-difference[i]]+sum[i]}

代码

#include<iostream>
#include<cstring>
#include<vector>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; int f[25][805];
int d[205][25][805];
int n,m,a[205],b[205],suma,sumb,T;
vector<int> c;
void get_path(int i,int j,int k){
if(j==0) return;
int last=d[i][j][k];
get_path(last-1,j-1,k-(a[last]-b[last]));
c.push_back(last);
suma+=a[last],sumb+=b[last];
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
while(read(n)|read(m)){
for(int i=1;i<=n;++i) read(a[i]),read(b[i]);
memset(f,0xcf,sizeof f);
f[0][400]=0;
for(int i=1;i<=n;++i){
for(int j=0;j<=m;++j)
memcpy(d[i][j],d[i-1][j],sizeof d[i][j]);
for(int j=m;j;--j)
for(int k=0;k<=800;++k){
if(k-(a[i]-b[i])<0||k-(a[i]-b[i])>800) continue;
if(f[j][k]<f[j-1][k-(a[i]-b[i])]+a[i]+b[i])
f[j][k]=f[j-1][k-(a[i]-b[i])]+a[i]+b[i],d[i][j][k]=i;
}
}
int ans=0;
for(int k=0;k<=400;++k)
if(max(f[m][400+k],f[m][400-k])>=0){
ans=f[m][400+k]>f[m][400-k]?400+k:400-k;
break;
}
c.clear();
suma=sumb=0;
get_path(n,m,ans);
printf("Jury #%d\n",++T);
printf("Best jury has value %d for prosecution and value %d for defence:\n",suma,sumb);
for(int i=0;i<c.size();++i) printf(" %d",c[i]);
printf("\n\n");
}
return 0;
}

POJ1742 Coins

题意

Language:Default
Coins
Time Limit: 3000MS Memory Limit: 30000K
Total Submissions: 45649 Accepted: 15445

Description

People in Silverland use coins.They have coins of value A1,A2,A3...An Silverland dollar.One day Tony opened his money-box and found there were some coins.He decided to buy a very nice watch in a nearby shop. He wanted to pay the exact price(without change) and he known the price would not more than m.But he didn't know the exact price of the watch.

You are to write a program which reads n,m,A1,A2,A3...An and C1,C2,C3...Cn corresponding to the number of Tony's coins of value A1,A2,A3...An then calculate how many prices(form 1 to m) Tony can pay use these coins.

Input

The input contains several test cases. The first line of each test case contains two integers n(1<=n<=100),m(m<=100000).The second line contains 2n integers, denoting A1,A2,A3...An,C1,C2,C3...Cn (1<=Ai<=100000,1<=Ci<=1000). The last test case is followed by two zeros.

Output

For each test case output the answer on a single line.

Sample Input

3 10
1 2 4 2 1 1
2 5
1 4 2 1
0 0

Sample Output

8
4

Source

分析

多重背包。

只问可行性,因此记录一下每个数用了多少次就行了,不超过上限才转移。

时间复杂度\(O(NM)\)

代码

#include<iostream>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=102,M=1e5+1;
int n,m,a[N],c[N],used[M];
bool f[M];
void Coins(){
for(int i=1;i<=m;++i) f[i]=0;
for(int i=1;i<=n;++i) read(a[i]);
for(int i=1;i<=n;++i) read(c[i]);
f[0]=1;
for(int i=1;i<=n;++i){
for(int j=0;j<=m;++j) used[j]=0;
for(int j=a[i];j<=m;++j)
if(!f[j]&&f[j-a[i]]&&used[j-a[i]]<c[i])
f[j]=1,used[j]=used[j-a[i]]+1;
}
int ans=0;
for(int i=1;i<=m;++i) ans+=f[i];
printf("%d\n",ans);
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
while(read(n)|read(m)) Coins();
return 0;
}

POJ1179 Polygon

题意

Language:Default
Polygon
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 6947 Accepted: 2972

Description

Polygon is a game for one player that starts on a polygon with N vertices, like the one in Figure 1, where N=4. Each vertex is labelled with an integer and each edge is labelled with either the symbol + (addition) or the symbol * (product). The edges are numbered from 1 to N.

常规DP专题练习

On the first move, one of the edges is removed. Subsequent moves involve the following steps:

�pick an edge E and the two vertices V1 and V2 that are linked by E; and

�replace them by a new vertex, labelled with the result of performing the operation indicated in E on the labels of V1 and V2.

The game ends when there are no more edges, and its score is the label of the single vertex remaining.



Consider the polygon of Figure 1. The player started by removing edge 3. After that, the player picked edge 1, then edge 4, and, finally, edge 2. The score is 0.

常规DP专题练习

Write a program that, given a polygon, computes the highest possible score and lists all the edges that, if removed on the first move, can lead to a game with that score.

Input

Your program is to read from standard input. The input describes a polygon with N vertices. It contains two lines. On the first line is the number N. The second line contains the labels of edges 1, ..., N, interleaved with the vertices' labels (first that of the vertex between edges 1 and 2, then that of the vertex between edges 2 and 3, and so on, until that of the vertex between edges N and 1), all separated by one space. An edge label is either the letter t (representing +) or the letter x (representing *).



3 <= N <= 50

For any sequence of moves, vertex labels are in the range [-32768,32767].

Output

Your program is to write to standard output. On the first line your program must write the highest score one can get for the input polygon. On the second line it must write the list of all edges that, if removed on the first move, can lead to a game with that score. Edges must be written in increasing order, separated by one space.

Sample Input

4
t -7 t 4 x 2 x 5

Sample Output

33
1 2

Source

多边形游戏是一个单人玩的游戏,开始时有一个由n个顶点构成的多边形。每个顶点被赋予一个整数值,每条边被赋予一个运算符“+”或“*”。所有边依次用整数从1到n编号

游戏第1步,将一条边删除

随后n-1步按以下方式操作

  1. 选择一条边E以及由E连接着的2个顶点V1和V2
  2. 用一个新的顶点取代边E以及由E连接着的2个顶点V1和V2。将由顶点V1和V2的整数值通过边E上的运算得到的结果赋予新顶点

最后,所有边都被删除,游戏结束。游戏的得分就是所剩顶点上的整数值

分析

参照再见~雨泉的题解。

题目的意思就是给n个数,n个两两数之间的运算符(只有+和*)问首先去掉哪个运算符号之后可以让其他的数按照一定的方法计算后结果最大。

其实结题思路还是比较好想到的,枚举(枚举去掉的符号)+DP(记忆化搜索)就可以做到。但这里有一个天坑,就是负负得正,所以不能单一的枚举最大值,而要同时DP最小值。

计算最大值:

加法 max(i,j) = max(i,k)+max(k,j);

乘法 max(i,j) = MAX(max(i,k)*max(k,j),max(i,k)*min(k,j),max(k,j)*min(i,k),min(i,k)*min(k,j));(i=<k<=j)

计算最小值:

加法 min(i,j) = min(i,k)+min(k,j);

乘法 min(i,j) = MIN(max(i,k)*max(k,j),min(i,k)*min(k,j),max(k,j)*min(i,k),min(i,k)*min(k,j));(i=<k<=j)

可以把链复制一倍,然后直接做区间DP,不用枚举断哪条边。时间复杂度\(O(n^3)\)

代码

#include<iostream>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int N=101;
char c[N];
int a[N],fmax[N][N],fmin[N][N];
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
int n=read<int>();
for(int i=1;i<=n;++i) scanf("%s",c+i),read(a[i]);
for(int i=n+1;i<=n<<1;++i) c[i]=c[i-n],a[i]=a[i-n];
memset(fmax,0xcf,sizeof fmax);
memset(fmin,0x3f,sizeof fmin);
for(int i=1;i<=n<<1;++i) fmax[i][i]=fmin[i][i]=a[i];
for(int j=2;j<=n<<1;++j)
for(int i=j-1;i;--i)
for(int k=i+1;k<=j;++k){
if(c[k]=='t')
fmax[i][j]=max(fmax[i][j],fmax[i][k-1]+fmax[k][j]),
fmin[i][j]=min(fmin[i][j],fmin[i][k-1]+fmin[k][j]);
else
fmax[i][j]=max(fmax[i][j],max(fmax[i][k-1]*fmax[k][j],fmin[i][k-1]*fmin[k][j])),
fmin[i][j]=min(fmin[i][j],min(fmin[i][k-1]*fmin[k][j],min(fmax[i][k-1]*fmin[k][j],min(fmin[i][k-1]*fmax[k][j],fmax[i][k-1]*fmax[k][j]))));
}
int ans=-0x3f3f3f3f;
for(int i=1;i<=n;++i) ans=max(ans,fmax[i][i+n-1]);
printf("%d\n",ans);
for(int i=1;i<=n;++i)if(ans==fmax[i][i+n-1])
printf("%d ",i);
return 0;
}

CH5302 金字塔

题意

5302 金字塔 0x50「动态规划」例题

描述

虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。首先,金字塔由若干房间组成,房间之间连有通道。如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。并且,每个房间的墙壁都涂有若干种颜色的一种。

探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。最后,机器人会从入口退出金字塔。

显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。因为结果可能会非常大,你只需要输出答案对10^9 取模之后的值。

输入格式

输入文件包含一行,一个字符串S,长度不超过300,表示机器人得到的颜色序列。

输出格式

输出一个整数表示答案。

样例输入

ABABABA

样例输出

5

样例解释

例如序列“ABABABA”对应5种金字塔结构,最底部是树根。我们认为子树之间是有序的,所以方案3和4是两种不同的方案。如书中图所示。

来源

原创

        </article>

给出一个欧拉序(只要到达每一个结点就把他加进序列的那种,编号会重复),求有多少树的分布。

分析

我们可以发现这里每个子树的字母是连续的,所以我们可以用\(f[i,j]\)表示\(i∼j\)这个区间组成树的方案树。然后每次我们考虑左右各增加一个字母,然后将其重新分割,得出状态转移方程。

子串S[l,r]对应一棵子树。S[l+1]和S[r-1]就是进入和离开时产生的。[l, r]区间对应一个子问题。

把子串S[l,r]分成两部分,每一部分由若干棵子树组成。为了计数不重不漏,只考虑S[l,r]的第一棵子树是由哪一段构成的。枚举划分点k,S[l+1,k-1]构成[l, r]的第一棵子树。如果k不相同,那么子串S[l+1,k-1]代表的子树的大小也不相同,就不可能产生重复计算的结构。

对于方案技术类的动态规划问题,通常一个状态的各个决策之间满足“加法原理”, 而每个决策划分的几个子状态之间满足“乘法原理”。在设计状态转移方程的决策方式与划分方法时,一个状态的所有决策之间必须具有互斥性,才能保证不会出现重复问题。

代码

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=302,mod=1e9;
char s[N];
int f[N][N];
int solve(int l,int r){
if(l>r) return 0;
if(l==r) return 1;
if(f[l][r]!=-1) return f[l][r];
f[l][r]=0;
if(s[l]!=s[r]) return 0;
for(int k=l+2;k<=r;++k)
f[l][r]=(f[l][r]+(ll)solve(l+1,k-1)*solve(k,r))%mod;
return f[l][r];
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
scanf("%s",s+1);
memset(f,-1,sizeof f);
printf("%d\n",solve(1,strlen(s+1)));
return 0;
}

POJ3585 Accumulation Degree

题意

Language:Default
Accumulation Degree
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 3986 Accepted: 984

Description

Trees are an important component of the natural landscape because of their prevention of erosion and the provision of a specific ather-sheltered ecosystem in and under their foliage. Trees have also been found to play an important role in producing oxygen and reducing carbon dioxide in the atmosphere, as well as moderating ground temperatures. They are also significant elements in landscaping and agriculture, both for their aesthetic appeal and their orchard crops (such as apples). Wood from trees is a common building material.

Trees also play an intimate role in many of the world's mythologies. Many scholars are interested in finding peculiar properties about trees, such as the center of a tree, tree counting, tree coloring. A(x) is one of such properties.

A(x) (accumulation degree of node x) is defined as follows:

  1. Each edge of the tree has an positive capacity.
  2. The nodes with degree of one in the tree are named terminals.
  3. The flow of each edge can't exceed its capacity.
  4. A(x) is the maximal flow that node x can flow to other terminal nodes.

Since it may be hard to understand the definition, an example is showed below:

常规DP专题练习

A(1)=11+5+8=24
Details: 1->2 11
  1->4->3 5
  1->4->5 8(since 1->4 has capacity of 13)
A(2)=5+6=11
Details: 2->1->4->3 5
  2->1->4->5 6
A(3)=5
Details: 3->4->5 5
A(4)=11+5+10=26
Details: 4->1->2 11
  4->3 5
  4->5 10
A(5)=10
Details: 5->4->1->2 10

The accumulation degree of a tree is the maximal accumulation degree among its nodes. Here your task is to find the accumulation degree of the given trees.

Input

The first line of the input is an integer T which indicates the number of test cases. The first line of each test case is a positive integer n. Each of the following n - 1 lines contains three integers x, y, z separated by spaces, representing there is an edge between node x and node y, and the capacity of the edge is z. Nodes are numbered from 1 to n.
All the elements are nonnegative integers no more than 200000. You may assume that the test data are all tree metrics.

Output

For each test case, output the result on a single line.
 

Sample Input

1
5
1 2 11
1 4 13
3 4 5
4 5 10

Sample Output

26

Source

给定一棵无根树,给定每条边的容量,找一个点使得,使得从这个点出发作为源点,发出的流量最大,输出这个最大的流量。

分析

参照coldfresh的题解。

以前的树规都是定点的,就是我们确定了根,或者说选择哪个点作为根都可以,但是这个题就是不一样了,

设\(D[x]\)为以x为根的子树中,把x作为源点,从x发出的流量的最大值,这个很用dfs扫描一次可以得出。

转移方程:

\[D[x]=\sum_{y∈son(x)}degree[y]==1?c(x,y):\min(D[y],c(x,y))
\]

设\(F[x]\)为以x作为整棵树的根是所求的答案,也只要dfs在扫描一次。

易知:\(D[root]==F[root]\)

转移过程:

\[F[y]=D[y]+degree[x]==1?c(x,y):\min(F[x]−\min(D[y],c(x,y)),c(x,y))
\]

所以二次扫描的名字由此得来.也的确是逻辑上把根换了。

时间复杂度\(O(n)\)

代码

#include<iostream>
#include<algorithm>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int N=2e5+1;
int n,deg[N],tot,head[N],to[N*2],nx[N*2],cp[N*2];
void add(int x,int y,int z){
to[++tot]=y,cp[tot]=z;
nx[tot]=head[x],head[x]=tot;
}
int d[N],f[N];
void down(int x,int fa){
d[x]=0;
for(int i=head[x],y;i;i=nx[i]){
if((y=to[i])==fa) continue;
down(y,x);
if(deg[y]==1) d[x]+=cp[i];
else d[x]+=min(d[y],cp[i]);
}
}
void up(int x,int fa){
for(int i=head[x],y;i;i=nx[i]){
if((y=to[i])==fa) continue;
if(deg[x]==1) f[y]=d[y]+cp[i];
else f[y]=d[y]+min(f[x]-min(d[y],cp[i]),cp[i]);
up(y,x);
}
}
void Accumulation_Degree(){
read(n),tot=0;
fill(head+1,head+n+1,0);
fill(deg+1,deg+n+1,0);
for(int i=1,x,y,z;i<n;++i){
read(x),read(y),read(z);
add(x,y,z),add(y,x,z);
++deg[x],++deg[y];
}
fill(f+1,f+n+1,0);
fill(d+1,d+n+1,0);
down(1,0);
f[1]=d[1];
up(1,0);
int ans=0;
for(int i=1;i<=n;++i)
ans=max(ans,f[i]);
printf("%d\n",ans);
}
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
for(int kase=read<int>();kase--;)
Accumulation_Degree();
return 0;
}

POJ2228 Naptime

题意

Language:Default
Naptime
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 3649 Accepted: 1350

Description

Goneril is a very sleep-deprived cow. Her day is partitioned into N (3 <= N <= 3,830) equal time periods but she can spend only B (2 <= B < N) not necessarily contiguous periods in bed. Due to her bovine hormone levels, each period has its own utility U_i (0 <= U_i <= 200,000), which is the amount of rest derived from sleeping during that period. These utility values are fixed and are independent of what Goneril chooses to do, including when she decides to be in bed.



With the help of her alarm clock, she can choose exactly which periods to spend in bed and which periods to spend doing more critical items such as writing papers or watching baseball. However, she can only get in or out of bed on the boundaries of a period.



She wants to choose her sleeping periods to maximize the sum of the utilities over the periods during which she is in bed. Unfortunately, every time she climbs in bed, she has to spend the first period falling asleep and gets no sleep utility from that period.



The periods wrap around in a circle; if Goneril spends both periods N and 1 in bed, then she does get sleep utility out of period 1.



What is the maximum total sleep utility Goneril can achieve?

Input

* Line 1: Two space-separated integers: N and B



* Lines 2..N+1: Line i+1 contains a single integer, U_i, between 0 and 200,000 inclusive

Output

The day is divided into 5 periods, with utilities 2, 0, 3, 1, 4 in that order. Goneril must pick 3 periods.

Sample Input

5 3
2
0
3
1
4

Sample Output

6

Hint

INPUT DETAILS:



The day is divided into 5 periods, with utilities 2, 0, 3, 1, 4 in that order. Goneril must pick 3 periods.



OUTPUT DETAILS:



Goneril can get total utility 6 by being in bed during periods 4, 5, and 1, with utilities 0 [getting to sleep], 4, and 2 respectively.

Source

一天有n个时间,有一只牛希望一天可以休息睡小时。如果牛在第i时刻已经熟睡,他可以得到ui的休息。但是如果他在i时刚刚入睡,他不能得到休息。牛可以从前一天晚上睡到第二天。睡觉时间也不一定连续。问如何安排睡觉时间,可以使牛得到的休息最大。

分析

参照wyboooo的题解。

如果牛休息的时间不能从前一天跨越到第二天的话,就是一道典型的线性DP

我们先假设不能跨越,那么第1个小时一定得不到休息。用dp[i][j][0]和dp[i][j][1]分别表示,在第i时刻休息了j小时并且第i时刻在睡觉,和在第i时刻休息了j小时并且第i时刻不在睡觉的最大休息值。跑一遍DP,在dp[n][b][0], dp[n][b][1]中选择最优解。

这种假设的情况下,我们可以发现和题意原来的意思就差了第1个小时的时候。那么我们强制令第n个时刻和第1个时刻都在睡觉,也就是说第1个时刻可以得到休息值。再跑一遍DP,把dp[n][b][1]和之前的最优解比较取最优就是答案

本题的解法本质上是把问题拆成了两部分。这两部分合起来可以覆盖整个问题。无论是哪一部分,因为第n小时和第1小时之间的特殊关系被确定,我们就可以把环拆开,用线性DP计算。

时间复杂度\(O(nm)\)

代码

卡着开空间的话,会使用58.706244MB,不会炸内存。但为什么不练一练滚动数组呢?

#include<iostream>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using std::max; co int N=4e3;
int n,m,f[2][N][2],w[N],ans;
int main(){
// freopen(".in","r",stdin),freopen(".out","w",stdout);
read(n),read(m);
for(int i=1;i<=n;++i) read(w[i]);
if(m==0) return puts("0"),0;
memset(f,0x80,sizeof f);
f[1&1][0][0]=0;
f[1&1][1][1]=0;
for(int i=2;i<=n;++i)
for(int j=0;j<=m;++j){
f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
if(j>=1) f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+w[i]);
}
ans=max(f[n&1][m][1],f[n&1][m][0]);
memset(f,0x80,sizeof f);
f[1&1][1][1]=w[1];
for(int i=2;i<=n;++i)
for(int j=0;j<=m;++j){
f[i&1][j][0]=max(f[(i-1)&1][j][0],f[(i-1)&1][j][1]);
if(j>=1) f[i&1][j][1]=max(f[(i-1)&1][j-1][0],f[(i-1)&1][j-1][1]+w[i]);
}
ans=max(ans,f[n&1][m][1]);
return printf("%d\n",ans),0;
}

CH5E01 乌龟棋

5E01 乌龟棋 0x5E「动态规划」练习

描述

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。

乌龟棋的棋盘是一行N 个格子,每个格子上一个分数(非负整数)。棋盘第1 格是唯一

的起点,第N 格是终点,游戏要求玩家控制一个乌龟棋子从起点出发走到终点。

1 2 3 4 5 …… N

乌龟棋中M 张爬行卡片,分成4 种不同的类型(M 张卡片中不一定包含所有4 种类型

的卡片,见样例),每种类型的卡片上分别标有1、2、3、4 四个数字之一,表示使用这种卡

片后,乌龟棋子将向前爬行相应的格子数。游戏中,玩家每次需要从所有的爬行卡片中选择

一张之前没有使用过的爬行卡片,控制乌龟棋子前进相应的格子数,每张卡片只能使用一次。

游戏中,乌龟棋子自动获得起点格子的分数,并且在后续的爬行中每到达一个格子,就得到

该格子相应的分数。玩家最终游戏得分就是乌龟棋子从起点到终点过程中到过的所有格子的

分数总和。

很明显,用不同的爬行卡片使用顺序会使得最终游戏的得分不同,小明想要找到一种卡

片使用顺序使得最终游戏得分最多。

现在,告诉你棋盘上每个格子的分数和所有的爬行卡片,你能告诉小明,他最多能得到

多少分吗?

输入格式

输入文件的每行中两个数之间用一个空格隔开。

第1 行2 个正整数N和M,分别表示棋盘格子数和爬行卡片数。

第2 行N 个非负整数,a1,a2,……,aN,其中ai 表示棋盘第i个格子上的分数。

第3 行M 个整数,b1,b2, ……, bM,表示M张爬行卡片上的数字。

输入数据保证到达终点时刚好用光M张爬行卡片

输出格式

输出只有1 行,1 个整数,表示小明最多能得到的分数。

样例输入

输入样例1
9 5
6 10 14 2 8 8 18 5 17
1 3 1 2 1 输入样例2
13 8
4 96 10 64 55 13 94 53 5 24 89 8 30
1 1 1 1 1 2 4 1

样例输出

输出样例1
73 输出样例2
455

数据范围与约定

  • 对于30%的数据有1 ≤ N≤ 30,1 ≤M≤ 12。
  • 对于50%的数据有1 ≤ N≤ 120,1 ≤M≤ 50,且4 种爬行卡片,每种卡片的张数不会超过20。
  • 对于100%的数据有1 ≤ N≤ 350,1 ≤M≤ 120,且4 种爬行卡片,每种卡片的张数不会
  • 超过40;0 ≤ ai ≤ 100,1 ≤ i ≤ N;1 ≤ bi ≤ 4,1 ≤ i ≤M。

来源

CCF NOIP2010 T2

        </article>

这种分步决策的问题先考虑动态规划。。由于有4种不同的卡片,所以我们肯定要在状态上把这个反映出来,要不然显然转移不了状态。。还有一点就是我们知道了四种卡片用了的数量我们自然就可以知道走了多少步,这样一来问题就很简单了。f[x1][x2][x3][x4]表示四种卡片分别用了x1,x2,x3,x4张时的最大得分,容易得到f[x1][x2][x3][x4]=max(f[x1-1][x2][x3][x4],f[x1][x2-1][x3][x4],f[x1][x2][x3-1][x4],f[x1][x2][x3][x4-1])+a[x1+x2*2+x3*3+x4*4]。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using std::max; int n,m,a[401],c[5],f[41][41][41][41];
int main(){
read(n),read(m);
for(int i=1;i<=n;++i) read(a[i]);
for(int i=1;i<=m;++i) ++c[read<int>()];
f[0][0][0][0]=a[1];
for(int i=0;i<=c[1];++i)
for(int j=0;j<=c[2];++j)
for(int k=0;k<=c[3];++k)
for(int l=0;l<=c[4];++l){
int t=a[1+i+2*j+3*k+4*l];
if(i) f[i][j][k][l]=max(f[i][j][k][l],f[i-1][j][k][l]+t);
if(j) f[i][j][k][l]=max(f[i][j][k][l],f[i][j-1][k][l]+t);
if(k) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k-1][l]+t);
if(l) f[i][j][k][l]=max(f[i][j][k][l],f[i][j][k][l-1]+t);
}
printf("%d\n",f[c[1]][c[2]][c[3]][c[4]]);
return 0;
}

CH5E02 花店橱窗

5E02 花店橱窗 0x5E「动态规划」练习

背景

xq和他的老婆xz最近开了一家花店,他们准备把店里最好看的花都摆在橱窗里。但是他们有很多花瓶,每个花瓶都具有各自的特点,因此,当各个花瓶中放入不同的花束时,会产生不同的美学效果。为了使橱窗里的花摆放的最合适,他们得想个办法安排每种花的摆放位置。

可是因为xq和xz每天都太忙,没有时间设计橱窗里花的摆法,所以他们想让你帮他们求出花摆放的最大美观程度和每种花所放的位置。

描述

每种花都有一个标识,假设杜鹃花的标识数为1,秋海棠的标识数为2,康乃馨的标识数为3,所有的花束在放入花瓶时必须保持其标识数的顺序,即:杜鹃花必须放在秋海棠左边的花瓶中,秋海棠必须放在康乃馨左边的花瓶中。如果花瓶的数目大于花束的数目。则多余的花瓶必须空置,且每个花瓶中只能放一束花。

每种花放在不同的瓶子里会产生不同的美观程度,美观程度可能是正数也可能是负数。

上述例子中,花瓶与花束的不同搭配所具有的美观程度,如下表所示:

花    瓶

1     2    3    4    5

       1 (杜鹃花)     7    23   -5  -24   16

       2 (秋海棠)     5    21   -4   10   23

       3 (康乃馨)    -21    5   -4  -20   20

根据上表,杜鹃花放在花瓶2中,会显得非常好看;但若放在花瓶4中则显得十分难看。

为取得最大美观程度,你必须在保持花束顺序的前提下,使花束的摆放取得最大的美学值,并求出每种花应该摆放的花瓶的编号。

输入格式

第1行:两个整数F和V,表示xq和xz一共有F种花,V个花瓶。(1<=F<=V<=100)

第2行到第F+1行:每行有V个数,表示花摆放在不同花瓶里的美观程度值value。(美观程度和不超过maxint,美观程度有正有负)

输出格式

输出有两行:第一行为输出最大美观程度和的值,第二行有F个数表示每朵花应该摆放的花瓶的编号。若有多种方案,输出字典序较小的(美观程度不变的情况下,花尽量往前放)

样例输入

3 5
7 23 -5 -24 16
5 21 -4 10 23
-21 5 -4 -20 20

样例输出

53
2 4 5
        </article>

这道题其实很明显是一道DP题。

我们不妨设dp[i][j]表示前i束花放在第j个瓶(或最大为j编号的瓶)中的最大美学值。

那么我们便可以用三重循环枚举,第一重循环枚举花束,第二重循环枚举瓶数,第三重循环枚举之前的花瓶

那么我们便可以得到以下方程

dp[i][j]=max(dp[i][j],dp[i-1]][k]+a[i][j]);//k为之前的花瓶

那么我们这里还有一个问题:如何输出瓶数?

这里,我们需要一个数组x,表示在dp的最有值得情况下的瓶子下标

对这个数组进行一系列处理便可

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=101,INF=0x3f3f3f3f;
int n,m,a[N][N],f[N][N],d[N][N],ans[N];
int main(){
read(n),read(m);
for(int i=1;i<=n;++i)
for(int j=1;j<=m;++j) read(a[i][j]);
for(int i=1;i<=m-n+1;++i) f[1][i]=a[1][i];
for(int i=2;i<=n;++i){
int k=-INF,t;
for(int j=i;j<=m-n+i;++j){
if(k<f[i-1][j-1]) k=f[i-1][j-1],t=j-1;
f[i][j]=k+a[i][j],d[i][j]=t;
}
}
int k=-INF,t;
for(int i=n;i<=m;++i)
if(k<f[n][i]) k=f[n][i],t=i;
printf("%d\n",k);
for(int w=n;t;t=d[w--][t]) ans[w]=t;
for(int i=1;i<=n;++i) printf("%d ",ans[i]);
return 0;
}

POJ1952 BUY LOW, BUY LOWER

Language:Default
BUY LOW, BUY LOWER
Time Limit: 1000MS Memory Limit: 30000K
Total Submissions: 11334 Accepted: 3987

Description

The advice to "buy low" is half the formula to success in the bovine stock market.To be considered a great investor you must also follow this problems' advice:

                    "Buy low; buy lower"

Each time you buy a stock, you must purchase it at a lower price than the previous time you bought it. The more times you buy at a lower price than before, the better! Your goal is to see how many times you can continue purchasing at ever lower prices.



You will be given the daily selling prices of a stock (positive 16-bit integers) over a period of time. You can choose to buy stock on any of the days. Each time you choose to buy, the price must be strictly lower than the previous time you bought stock. Write a program which identifies which days you should buy stock in order to maximize the number of times you buy.



Here is a list of stock prices:

 Day   1  2  3  4  5  6  7  8  9 10 11 12

Price 68 69 54 64 68 64 70 67 78 62 98 87

The best investor (by this problem, anyway) can buy at most four times if each purchase is lower then the previous purchase. One four day sequence (there might be others) of acceptable buys is:

Day    2  5  6 10

Price 69 68 64 62

Input

* Line 1: N (1 <= N <= 5000), the number of days for which stock prices are given



* Lines 2..etc: A series of N space-separated integers, ten per line except the final line which might have fewer integers.

Output

Two integers on a single line:

* The length of the longest sequence of decreasing prices

* The number of sequences that have this length (guaranteed to fit in 31 bits)



In counting the number of solutions, two potential solutions are considered the same (and would only count as one solution) if they repeat the same string of decreasing prices, that is, if they "look the same" when the successive prices are compared. Thus, two different sequence of "buy" days could produce the same string of decreasing prices and be counted as only a single solution.

Sample Input

12
68 69 54 64 68 64 70 67 78 62
98 87

Sample Output

4 2

Source

如何做到题目中所说的去重呢?方法是若前面出现了一个j,并且a[j]=a[i],f[j]=f[i]就从统计方案数中break。必要性显然。充分性可以注意到这种方式没有完全统计最长的方案数, 而是在相同值处进行了转移的划分。又要实现高精度……时间复杂度\(O(n^2 s)\)

#include<iostream>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int N=5e3+2;
int n,a[N],f[N],d[N][206];
void work(int x,int y){
d[x][0]=max(d[x][0],d[y][0]);
for(int i=1;i<=d[x][0];++i){
d[x][i]+=d[y][i];
d[x][i+1]+=d[x][i]/10,d[x][i]%=10;
}
if(d[x][d[x][0]+1]) ++d[x][0];
}
int main(){
read(n);
for(int i=1;i<=n;++i) read(a[i]);
d[0][0]=d[0][1]=1;
a[0]=0x7fffffff;
for(int i=1;i<=n;++i){
for(int j=i-1;j>=0;--j)
if(a[j]>a[i]) f[i]=max(f[i],f[j]+1);
for(int j=i-1;j>=0;--j){
if(a[j]>a[i]&&f[i]==f[j]+1) work(i,j);
if(a[i]==a[j]&&f[i]==f[j]) break;
}
}
int z=0;
for(int i=1;i<=n;++i) z=max(z,f[i]);
for(int i=1;i<=n;++i)if(f[i]==z) work(N-1,i);
printf("%d ",z);
for(int i=d[N-1][0];i;--i) printf("%d",d[N-1][i]);
return 0;
}

POJ1934 Trip

Language:Default
Trip
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 3989 Accepted: 1076

Description

Alice and Bob want to go on holiday. Each of them has planned a route, which is a list of cities to be visited in a given order. A route may contain a city more than once.

As they want to travel together, they have to agree on a common route. None wants to change the order of the cities on his or her route or add other cities. Therefore they have no choice but to remove some cities from the route. Of course the common route should be as long as possible.

There are exactly 26 cities in the region. Therefore they are encoded on the lists as lower case letters from 'a' to 'z'.

Input

The input consists of two lines; the first line is Alice's list, the second line is Bob's list.

Each list consists of 1 to 80 lower case letters with no spaces inbetween.

Output

The output should contain all routes that meet the conditions described above, but no route should be listed more than once. Each route should be printed on a separate line. There is at least one such non-empty route, but never more than 1000 different ones. Output them in ascending order.

Sample Input

abcabcaa
acbacba

Sample Output

ababa
abaca
abcba
acaba
acaca
acbaa
acbca

Source

如何输出方案?方法是使用费严格定义的dp数组,用dfs搜出所有方案。预处理出每个位置之前最近的26个字母的位置,枚举转移搜索。由于每次搜索都查找的是最近的某一个字母,所以不会有重复方案。

#include<iostream>
#include<algorithm>
#include<string>
#define rg register
#define il inline
#define co const
using namespace std; co int N=106,M=1006,X=26;
int f[N][N],fa[N][X],fb[N][X],t=0,z=0; // base 1
string a,b,s[M];
void dp(int x,int y,string w,int l){
if((int)w.size()==z){
s[++t]=w;
return;
}
if(x<0||y<0) return;
if(a[x]==b[y]){
dp(x-1,y-1,a[x]+w,l-1);
return;
}
for(int i=0;i<X;++i)
if(f[fa[x+1][i]][fb[y+1][i]]==l)
dp(fa[x+1][i]-1,fb[y+1][i]-1,w,l);
}
int main(){
ios::sync_with_stdio(0);
cin>>a>>b;
for(unsigned i=0;i<a.size();++i)
for(unsigned j=0;j<b.size();++j){
if(a[i]==b[j]) f[i+1][j+1]=f[i][j]+1;
else f[i+1][j+1]=max(f[i][j+1],f[i+1][j]);
z=max(z,f[i+1][j+1]);
}
for(unsigned i=0;i<a.size();++i)
for(int j=0;j<X;++j)
if(a[i]!=j+'a') fa[i+1][j]=fa[i][j];
else fa[i+1][j]=i+1;
for(unsigned i=0;i<b.size();++i)
for(int j=0;j<X;++j)
if(b[i]!=j+'a') fb[i+1][j]=fb[i][j];
else fb[i+1][j]=i+1;
dp(a.size()-1,b.size()-1,"",z);
sort(s+1,s+t+1);
for(int i=1;i<=t;++i) cout<<s[i]<<endl;
return 0;
}

POJ1722 SUBTRACT

Language:Default
SUBTRACT
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 2112 Accepted: 935 Special Judge

Description

We are given a sequence of N positive integers a = [a1, a2, ..., aN] on which we can perform contraction operations.

One contraction operation consists of replacing adjacent elements ai and ai+1 by their difference ai-ai+1. For a sequence of N integers, we can perform exactly N-1 different contraction operations, each of which results in a new (N-1) element sequence.



Precisely, let con(a,i) denote the (N-1) element sequence obtained from [a1, a2, ..., aN] by replacing the elements ai and ai+1 by a single integer ai-ai+1 :



con(a,i) = [a1, ..., ai-1, ai-ai+1, ai+2, ..., aN]

Applying N-1 contractions to any given sequence of N integers obviously yields a single integer.

For example, applying contractions 2, 3, 2 and 1 in that order to the sequence [12,10,4,3,5] yields 4, since :

con([12,10,4,3,5],2) = [12,6,3,5]

con([12,6,3,5] ,3) = [12,6,-2]

con([12,6,-2] ,2) = [12,8]

con([12,8] ,1) = [4]

Given a sequence a1, a2, ..., aN and a target number T, the problem is to find a sequence of N-1 contractions that applied to the original sequence yields T.

Input

The first line of the input contains two integers separated by blank character : the integer N, 1 <= N <= 100, the number of integers in the original sequence, and the target integer T, -10000 <= T <= 10000.

The following N lines contain the starting sequence : for each i, 1 <= i <= N, the (i+1)st line of the input file contains integer ai, 1 <= ai <= 100.

Output

Output should contain N-1 lines, describing a sequence of contractions that transforms the original sequence into a single element sequence containing only number T. The ith line of the output file should contain a single integer denoting the ith contraction to be applied.

You can assume that at least one such sequence of contractions will exist for a given input.

Sample Input

5 4
12
10
4
3
5

Sample Output

2
3
2
1

Source

执行n-1次操作后,最后得到的值实际上可以表示成这样:a1 - a2 [] a3 [] a4...... [] an,[]内填上+或者-。

‘+’号操作就是选该元素向左看遇到的第一个‘-’,然后将这个地方先进行t次‘-’,然后再在这之前进行一次‘-’,如,a1-a2+a3+a4就是a1-(a2-a3-a4),就是先对2这个位置进行2次减法,在对1这个位置进行1次减法。

所以说,用dp[i][j]代表第i位运算结果为j的状态是否可达,并且用pre[i][j]记录它的上一个状态,因为T可正可负,所以需要给j加上一个常数使它变成正数。最后,根据pre数组推算每一个位置是‘+’还是‘-’,再根据上面说的把它全改成‘-’,输出就行了。(如果从右边开始看,开始输出,就不用考虑数组变短所带来的下标偏差了)

#include<iostream>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=106,T=20006,X=10000;
int n,t,a[N],f[N][T],o[N];
int main(){
read(n),read(t);
t+=X;
for(int i=1;i<=n;++i) read(a[i]);
f[2][a[1]-a[2]+X]=-1;
for(int i=2;i<n;++i)
for(int j=0;j<=X<<1;++j)if(f[i][j]){
if(j+a[i+1]<=X<<1) f[i+1][j+a[i+1]]=1;
if(j-a[i+1]>=0) f[i+1][j-a[i+1]]=-1;
}
for(int i=n;i>1;--i)
if((o[i]=f[i][t])==1) t-=a[i];
else t+=a[i];
int cnt=0;
for(int i=2;i<=n;++i)
if(o[i]==1) printf("%d\n",i-cnt++-1);
for(int i=2;i<=n;++i)
if(o[i]==-1) puts("1");
return 0;
}

POJ1187 陨石的秘密

Language:Default
陨石的秘密
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 2726 Accepted: 1042

Description

公元11380年,一颗巨大的陨石坠落在南极。于是,灾难降临了,地球上出现了一系列反常的现象。当人们焦急万分的时候,一支中国科学家组成的南极考察队赶到了出事地点。经过一番侦察,科学家们发现陨石上刻有若干行密文,每一行都包含5个整数:

1 1 1 1 6

0 0 6 3 57

8 0 11 3 2845

著名的科学家SS发现,这些密文实际上是一种复杂运算的结果。为了便于大家理解这种运算,他定义了一种SS表达式:

1. SS表达式是仅由'{','}','[',']','(',')'组成的字符串。

2. 一个空串是SS表达式。

3. 如果A是SS表达式,且A中不含字符'{','}','[',']',则(A)是SS表达式。

4. 如果A是SS表达式,且A中不含字符'{','}',则[A]是SS表达式。

5. 如果A是SS表达式,则{A}是SS表达式。

6. 如果A和B都是SS表达式,则AB也是SS表达式。





例如

()(())[]

{()[()]}

{{[[(())]]}}

都是SS表达式。



()([])()

[()

不是SS表达式。



一个SS表达式E的深度D(E)定义如下:

常规DP专题练习

例如(){()}[]的深度为2。



密文中的复杂运算是这样进行的:

设密文中每行前4个数依次为L1,L2,L3,D,求出所有深度为D,含有L1对{},L2对[],L3对()的SS串的个数,并用这个数对当前的年份11380求余数,这个余数就是密文中每行的第5个数,我们称之为?神秘数?。

密文中某些行的第五个数已经模糊不清,而这些数字正是揭开陨石秘密的钥匙。现在科学家们聘请你来计算这个神秘数。

Input

共一行,4个整数L1,L2,L3,D。相邻两个数之间用一个空格分隔。

(0 <= L1 <= 10,0 <= L2 <= 10,0 <= L3 <= 10,0 <= D <= 30)

Output

共一行,包含一个整数,即神秘数。

Sample Input

1 1 1 2

Sample Output

8

Source

用d[i][j][u][v]表示i个{},j个[],u个(),深度为v的序列数,根据最后一个子括号(就是最后一个反括号对应的正括号范围内的括号)分类计数。

最后一个子括号可能深度小于v,此时前面的括号深度必须为v,最后一个子括号深度也可能等于v,此时前面的括号深度可以小于或等于v。

用sum[i][j][u][v]表示i个{},j个[],u个(),深度小于等于v的序列数。没递推完i,j,u后,立即计算相应的sum[i][j][u][0…D]的值。

枚举最后一个子括号内有哪些括号完成递推。

有一个直观的转移:套一个括号和两部分合并。

但是这样会有问题:ABC这种SS串会由于合并AB,C和合并A,BC而重复计数。

所以我们换个转移:(A)B,即枚举一部分套一个括号。这样可以同时处理两种情况,不重不漏。

考虑在套最外面的是什么.

若当前枚举到的是i个"()",j个"[]",k个"{}",

那么当最外面是"()"时,方案数就应是(0,0,i−1,d−1)∗(a,b,c−i,d)(a,b,c为dfs时的状态)

而最外面是"[]"时,就是(0,j−1,i,d−1)∗(a,b−j,c−i,d),

同理,最外面是"{}"时,就是(k−1,j,i,d−1)∗(a−k,b−j,c−i,d),

时间复杂度\(O((l_1l_2l_3)^2 d)\)

#include<iostream>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int L=11,D=31,mod=11380;
int f[L][L][L][D],s[L][L][L][D];
int main(){
int l1,l2,l3,d;
read(l1),read(l2),read(l3),read(d);
f[0][0][0][0]=s[0][0][0][0]=1;
for(int i=0;i<=l1;++i)for(int j=0;j<=l2;++j)for(int k=0;k<=l3;++k){
for(int l=1;l<=d;++l)if(i+j+k>=l){
int&x=f[i][j][k][l];
if(i) x=f[i-1][j][k][l-1];
else if(j) x=f[0][j-1][k][l-1];
else if(k) x=f[0][0][k-1][l-1];
else x=0;
for(int ii=0;ii<=i;++ii)for(int jj=0;jj<=j;++jj)for(int kk=0;kk<=k;++kk)
if(ii+jj+kk&&ii+jj+kk!=i+j+k){
if(ii){
if(l>=2) x=(x+s[ii-1][jj][kk][l-2]*f[i-ii][j-jj][k-kk][l])%mod;
x=(x+f[ii-1][jj][kk][l-1]*s[i-ii][j-jj][k-kk][l])%mod;
}
else if(jj){
if(l>=2) x=(x+s[0][jj-1][kk][l-2]*f[i][j-jj][k-kk][l])%mod;
x=(x+f[0][jj-1][kk][l-1]*s[i][j-jj][k-kk][l])%mod;
}
else{
if(l>=2) x=(x+s[0][0][kk-1][l-2]*f[i][j][k-kk][l])%mod;
x=(x+f[0][0][kk-1][l-1]*s[i][j][k-kk][l])%mod;
}
}
}
for(int l=1;l<=d;++l) s[i][j][k][l]=(s[i][j][k][l-1]+f[i][j][k][l])%mod;
}
printf("%d\n",f[l1][l2][l3][d]);
return 0;
}

CH5E07 划分大理石

5E07 划分大理石 0x5E「动态规划」练习

描述

有价值分别为1..6的大理石各a[1..6]块,现要将它们分成两部分,使得两部分价值之和相等,问是否可以实现。其中大理石的总数不超过20000。

输入格式

有多组数据!

所以可能有多行

如果有0 0 0 0 0 0表示输入文件结束

其余的行为6个整数

输出格式

有多少行可行数据就有几行输出

如果划分成功,输出Can,否则Can't

样例输入

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

样例输出

Can't
Can
Can't
Can't
Can
        </article>

多重背包二进制划分。随便翻了下别人的代码,学习了一种倍增写法。

为什么bitset比循环慢?报警了。

#include<bits/stdc++.h>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; int a[7];
bitset<60001> f;
int main(){
while(1){
int s=0;
for(int i=1;i<=6;++i) s+=read(a[i])*i;
if(!s) break;
if(s&1) {puts("Can't");continue;}
f.reset();
f[0]=1;
for(int i=1;i<=6;++i){
for(int p=1;a[i]>=p;a[i]-=p) f|=f<<i*p;
if(a[i]) f|=f<<i*a[i];
}
puts(f[s/2]?"Can":"Can't");
}
return 0;
}

POJ2176 Folding

Language:Default
Folding
Time Limit: 1000MS Memory Limit: 65536K
Total Submissions: 1928 Accepted: 671 Special Judge

Description

Bill is trying to compactly represent sequences of capital alphabetic characters from 'A' to 'Z' by folding repeating subsequences inside them. For example, one way to represent a sequence AAAAAAAAAABABABCCD is 10(A)2(BA)B2(C)D. He formally defines folded sequences of characters along with the unfolding transformation for them in the following way:

  • A sequence that contains a single character from 'A' to 'Z' is considered to be a folded sequence. Unfolding of this sequence produces the same sequence of a single character itself.
  • If S and Q are folded sequences, then SQ is also a folded sequence. If S unfolds to S' and Q unfolds to Q', then SQ unfolds to S'Q'.
  • If S is a folded sequence, then X(S) is also a folded sequence, where X is a decimal representation of an integer number greater than 1. If S unfolds to S', then X(S) unfolds to S' repeated X times.

According to this definition it is easy to unfold any given folded sequence. However, Bill is much more interested in the reverse transformation. He wants to fold the given sequence in such a way that the resulting folded sequence contains the least possible number of characters.



Input

The input contains a single line of characters from 'A' to 'Z' with at least 1 and at most 100 characters.

Output

Write to the output a single line that contains the shortest possible folded sequence that unfolds to the sequence that is given in the input file. If there are many such sequences then write any one of them.

Sample Input

AAAAAAAAAABABABCCD

Sample Output

9(A)3(AB)CCD

Source

给一个字符串,尽量压缩,让他长度最短。()和数字都是算长度的。所以样例里CC才没有变成2(C)

能够想到的是子结构是保存区间i,j中最短的串的长度len,以及这个最短的串

状态转移的时候我们有两种操作,一种就是简单的找一个中间的点,把两边的串合并。这个比较简单。

一种是看这个串能如何压缩。于是我们可以去枚举最后压缩了之后的子串的长度,不包括数字和括号。

对于一个区间(i, j)我们从小到大枚举压缩后的子串长度,因为压缩的越小越好。压缩完成后去比较是压缩比较好还是合并比较好。

每一次枚举区间长度和起始点。

#include<iostream>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll; co int N=106,INF=0x3f3f3f3f;
struct F{
char s[N];
int len;
}f[N][N];
char s[N];
int n;
int main(){
scanf("%s",s),n=strlen(s);
for(int i=0;i<n;++i) f[i][i].s[0]=s[i],f[i][i].len=1;
for(int len=2;len<=n;++len)
for(int l=0;l+len<=n;++l){
int r=l+len-1;
f[l][r].len=INF;
for(int i=1;i<=len>>1;++i)if(len%i==0){
int L=l,R=l+i;
while(R<=r&&s[L]==s[R]) ++L,++R;
if(R>r){
sprintf(f[l][r].s,"%d(",len/i);
strcat(f[l][r].s,f[l][l+i-1].s);
strcat(f[l][r].s,")");
f[l][r].len=strlen(f[l][r].s);
break;
}
}
for(int i=l;i<=r;++i)if(f[l][r].len>f[l][i].len+f[i+1][r].len){
f[l][r].len=f[l][i].len+f[i+1][r].len;
strcpy(f[l][r].s,f[l][i].s);
strcat(f[l][r].s,f[i+1][r].s);
}
}
puts(f[0][n-1].s);
return 0;
}

POJ1191 棋盘分割

Language:Default
棋盘分割
Time Limit: 1000MS Memory Limit: 10000K
Total Submissions: 16561 Accepted: 5945

Description

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

常规DP专题练习

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

均方差常规DP专题练习,其中平均值常规DP专题练习,xi为第i块矩形棋盘的总分。

请编程对给出的棋盘及n,求出O'的最小值。

Input

第1行为一个整数n(1 < n < 15)。

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

Output

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

Sample Input

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

Sample Output

1.633

Source

常规DP专题练习

这种题打个搜索应该也能过吧。

#include<iostream>
#include<cmath>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int INF=0x3f3f3f3f;
int s[9][9],f[15][9][9][9][9];
int calc(int k,int x1,int y1,int x2,int y2){
int ans=INF;
for(int i=x1+1;i<x2;++i){
ans=min(ans,f[k-1][x1][y1][i][y2]+f[1][i][y1][x2][y2]);
ans=min(ans,f[k-1][i][y1][x2][y2]+f[1][x1][y1][i][y2]);
}
for(int i=y1+1;i<y2;++i){
ans=min(ans,f[k-1][x1][y1][x2][i]+f[1][x1][i][x2][y2]);
ans=min(ans,f[k-1][x1][i][x2][y2]+f[1][x1][y1][x2][i]);
}
return ans;
}
int main(){
int n=read<int>();
for(int i=1;i<=8;++i)for(int j=1;j<=8;++j)
s[i][j]=read<int>()+s[i][j-1]+s[i-1][j]-s[i-1][j-1];
for(int x1=0;x1<8;++x1)for(int y1=0;y1<8;++y1)
for(int x2=x1+1;x2<=8;++x2)for(int y2=y1+1;y2<=8;++y2){
int p=s[x2][y2]-s[x1][y2]-s[x2][y1]+s[x1][y1];
f[1][x1][y1][x2][y2]=p*p;
}
for(int k=2;k<=n;++k)
for(int x1=0;x1<8;++x1)for(int y1=0;y1<8;++y1)
for(int x2=x1+1;x2<=8;++x2)for(int y2=y1+1;y2<=8;++y2)
f[k][x1][y1][x2][y2]=calc(k,x1,y1,x2,y2);
printf("%.3lf\n",sqrt((double)f[n][0][0][8][8]/n-(double)s[8][8]*s[8][8]/n/n));
return 0;
}

POJ1390 Blocks

Language:Default
Blocks
Time Limit: 5000MS Memory Limit: 65536K
Total Submissions: 6506 Accepted: 2689

Description

Some of you may have played a game called 'Blocks'. There are n blocks in a row, each box has a color. Here is an example: Gold, Silver, Silver, Silver, Silver, Bronze, Bronze, Bronze, Gold.

The corresponding picture will be as shown below:

常规DP专题练习

Figure 1

If some adjacent boxes are all of the same color, and both the box to its left(if it exists) and its right(if it exists) are of some other color, we call it a 'box segment'. There are 4 box segments. That is: gold, silver, bronze, gold. There are 1, 4, 3, 1 box(es) in the segments respectively.



Every time, you can click a box, then the whole segment containing that box DISAPPEARS. If that segment is composed of k boxes, you will get k*k points. for example, if you click on a silver box, the silver segment disappears, you got 4*4=16 points.



Now let's look at the picture below:

常规DP专题练习

Figure 2



The first one is OPTIMAL.



Find the highest score you can get, given an initial state of this game.

Input

The first line contains the number of tests t(1<=t<=15). Each case contains two lines. The first line contains an integer n(1<=n<=200), the number of boxes. The second line contains n integers, representing the colors of each box. The integers are in the range 1~n.

Output

For each test case, print the case number and the highest possible score.

Sample Input

2
9
1 2 2 2 2 3 3 3 1
1
1

Sample Output

Case 1: 29
Case 2: 1

Source

题目的方块可以表示成color[i], len[i], 1 <= i <= l,其中l表示有多少段不同的颜色方块。color[i]表示第i 段的颜色,len[i]表示第i 段的方块长度。

设dp[i, j, k]表示把(color[i], len[i]) , (color[i+1], len[i+1]), ……, (color[j], len[j] + k)合并的最大得分。

考虑(color[j], len[j] + k)这一段,

  1. 马上消掉,dp[i, j-1, 0] + (len[j] + k) ^ 2;
  2. 和前面的若干段一起消,可以假设这若干段中最后面得那一段是p,则此时的得分是dp[i, p, len[j]+k] + dp[p+1, j-1, 0].

于是有 dp[i, j, k] = max(f[i, j-1, 0] + (len[j] + k) ^ 2, dp[ i, p, len[j]+k ] + dp[p+1, j-1, 0]),其中color[p] == color[r];

#include<iostream>
#include<cstring>
#define rg register
#define il inline
#define co const
template<class T>il T read(){
rg T data=0,w=1;rg char ch=getchar();
while(!isdigit(ch)) {if(ch=='-') w=-1;ch=getchar();}
while(isdigit(ch)) data=data*10+ch-'0',ch=getchar();
return data*w;
}
template<class T>il T read(rg T&x) {return x=read<T>();}
typedef long long ll;
using namespace std; co int N=201;
int n,a[N],tot,id,c[N],cnt[N];
int o[N],nxt[N],x[N][N],f[N][N][N];
void Blocks(){
read(n);
for(int i=1;i<=n;++i) read(a[i]);
tot=0,memset(cnt,0,sizeof cnt);
for(int t=1;t<=n;){
c[++tot]=a[t];
while(t<=n&&a[t]==c[tot])
++cnt[tot],++t;
}
memset(o,0,sizeof o);
for(int i=1;i<=tot;++i)
nxt[i]=o[c[i]],o[c[i]]=i;
memset(x,0,sizeof x);
for(int i=tot;i;--i)
for(int j=1;j<=n;++j) // suffix cnt
x[j][i]=x[j][i+1]+(j==c[i]?cnt[i]:0);
memset(f,0,sizeof f);
for(int len=1;len<=tot;++len)
for(int l=1;l+len-1<=tot;++l){
int r=l+len-1;
for(int i=0;i<=x[c[r]][r+1];++i){
f[l][r][i]=f[l][r-1][0]+(cnt[r]+i)*(cnt[r]+i);
for(int p=nxt[r];p>=l;p=nxt[p])
f[l][r][i]=max(f[l][r][i],f[l][p][cnt[r]+i]+f[p+1][r-1][0]);
}
}
printf("Case %d: %d\n",++id,f[1][tot][0]);
}
int main(){
for(int t=read<int>();t--;) Blocks();
return 0;
}
上一篇:centos 7.0安装花生壳


下一篇:Java之旅_面向对象_抽象类