简单DP专题练习

POJ2279 Mr. Young's Picture Permutations

题意

Language:
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

分析

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

时间复杂度\(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),则对应一个杨表(注意这是一个递降的过程,也就是说下面一行的方格数要大于等于上一行的方格数)。

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

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

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

一个杨表的表示

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

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

对于分拆10 = 5 + 4 + 1 的应的杨表. 因此共有
种方法。

公式题。求出同行右边的方格数+同列上面的方格数+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

题意

描述

熊大妈的奶牛在小沐沐的熏陶下开始研究信息题目。小沐沐先让奶牛研究了最长上升子序列,再让他们研究了最长公共子序列,现在又让他们研究最长公共上升子序列了。
小沐沐说,对于两个数列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

分析

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:
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

| A 1 - B 1| + | A 2 - B 2| + ... + | 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

题意

描述

一个公司有三个移动服务员,最初分别在位置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

分析

\(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 传纸条

题意

描述

给定一个 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

分析

这种只能向右和向下走,只能在同时走到相同的格子,所以维护当前在哪个格子即可。容易设出状态\(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

题意

描述

在 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 }

分析

参照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

题意

描述

圣诞老人共有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 }

分析

参照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:
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:
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:
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.

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.

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 金字塔

题意

描述

虽然探索金字塔是极其老套的剧情,但是有一队探险家还是到了某金字塔脚下。经过多年的研究,科学家对这座金字塔的内部结构已经有所了解。首先,金字塔由若干房间组成,房间之间连有通道。如果把房间看作节点,通道看作边的话,整个金字塔呈现一个有根树结构,节点的子树之间有序,金字塔有唯一的一个入口通向树根。并且,每个房间的墙壁都涂有若干种颜色的一种。
探险队员打算进一步了解金字塔的结构,为此,他们使用了一种特殊设计的机器人。这种机器人会从入口进入金字塔,之后对金字塔进行深度优先遍历。机器人每进入一个房间(无论是第一次进入还是返回),都会记录这个房间的颜色。最后,机器人会从入口退出金字塔。
显然,机器人会访问每个房间至少一次,并且穿越每条通道恰好两次(两个方向各一次), 然后,机器人会得到一个颜色序列。但是,探险队员发现这个颜色序列并不能唯一确定金字塔的结构。现在他们想请你帮助他们计算,对于一个给定的颜色序列,有多少种可能的结构会得到这个序列。因为结果可能会非常大,你只需要输出答案对10^9 取模之后的值。

输入格式

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

输出格式

输出一个整数表示答案。

样例输入

ABABABA

样例输出

5

样例解释

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

来源

原创

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

分析

我们可以发现这里每个子树的字母是连续的,所以我们可以用\(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:
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:


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:
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 乌龟棋

描述

小明过生日的时候,爸爸送给他一副乌龟棋当作礼物。
乌龟棋的棋盘是一行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

这种分步决策的问题先考虑动态规划。。由于有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;
}

猜你喜欢

转载自www.cnblogs.com/autoint/p/10677497.html