说白了dp就是状态记录和转移,区间dp就是状态记录中是以区间的形式记录的。
有些题目还是很精妙的,不容易看出来怎么设置状态和列状态转移方程。
(前面三题比较久之前写的了已经不太记得细节了
B - Halloween Costumes LightOJ - 1422
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
using namespace std;
int n;
int dp[105][105];
int num[105];
int main(){
int t;
scanf("%d", &t);
for (int cas = 1; cas <= t; cas++){
scanf("%d", &n);
memset(dp, 0x3f, sizeof(dp));
for (int i = 0; i < n; i++){ scanf("%d", &num[i]); dp[i][i] = 1; }
for (int l = 2; l <= n; l++){
for (int i = 0; i <= n - l; i++){
int j = i + l - 1;
if (num[i] == num[j]){
if ((i + 1)>(j - 1)){ dp[i + 1][j - 1] = 1; }
dp[i][j] = dp[i][j - 1];
//if (num[i] == num[i + 1] || num[j] == num[j - 1]){ dp[i][j] = dp[i + 1][j - 1]; }
//else{ dp[i][j] = dp[i + 1][j - 1] + 1; }
}
for (int k = i; k < j; k++){
if (num[k] == num[k+1]){
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j] - 1);
}
else{
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k+1][j]);
}
}
}
}
cout << "Case " << cas << ": " << dp[0][n - 1] << endl;
//printf("Case %d: ", cas);
//printf("%d\n", dp[0][n - 1]);
}
return 0;
}
C - Brackets POJ - 2955
求最大数量的括号匹配。枚举区间枚举分割点都行了。
#include<iostream>
#include<string.h>
#include<algorithm>
#include<string>
#include<stdio.h>
#include<vector>
using namespace std;
string str;
bool kuo(int i,int j){
if(str[i]=='('&&str[j]==')')return 1;
if(str[i]=='['&&str[j]==']')return 1;
return 0;
}
int dp[105][105];
int main(){
while(cin>>str,str!="end"){
int len=str.length();
memset(dp,0,sizeof(dp));
//for(int i=0;i<len;i++){dp[i][i]=1;}
for(int l=2;l<=len;l++){
for(int i=0;i<=len-l;i++){
int j=i+l-1;
if(kuo(i,j)){dp[i][j]=dp[i+1][j-1]+2;}
for(int k=i;k<j;k++){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
}
}
printf("%d\n",dp[0][len-1]);
}
return 0;
}
D - Coloring Brackets CodeForces - 149D
#include<iostream>
#include<string.h>
#include<stdio.h>
#include<string>
#include<vector>
#include<algorithm>
#include<queue>
#include<set>
#include<stack>
using namespace std;
string str;
const int mod = 1000000007;
int match[750];
long long dp[750][750][3][3];//0 0无色,1蓝,2红
void dfs(int lef, int rig){
if (rig == lef + 1){
dp[lef][rig][0][1] = dp[lef][rig][1][0] = dp[lef][rig][0][2] = dp[lef][rig][2][0] = 1;
return;
}
if (match[lef] == rig){
dfs(lef + 1, rig - 1);
for (int i = 0; i < 3; i++){//枚举左端颜色
for (int j = 0; j < 3; j++){//枚举右端颜色
if (j != 1)dp[lef][rig][0][1] += dp[lef + 1][rig - 1][i][j];
dp[lef][rig][0][1] %= mod;
if (i != 1)dp[lef][rig][1][0] += dp[lef + 1][rig - 1][i][j];
dp[lef][rig][1][0] %= mod;
if (j != 2)dp[lef][rig][0][2] += dp[lef + 1][rig - 1][i][j];
dp[lef][rig][0][2] %= mod;
if (i != 2)dp[lef][rig][2][0] += dp[lef + 1][rig - 1][i][j];
dp[lef][rig][2][0] %= mod;
}
}
}
else{
dfs(lef, match[lef]);
dfs(match[lef] + 1, rig);
for (int i = 0; i < 3; i++){//l d l
for (int j = 0; j < 3; j++){//l d r
for (int k = 0; k < 3; k++){//r d l
for (int l = 0; l < 3; l++){//r d r
if (j!=0&&j == k)continue;
dp[lef][rig][i][l] += (dp[lef][match[lef]][i][j] * dp[match[lef] + 1][rig][k][l]) % mod;
dp[lef][rig][i][l] %= mod;
}
}
}
}
}
}
int main(){
cin >> str;
int len = str.length();
int s[750], top = -1;
for (int i = 0; i < len; i++){
if (str[i] == '(')s[++top] = i;
else{ match[s[top--]] = i; }
}
dfs(0, len-1);
long long ans = 0;
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
ans += dp[0][len - 1][i][j];
ans %= mod;
}
}
cout << ans << endl;
return 0;
}
E - Multiplication Puzzle POJ - 1651
一排卡片,每次抽一张卡就对答案贡献这张卡和他左右两张卡共三张卡的乘积,求如何抽使得最终答案最大。
首先,区间两端的卡片是不能抽的,枚举区间,枚举中间抽剩的最后一张牌,就可以状态转移了。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1005;
const int maxe=100005;
const int inf=0x3f3f3f3f;
int n,v,xx;
int dp[105][105];
int num[105];
int DP(int x,int y){
if(y-x+1<3)return 0;
return dp[x][y];
}
int main(){
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&num[i]);
memset(dp,0x3f,sizeof(dp));
for(int l=3;l<=n;l++){
for(int i=0;i+l-1<n;i++){
int j=i+l-1;
for(int k=i+1;k<j;k++){
dp[i][j]=min(DP(i,j),DP(i,k)+DP(k,j)+num[i]*num[j]*num[k]);
}
}
}
printf("%d\n",dp[0][n-1]);
return 0;
}
F - Food Delivery ZOJ - 3469
送外卖的故事。问如何安排送餐顺序才能使得所有顾客的不耐烦程度之和最小。
dp[x][y][k]表示第x个顾客到第y个顾客这段区间送完后人在k(0为区间左端,1为区间右端)时的不耐烦程度之和,因为时间是累加的,你在送这段区间的餐时,区间外的人就在累计不耐烦程度也得加上,所以其实dp状态真正表示的是已经送完的人的不耐烦程度之和加上还没送到的人目前的不耐烦程度之和。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1005;
const int maxe=100005;
const int inf=0x3f3f3f3f;
int n,v,xx;
int dp[maxn][maxn][2];
int sum[maxn];
struct nod{
int x,b;
nod(){}
nod(int i,int j){x=i,b=j;}
bool operator<(const nod b)const {
return x<b.x;
}
};
nod p[maxn];
int dis(int i,int j){
return abs(p[i].x-p[j].x);
}
int delay(int i,int j){
if(i<=j)return sum[j]-sum[i-1];
return 0;
}
int main(){
while(~scanf("%d%d%d",&n,&v,&xx)){
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;i++){
scanf("%d%d",&p[i].x,&p[i].b);
}
p[n+1].x=xx,p[n+1].b=0;
n++;
sort(p+1,p+n+1);
for(int i=1;i<=n;i++){
sum[i]=sum[i-1]+p[i].b;
}
int cnt=lower_bound(p+1,p+n+1,nod(xx,0))-p;
dp[cnt][cnt][0]=dp[cnt][cnt][1]=0;
for(int i=cnt;i>=1;i--){
for(int j=cnt;j<=n;j++){
if(i==j)continue;
int kk=delay(1,i-1)+delay(j+1,n);
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+dis(i,i+1)*(kk+p[i].b));
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+dis(i,j)*(kk+p[i].b));
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+dis(i,j)*(kk+p[j].b));
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+dis(j-1,j)*(kk+p[j].b));
}
}
printf("%d\n",min(dp[1][n][0],dp[1][n][1])*v);
}
return 0;
}
G - You Are the One HDU - 4283
安排上场顺序,每个人的不开心程度等于前面上场的人数*屌丝值,初始顺序给出,可以把人放入堆栈中以延迟上场,满足先进后出,所以第1个人成为第k个上场时,说明他被压入堆栈中了,这样来看第2个人到第k-1个人肯定在他前面上场,然后就划分出子区间找到子问题了,就可以转移了。
#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<queue>
#include<map>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=105;
const int maxe=100005;
const int inf=0x3f3f3f3f;
int n,v,xx;
int dp[maxn][maxn];
int num[maxn];
int sum[maxn];
int DP(int x,int y){
if(x>=y)return 0;
if(dp[x][y]!=inf)return dp[x][y];
int xu=y-x+1;
int ans=inf;
for(int i=0;i<xu;i++){
ans=min(ans,DP(x+1,x+i)+num[x]*i+(sum[y]-sum[x+i])*(i+1)+DP(x+i+1,y));
}
return dp[x][y]=ans;
}
int main(){
int T;
scanf("%d",&T);
int cas=0;
while(T--){
cas++;
memset(dp,0x3f,sizeof(dp));
scanf("%d",&n);
for(int i=0;i<n;i++)scanf("%d",&num[i]);
sum[0]=num[0];
for(int i=1;i<n;i++)sum[i]=sum[i-1]+num[i];
int ans=DP(0,n-1);
printf("Case #%d: %d\n",cas,ans);
}
return 0;
}
H - String painter HDU - 2476
改变字符使串a变成串b
一次可以使得一个区间的字符全部变成某一个字符。
dp[i][j][k]表示区间[i,j]当前字符状态为k时变成与b串相同需要的最小步骤,k=0表示为原始串,否则表示该区间的字母全部是’a’+k-1
状态知道了就好做了
#include<iostream>
#include<cstdio>
#include<string.h>
#include<string>
#include<set>
#include<algorithm>
#include<queue>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int maxn=1e6+5;
const int maxe=100005;
const int inf=0x3f3f3f3f;
int n,k;
int dp[105][105][30];//dp[i][j][0]表示原串,其余表示全变成了某字母
char a[105],b[105];
int idx(char c){return c-'a'+1;}
int getc(int p,int c){
if(c==0)return a[p]-'a'+1;
else return c;
}
int DP(int x,int y,int c){
if(y<x)return 0;
if(dp[x][y][c]!=-1)return dp[x][y][c];
if(x==y){
if(c==0){
if(a[x]==b[x])return dp[x][y][c]=0;
return dp[x][y][c]=1;
}
else {
if(idx(b[x])==c)return dp[x][y][c]=0;
return dp[x][y][c]=1;
}
}
int ans=inf;
int maxl=y-x+1;
for(int l=1;l<=maxl;l++){
int i=x;
int j=i+l-1;
int lef=i,rig=j,lef1=i,rig1=j;
int need=idx(b[i]);
while(lef1<=j&&getc(lef1,c)==idx(b[lef1]))lef1++;
while(rig1>=i&&getc(rig1,c)==idx(b[rig1]))rig1--;
while(lef<=j&&need==idx(b[lef]))lef++;
while(rig>=i&&need==idx(b[rig]))rig--;
ans=min(ans,DP(lef,rig,need)+DP(j+1,y,c)+1);
if(lef1!=i)
ans=min(ans,DP(lef1,rig1,c)+DP(j+1,y,c));
}
return dp[x][y][c]=ans;
}
int main(){
while(~scanf("%s%s",a,b)){
memset(dp,-1,sizeof(dp));
n=strlen(a);
int ans=DP(0,n-1,0);
printf("%d\n",ans);
}
return 0;
}