赞
踩
在一个圆形操场的四周摆放 N 堆石子,现要将石子有次序地合并成一堆,规定每次只能选相邻的 2 堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。试设计出一个算法,计算出将 N 堆石子合并成 11 堆的最小得分和最大得分。
设dp[i] [j]表示 将i堆合并到j堆的最小得分(最大得分同理)
sum[i] [j]表示 i堆到 j堆的石子的和
我们可以将石子堆从小堆逐渐推导:
综上: 状态转移方程:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
k
]
+
d
p
[
k
+
1
]
[
j
]
+
s
u
m
[
i
]
[
j
]
,
d
p
[
i
]
[
j
]
)
dp[i][j]=min(dp[i][k]+dp[k+1][j]+sum[i][j],dp[i][j])
dp[i][j]=min(dp[i][k]+dp[k+1][j]+sum[i][j],dp[i][j])
其中 k表示两堆之间的分割,再最后合并成两堆后,别忘了合并成最后一堆,加上sum[i] [j]
本题需要注意点:
这是一个环形,而我们上述所讨论的情况是一条链,因此我们把这个环形拉长变成一条链
环形拉长后形成的链长应为初始的长度的二倍
最后:在这条长链中的任意起点和终点位置寻找一个最小值和最大值,得到的即是N堆石子合并后的最小值和最大值
AC code:
//TODO: Write code here int n,m; const int N=2e3+10; int nums[N],dpmax[N][N],dpmin[N][N],sum[N][N]; signed main() { cin>>n; for (int i=1;i<=n;i++) { cin>>nums[i]; nums[i+n]=nums[i]; sum[i][i]=nums[i],sum[i+n][i+n]=nums[i+n]; } for (int len=2;len<=n;len++) { for (int i=1;i+len<=(n<<1)+1;i++) //寻找起点 { int j=i+len-1;//对应的终点 dpmax[i][j]=0,dpmin[i][j]=INF; for (int k=i;k<j;k++) //分割成两堆, { sum[i][j]=sum[i][k]+sum[k+1][j];//首先计算[i,j]的和 dpmin[i][j]=min(dpmin[i][k]+dpmin[k+1][j]+sum[i][j],dpmin[i][j]); dpmax[i][j]=max(dpmax[i][k]+dpmax[k+1][j]+sum[i][j],dpmax[i][j]); } } } int ansMax=0,ansMin=INF; for (int i=1;i<=n;i++) { //寻找一条链中能够形成的最大和最小的值 ansMin=min(ansMin,dpmin[i][i+n-1]); ansMax=max(ansMax,dpmax[i][i+n-1]); } cout<<ansMin<<endl<<ansMax; #define one 0 return 0; }
字串S长M,由N个小写字母构成。欲通过增删字母将其变为回文串,增删特定字母花费不同,求最小花费。
我们可以得到一个性质: 当我们把一个字符串通过添加a字符或者删除a字符变为回文串的时候,我们可以发现我们只需要进行a字符的最小花费的操作即可。
例如 : abcb 删除a:1200 添加a:1000
定义dp[i] [j]表示将字符串的第 i 位置到第 j 位置
修改为回文串的最小花费
状态转移方程如下:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
+
1
]
[
j
]
+
c
o
s
t
[
a
[
i
]
]
,
d
p
[
i
]
[
j
−
1
]
+
c
o
s
t
[
s
[
j
]
]
)
dp[i][j]=min(dp[i+1][j]+cost[a[i]],dp[i][j-1]+cost[s[j]])
dp[i][j]=min(dp[i+1][j]+cost[a[i]],dp[i][j−1]+cost[s[j]])
AC code
#include <iostream> #include <string> #include <cstring> #include <map> using namespace std; const int N=3e3+10; int n,m; char s[N]; map<char,int> mp; int dp[N][N]; int main(){ cin>>n>>m; for (int i=1;i<=m;i++){ cin>>s[i]; } for (int i=1;i<=n;i++){ char ch; int a,b; cin>>ch>>a>>b; mp[ch]=min(a,b);///最少花费操作 } for (int len=2;len<=m;len++){ for (int i=1;i+len-1<=m;i++){ int j=i+len-1; //不需要枚举k的位置 if (s[i]==s[j]){ dp[i][j]=dp[i+1][j-1]; } else{ dp[i][j]=min(dp[i+1][j]+mp[s[i]],dp[i][j-1]+mp[s[j]]); } } } cout<<dp[1][m]; return 0; }
给定两个长度相等的字符串 A、B,由小写字母组成。
现有一种操作,可以把 A 中的一个连续子串(区间)都转换为某个字符(就像用刷子刷成一样的字符)。
问要把 A 转换为 B,最少的操作数是多少?
定义dp[i] [j]表示字符串A从[i,j] 范围的转换的最少操作数
首先用一个空串模拟转换为B串的过程,然后用A串转换为B串
首先模拟用空串转换为B:
然后我们再根据空串转换为B串的DP方程所得到的结果来处理A串转换为B串的形式,注意此时我们只对终点进行操作,因为我们要固定a的起点,得到从a[1]转换为a[j]的最少操作数
状态转移方程如下:
d p [ i ] [ j ] = d p [ i ] [ j − 1 ] ∣ ∣ ( d p [ i + 1 ] [ j ] ) , b [ i ] = = b [ j ] d p [ i ] [ j ] = m i n ( d p [ i ] [ k ] + d p [ k + 1 ] [ j ] , d p [ i ] [ j ] ) dp[i][j]=dp[i][j-1] || (dp[i+1][j]) , b[i]==b[j] \\ dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][j]) dp[i][j]=dp[i][j−1]∣∣(dp[i+1][j]),b[i]==b[j]dp[i][j]=min(dp[i][k]+dp[k+1][j],dp[i][j])
AC code
#include <bits/stdc++.h> using namespace std; const int N = 1005,INF=0x3F3F3F3F; char a[N], b[N]; int dp[N][N]; int main() { scanf("%s%s", a + 1, b + 1); int n = strlen(a + 1); for (int i = 1; i <= n; i++) dp[i][i] = 1; //单个字符转换次数为1 /* dp[i][j]: 表示[i,j]内转换的最少操作数 */ //将一个空串转换为b串:预处理 for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++)//起点 { int j = i + len - 1; //终点 dp[i][j] = INF; if (b[i] == b[j]) { dp[i][j] = dp[i][j - 1]; //dp[i][j]=dp[i+1][j] } else { /* "合并石子": abbbc */ for (int k = i; k < j; k++) //区间分割 { dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j]); } } } } //将a串转换为b串: dp[1][j]: 从1转换到j的最少操作数 for (int j = 1; j <= n; j++) { if (a[j] == b[j]) { dp[1][j] = dp[1][j - 1];//当前字符相同不用转,[1,j]的操作数就等于上一次的操作数 } else { for (int k = 1; k < j; k++) //分割[1,j]的区间 { dp[1][j] = min(dp[1][j], dp[1][k] + dp[k + 1][j]); } } } cout << dp[1][n]; return 0; }
n 个男孩去相亲,排成一队上场。大家都不想等,排队越靠后越愤怒。每人的耐心不同,用 D 表示火气,设男孩 i 的火气是 D i D_i Di,他排在第 k 个时,愤怒值是 ( k − 1 ) ∗ D i (k−1)∗D_i (k−1)∗Di。
主持人不想看到会场气氛紧张。他安排了一个黑屋,可以调整这排男孩上场的顺序,屋子很狭长,先进去的男孩最后出来(黑屋就是一个堆栈)。
例如,当男孩 A 排到时,如果他后面的男孩 B 火气更大,就把 A 送进黑屋,让 B 先上场。一般情况下,那些火气小的男孩要多等等,让火气大的占便宜。不过,零脾气的你也不一定吃亏,如果你原本排在倒数第二个,而最后一个男孩脾气最坏,主持人为了让这个坏家伙第一个上场,把其他人全赶进了黑屋,结果你就排在了黑屋的第 11 名,第二个上场相亲了(注意,每个男孩都要进出黑屋)。
对所有男孩的愤怒值求和,求所有可能情况的最小和。
定义dp[i] [j]表示[i,j]个人的最小愤怒值之和
我们假设第 i 个人第 k个出场,男孩们的火气值分别为 1,2,3,4,5
如果 i =1 , k = 3,则我们应该让 第一个人第三个出场,根据栈的含义,我们应该这样安排:
1入栈,2入栈,3入栈 ,3出栈,2出栈,1出栈 ,后面的4,5不变
则男孩们的出场顺序为: 3 2 1 4 5
我们可以分三段对他进行分析
状态转移方程为:
d
p
[
i
]
[
j
]
=
m
i
n
(
d
p
[
i
]
[
j
]
,
d
p
[
i
+
1
]
[
k
]
+
D
[
i
]
∗
(
k
−
i
)
+
d
p
[
k
+
1
]
[
j
]
+
(
k
−
i
+
1
)
∗
(
s
u
m
[
j
]
−
s
u
m
[
k
]
)
)
dp[i][j]=min(dp[i][j],\\ dp[i+1][k]+D[i]*(k-i)+dp[k+1][j]+(k-i+1)*(sum[j]-sum[k]))
dp[i][j]=min(dp[i][j],dp[i+1][k]+D[i]∗(k−i)+dp[k+1][j]+(k−i+1)∗(sum[j]−sum[k]))
其中,k表示分割的区间的位置,即第i个男孩移动到的位置,sum表示区间和
AC code
#include <bits/stdc++.h> using namespace std; const int N = 1005,INF=0x3F3F3F3F; char a[N], b[N]; int dp[N][N]; int t, n; int D[N]; int sum[N]; void solve2() { cin >> t; while (t--) { memset(dp, 0, sizeof(dp)); cin >> n; sum[0] = 0; for (int i = 1; i <= n; i++) { cin >> D[i]; sum[i] = sum[i - 1] + D[i]; } /* dp[i][j]:表示 [i,j]的最小愤怒值之和 */ for (int len = 2; len <= n; len++) { for (int i = 1; i <= n - len + 1; i++) { int j = i + len - 1; dp[i][j] = INF; for (int k = i; k <= j; k++) { dp[i][j] = min(dp[i][j], dp[i + 1][k] + //1. i+1到k的人往前挪 D[i] * (k - i) + //2. 第i个人往后移动k-i个位置 dp[k + 1][j] + (k - i + 1) * (sum[j] - sum[k]));//3. k后面的人往前移动k-i+1个位置 } } } cout << dp[1][n]<<endl; } } int main() { solve2(); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。