Nefu区间dp,ACM暑期培训
A - 能量项链
题目描述
在 Mars 星球上,每个 Mars 人都随身佩带着一串能量项链。在项链上有 N 颗能量珠。能量珠是一颗有头标记与尾标记的珠子,这些标记对应着某个正整数。并且,对于相邻的两颗珠子,前一颗珠子的尾标记一定等于后一颗珠子的头标记。因为只有这样,通过吸盘(吸盘是 Mars 人吸收能量的一种器官)的作用,这两颗珠子才能聚合成一颗珠子,同时释放出可以被吸盘吸收的能量。如果前一颗能量珠的头标记为 m,尾标记为 r,后一颗能量珠的头标记为 r,尾标记为 n,则聚合后释放的能量为 m×r×n(Mars 单位),新产生的珠子的头标记为 m,尾标记为 n。
需要时,Mars 人就用吸盘夹住相邻的两颗珠子,通过聚合得到能量,直到项链上只剩下一颗珠子为止。显然,不同的聚合顺序得到的总能量是不同的,请你设计一个聚合顺序,使一串项链释放出的总能量最大。
例如:设 N=4,4 颗珠子的头标记与尾标记依次为 (2,3)(3,5)(5,10)(10,2).我们用记号 ⊕ 表示两颗珠子的聚合操作,(j⊕k) 表示第 j,k 两颗珠子聚合后所释放的能量。则第 4,1 两颗珠子聚合后释放的能量为:
(4⊕1)=10×2×3=60。
这一串项链可以得到最优值的一个聚合顺序所释放的总能量为:
(((4⊕1)⊕2)⊕3)=10×2×3+10×3×5+10×5×10=710。
输入格式
第一行是一个正整数 N(4≤N≤100),表示项链上珠子的个数。第二行是 N 个用空格隔开的正整数,所有的数均不超过1000。第 i 个数为第 i 颗珠子的头标记(1≤i≤N),当 i<N 时,第 i 颗珠子的尾标记应该等于第i+1 颗珠子的头标记。第 N 颗珠子的尾标记应该等于第 11 颗珠子的头标记。
至于珠子的顺序,你可以这样确定:将项链放到桌面上,不要出现交叉,随意指定第一颗珠子,然后按顺时针方向确定其他珠子的顺序。
输出格式
一个正整数 E(E≤2.1×109),为一个最优聚合顺序所释放的总能量。
输入输出样例
输入 #1复制
4 2 3 5 10
输出 #1复制
710
说明/提示
NOIP 2006 提高组 第一题
解析:区间dp
本题的数据是一个环,解决这的办法是将数组加长,两倍的输出长度,前一段和后一段的数据一样,每次只处理长度位n的数据,然后就没什么难点了
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 200 + 5;
const int INF = 1e9;
int n;
int sum[N], dp[N][N];
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%d", &sum[i]);
}
for (int i = 1; i <= n; i++) {
sum[i + n] = sum[i];
}
for (int len = 2; len <= n; len++) {
for (int l = 1; l + len - 1 <= 2*n; l++) {
int r = l + len;
for (int k = l + 1; k < r; k++) {
dp[l][r] = max(dp[l][r], dp[l][k] + dp[k][r] + sum[l] * sum[k] * sum[r]);
}
}
}
int mx = 0;
for (int l = 1; l <= n; l++) {
mx = max(mx, dp[l][l + n]);
}
cout << mx << endl;
return 0;
}
F - 合唱队
题目描述
为了在即将到来的晚会上有更好的演出效果,作为 AAA 合唱队负责人的小 A 需要将合唱队的人根据他们的身高排出一个队形。假定合唱队一共 n 个人,第 i 个人的身高为 hi 米(1000≤hi≤2000),并已知任何两个人的身高都不同。假定最终排出的队形是 A 个人站成一排,为了简化问题,小 A 想出了如下排队的方式:他让所有的人先按任意顺序站成一个初始队形,然后从左到右按以下原则依次将每个人插入最终棑排出的队形中:
-
第一个人直接插入空的当前队形中。
-
对从第二个人开始的每个人,如果他比前面那个人高(ℎh 较大),那么将他插入当前队形的最右边。如果他比前面那个人矮(ℎh 较小),那么将他插入当前队形的最左边。
当 n 个人全部插入当前队形后便获得最终排出的队形。
例如,有 66 个人站成一个初始队形,身高依次为 1850,1900,1700,1650,1800,17501850,1900,1700,1650,1800,1750,
那么小 A 会按以下步骤获得最终排出的队形:
-
18501850。
-
1850,19001850,1900,因为 1900>1850
-
1700,1850,19001700,1850,1900,因为 1700<1900
-
1650,1700,1850,19001650,1700,1850,1900,因为 1650<1700
-
1650,1700,1850,1900,18001650,1700,1850,1900,1800,因为 1800>1650
-
1750,1650,1700,1850,1900,18001750,1650,1700,1850,1900,1800,因为 1750<1800
因此,最终排出的队形是 1750,1650,1700,1850,1900,1800。
小 A 心中有一个理想队形,他想知道多少种初始队形可以获得理想的队形。
请求出答案对 19650827取模的值。
输入格式
第一行一个整数 n。
第二行 n 个整数,表示小 A 心中的理想队形。
输出格式
输出一行一个整数,表示答案 mod 19650827 的值。
输入输出样例
输入 #1复制
4 1701 1702 1703 1704
输出 #1复制
8
说明/提示
对于 30%30% 的数据,n≤100。
对于 100%100% 的数据,n≤1000,1000≤hi≤2000。
解析:区间dp,三维
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 1000 + 5;
int a[N], dp[N][N][2];
int n;
int main() {
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i]);
}
for (int i = 1; i <= n; i++) {
dp[i][i][0] = 1;
/*dp[i][i][1] = 1;*/
}
for (int i = n; i >= 1; i--) {
for (int j = i + 1; j <= n; j++) {
if (a[i + 1] > a[i]) {
dp[i][j][0] += dp[i + 1][j][0];
}
if (a[j] > a[i] /*&& i + 1 != j*/) {
dp[i][j][0] += dp[i + 1][j][1];
}
if (a[j - 1] < a[j]) {
dp[i][j][1] += dp[i][j - 1][1];
}
if (a[i] < a[j] /*&& j - 1 != i*/) {
dp[i][j][1] += dp[i][j - 1][0];
}
dp[i][j][0] %= 19650827;
dp[i][j][1] %= 19650827;
}
}
cout << (dp[1][n][0] + dp[1][n][1]) % 19650827 << endl;
return 0;
}
H - 玩具取名
P4290 [HAOI2008] 玩具取名 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)
Description
某人有一套玩具,并想法给玩具命名。首先他选择WING四个字母中的任意一个字母作为玩具的基本名字。然后他会根据自己的喜好,将名字中任意一个字母用“WING”中任意两个字母代替,使得自己的名字能够扩充得很长。
现在,他想请你猜猜某一个很长的名字,最初可能是由哪几个字母变形过来的。
Input
第一行四个整数W、I、N、G。表示每一个字母能由几种两个字母所替代。
接下来W行,每行两个字母,表示W可以用这两个字母替代。
接下来I行,每行两个字母,表示I可以用这两个字母替代。
接下来N行,每行两个字母,表示N可以用这两个字母替代。
接下来G行,每行两个字母,表示G可以用这两个字母替代。
最后一行一个长度不超过Len的字符串。表示这个玩具的名字。
Output
一行字符串,该名字可能由哪些字母变形而得到。(按照WING的顺序输出)
如果给的名字不能由任何一个字母变形而得到则输出“The name is wrong!”
Sample 1
Inputcopy | Outputcopy |
---|---|
1 1 1 1 II WW WW IG IIII | IN |
Hint
30%数据满足Len<=20,W、I、N、G<=6
100%数据满足Len<=200,W、I、N、G<=16
解析:区间dp,三维数组
[HAOI2008]玩具取名_牛客博客 (nowcoder.net)
#include<iostream>
#include<cstdio>
#include<cstdlib>
#include<string>
#include<cstring>
#include<cmath>
#include<ctime>
#include<algorithm>
#include<utility>
#include<stack>
#include<queue>
#include<vector>
#include<set>
#include<map>
using namespace std;
typedef long long LL;
const int N = 300+7;
int p[5];
int a[N];
int can[N][N][N],dp[N][N][N];
char s[N];
int main() {
a['W'] = 1;
a['I'] = 2;
a['N'] = 3;
a['G'] = 4;
for (int i = 1; i <= 4; i++) {
cin >> p[i];
}
for (int i = 1; i <= 4; i++) {
char x, y;
for (int j = 1; j <= p[i]; j++) {
cin >> x >> y;
can[i][a[x]][a[y]] = 1;
}
}
cin >> (s + 1);
int n = strlen(s + 1);
for (int i = 1; i <= n; i++)
dp[i][i][a[s[i]]] = 1;
for (int len = 2; len <= n; len++) {
for (int i = 1; i <= n; i++) {
int j = i + len - 1;
if (j > n)break;
for (int k =i; k < j; k++) {
for (int z = 1; z <= 4; z++) {
for (int z1 = 1; z1 <= 4; z1++) {
for (int z2 = 1; z2 <= 4; z2++) {
if (can[z][z1][z2] && dp[i][k][z1] && dp[k + 1][j][z2])
dp[i][j][z] = 1;
}
}
}
}
}
}
bool flag = false;
if (dp[1][n][1]) { flag = true; printf("W"); }
if (dp[1][n][2]) { flag = true; printf("I"); }
if (dp[1][n][3]) { flag = true; printf("N"); }
if (dp[1][n][4]) { flag = true; printf("G"); }
if (!flag) printf("The name is wrong!");
return 0;
}