题意:
在一个圆形操场的四周摆放N堆石子,现要将石子有次序地合并成一堆.规定每次只能选相邻的2堆合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。(注意是环状)
题解:
dp1[i][j]表示第i堆到第j堆最小得分,dp2[i][j]表示第i堆到第j堆最大得分,环状数组开两倍,状态转移方程如下:
dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + summ);
dp2[i][j] = max(dp2[i][j],dp2[i][k] + dp2[k+1][j] + summ);
PS:求最大值时不能采用四边形法则(坑)。
AC_Code:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int maxn = 205;
const int inf = 0x3f3f3f3f;
int sum[maxn];
int dp1[maxn][maxn];//xiao
int dp2[maxn][maxn];//da
int weight[maxn];
int n;
#define ms1(s) memset(s,0,sizeof(s))
#define ms2(s) memset(s,inf,sizeof(s))
void solve(){
for(int len = 2 ; len <= 2*n; ++len){//changdu
for(int i=1,j=len; j<=2*n; i++,j++){
for(int k=i; k<j; ++k){
//if(dp1[i][j]) dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + sum[j] - sum[i-1]);
//else dp1[i][j] = dp1[i][k] + dp1[k+1][j] + sum[j] - sum[i-1];
int summ;
if(i <= n + 1 && j > n) summ = sum[j] + sum[n] - sum[i-1];
else summ = sum[j] - sum[i-1];
dp1[i][j] = min(dp1[i][j],dp1[i][k] + dp1[k+1][j] + summ);
dp2[i][j] = max(dp2[i][j],dp2[i][k] + dp2[k+1][j] + summ);
}
}
}
int minn = inf;
int maxx = -inf;
for(int i=1; i<=n; ++i){
minn = min(minn,dp1[i][i+n-1]);
maxx = max(maxx,dp2[i][i+n-1]);
}
cout << minn << endl << maxx << endl;
}
int main(){
cin >> n;
ms2(dp1);ms1(dp2);
for(int i=0; i<=2*n; ++i) dp1[i][i] = dp2[i][i] = 0;
for(int i=1; i<=n; ++i) {
cin >> weight[i];
//dp1[i][i] = dp2[i][i] = weight[i];
}
for(int i=n+1; i<=2*n; ++i) weight[i] = weight[i-n];
for(int i=1; i<=n; ++i) sum[i] = sum[i-1] + weight[i];
for(int i=n+1; i<=2*n; ++i) sum[i] = sum[i-n];
solve();
return 0;
}
题意:
有n个人上台演出,每个人ai有一个diaosi值di,他们的不开心值si会随着自己的出场顺序u而变化, si = di * (u - 1),求最小不开心值总和。
题解:
给的顺序是入栈的顺序。
区间dp : dp[i][j]表示第i个人到第j个人的最小不开心总值,sum[i]表示前i个数的和。
k表示i ~ j 区间第 i 个人(即 i 到 j 区间第一个人)第 k 个上台表演,而 i + 1 到 i + k - 1 区间的人是在 a[i] 前面上台的,就划分出了一个子问题 dp[i + 1][i + k - 1],因为i + 1 到 i + k - 1 区间的人是先上台的,dp[i + 1][i + k - 1] 就是他们的愤怒总量;而 i + k 到 j 区间的人就是在 a[i] 后上台的,有划分出了一个子问题,dp[i + k][j],但是由于该区间是在第 k 个后上台的,所以要加上他们的愤怒总量乘 k ,即 ((sum[j] - sum[i + k - 1]) * k);最后加上第 i 个人第 k 次上场的愤怒总量a[i] * (k -1),就是状态转移方程。
//
//Write by Yuan Xilan on 2019...
//
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf = 0x3f3f3f3f;
const int maxn = 222;
int dp[maxn][maxn];
int sum[maxn];
int a[maxn];
int n,z=0;
#define ms1(s) memset(s, 0, sizeof(s))
#define ms2(s) memset(s,inf,sizeof(s))
#define test(t) int t; cin >> t; while(t--)
void init(){
ms2(dp);ms1(sum);
cin >> n;
for(int i=1; i<=n; ++i){
cin >> a[i];
sum[i] = sum[i-1] + a[i];
dp[i][i] = 0;
}
}
void solve(){
for(int len = 1; len <= n; ++len){//长度
for(int i = 1, j = len; j <= n; ++i, ++j){//i表示起点,j表示终点
for(int k = 1; k <= len; ++k){//k表示a[i]插入的位置
if(k == 1) dp[i][j] = min(dp[i][j], dp[i+k][j] + (sum[j] - sum[i+k-1]) * k);
else if(k == len) dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + a[i] * (k - 1));
else dp[i][j] = min(dp[i][j], dp[i+1][i+k-1] + dp[i+k][j] + (sum[j] - sum[i+k-1]) * k + a[i] * (k - 1));
}
}
}
printf("Case #%d: %d\n", ++z, dp[1][n]);
}
int main(){
test(t){
init();
solve();
}
return 0;
}
题意:
给定两个等长度的字符串,有一种刷新字符串的方法,它能够将一段字符串刷成同一个字符。现在要你使用这种方法,使得第一个字符串被刷成第二个字符串,问你最少需要刷多少次?
题解:
dp[i][j]维护区间i到j s1修改成s2的最少次数,考虑直接将一个空串刷成第二个字符串,然后再与第一个字符串去比较。这样,如果每个字符都是单独刷新,则dp[i][j] = dp[i+1][j]+1,如果在区间[i+1,j]之间有字符与t[i]相同,则可以将区间分为两个区间,分别为[i+1,k]和[k+1,j],考虑一起刷新.
AC_Code:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
const int maxn = 105;
char s1[maxn],s2[maxn];
int dp[maxn][maxn];
void solve(){
int len = strlen(s1+1);
for(int j=1; j<=len; ++j){
for(int i=j; i>0; --i){
dp[i][j] = dp[i+1][j] + 1;
for(int k=i+1; k<=j; ++k){
if(s2[k] == s2[i]){
dp[i][j] = min(dp[i][j],dp[i+1][k] + dp[k+1][j]);
}
}
}
}
for(int i=1; i<=len; ++i){
if(s1[i] == s2[i]){
if(i != 1){
dp[1][i] = dp[1][i-1];
}
else{
dp[1][i] = 0;
}
}
else{
for(int j=1; j<i; ++j){
dp[1][i] = min(dp[1][i],dp[1][j] + dp[j+1][i]);
}
}
}
printf("%d\n",dp[1][len]);
}
int main(){
//freopen("in.txt","r",stdin);
while(~scanf("%s%s",s1+1,s2+1)){
memset(dp,0,sizeof(dp));
solve();
}
return 0;
}
题意:
你是一个战士现在面对,一群狼,每只狼都有一定的主动攻击力和附带攻击力。你杀死一只狼。你会受到这只狼的(主动攻击力+旁边两只狼的附带攻击力)这么多伤害~现在问你如何选择杀狼的顺序使的杀完所有狼时,自己受到的伤害最小。(提醒,狼杀死后就消失,身边原本相隔的两只狼会变成相邻,而且不需要考虑狼围城环这种情况)
题解:
dp[i][j]表示把第i只狼到第j只狼杀掉后的最小承伤量,设k表示在[i,j]范围内第k只狼是最后一个死的,那么该问题可分解为两个子问题,即dp[i][k-1]和dp[k+1][j],最后杀第k只狼时,除了自己的主动攻击力外,还会附带第i-1和j+1只狼的附带攻击力,因此有状态转移方程:
dp[i][j] = min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
PS:考虑i比k-1大和k-1比j大的情况。
AC_Code:
#include <bits/stdc++.h>
using namespace std;
#define ll long long
#define ull unsigned long long
#define inf 0x3f3f3f3f
const int maxn = 205;
int a[maxn],b[maxn];
int dp[maxn][maxn];
int times;
void solve(){
int n;
scanf("%d",&n);
for(int i=1; i<=n; ++i) scanf("%d",&a[i]);
for(int i=1; i<=n; ++i) scanf("%d",&b[i]);
for(int i=1; i<=n; ++i) dp[i][i] = a[i] + b[i-1] + b[i+1];
for(int j=1; j<=n; ++j){//终点
for(int i=j; i>=1; --i){//起点
for(int k=i; k<=j; ++k){//第k只狼最后被杀死
if(k == i) dp[i][j] = min(dp[i][j],dp[i+1][j]+a[i]+b[i-1]+b[j+1]);
else if(k == j) dp[i][j] = min(dp[i][j],dp[i][j-1]+a[j]+b[i-1]+b[j+1]);
else dp[i][j] = min(dp[i][j],dp[i][k-1]+dp[k+1][j]+a[k]+b[i-1]+b[j+1]);
}
}
}
printf("Case #%d: %d\n",times,dp[1][n]);
}
int main(){
//freopen("in.txt","r",stdin);
int t;
while(~scanf("%d",&t)){
times = 0;
while(t--){
times += 1;
memset(dp,inf,sizeof(dp));
memset(a,0,sizeof(a));
memset(b,0,sizeof(b));
solve();
}
}
return 0;
}