专题二十一概率dp和二十二区间dp让我对dp感到了害怕,基本上每一题都不是自主想出来的。今后还得加强对dp的训练。
A - Cake
一开始dp式子想复杂了,隐约感觉到不是那样做。实际上dp式子很简单。
设dp[i][j] 为下标i到j最小的花费,那么
dp[i][j] =min(dp[i][k]+dp[k][j]+cost[i][k]+cost[k][j])
这题也让我真正明白了如何用极角排序了。
#include<bits/stdc++.h>
using namespace std;
#define eps 0
#define _sign(x) ((x)>eps?1:((x)<-eps?2:0))
#define list lst
struct point
{
int x,y;
point()
{
x=0;
y=0;
}
point(int sx,int sy)
{
x=sx;
y=sy;
}
};
int xmult(point p1,point p2,point p0)
{
return (p1.x-p0.x)*(p2.y-p0.y)-(p2.x-p0.x)*(p1.y-p0.y);
}
int is_convex_v2(int n,point *p)
{
int i,s[3]= {1,1,1};
for(i=0; i<n&&s[0]&&s[1]|s[2]; i++)
s[_sign(xmult(p[(i+1)%n],p[(i+2)%n],p[i]))]=0;
return s[0]&&s[1]|s[2];
}
int n,p;
point list[305];
int cost(int i,int j,int p)
{
if((i+1)%n==j||(j+1)%n==i)return 0;
return abs(list[i].x+list[j].x)*abs(list[i].y+list[j].y)%p;
}
double dist(point p1,point p2)
{
double x=p1.x-p2.x;
double y=p1.y-p2.y;
return sqrt(x*x+y*y);
}
bool cmp(point p1,point p2)
{
int tmp=xmult(list[0],p1,p2);
if(tmp>0)return 1;
else if(tmp==0&&dist(list[0],p1)<dist(list[0],p2))return 1;
return 0;
}
void read()
{
int i,k;
point p0;
cin>>list[0].x>>list[0].y;
p0.x=list[0].x,p0.y=list[0].y;
k=0;
for(i=1;i<n;i++)
{
scanf("%d %d",&list[i].x,&list[i].y);
if(p0.y>list[i].y||(p0.y==list[i].y&&p0.x>list[i].x))
{
p0.x=list[i].x;
p0.y=list[i].y;
k=i;
}
}
list[k]=list[0];
list[0]=p0;
sort(list+1,list+n,cmp);
}
long long dp[305][305];
int main()
{
while(~scanf("%d %d",&n,&p))
{
read();
if(!(is_convex_v2(n,list)))
puts("I can't cut.");
else
{
memset(dp,0,sizeof(dp));
for(int i=n-3; i>=0; i--)
for(int j=i+2; j<n; j++)
{
dp[i][j]=1e18;
for(int k=i+1; k<j; k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+cost(i,k,p)+cost(k,j,p));
}
cout<<dp[0][n-1]<<endl;
}
}
return 0;
}
B - Halloween Costumes
设dp[i][j] 为i到j最少换几次衣服。
如果一次也不换,那么dp[i][j]=dp[i+1][j]+1
当存在k,i< k<=j,a[i]=a[k]时,我们可以把i的衣服一直保留着,给k穿,所以dp[i][j]=dp[i][k-1]+dp[k+1][j].
#include<bits/stdc++.h>
using namespace std;
int a[105];
int dp[105][105];
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--)
{
int N;
scanf("%d",&N);
for(int i=1;i<=N;i++)scanf("%d",&a[i]);
memset(dp,0,sizeof(dp));
for(int i=N;i>=1;i--)
for(int j=i;j<=N;j++)
{
dp[i][j]=dp[i+1][j]+1;
for(int k=i+1;k<=j;k++)if(a[k]==a[i])
dp[i][j]=min(dp[i][j],dp[i][k-1]+dp[k+1][j]);
}
printf("Case %d: %d\n",cas++,dp[1][N]);
}
return 0;
}
C - Brackets
独立完成的题目之一。。
毕竟比较简单。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=105;
int dp[maxn][maxn];
int main()
{
char s[105];
while(~scanf("%s",s+1)&&s[1]!='e')
{
memset(dp,0,sizeof(dp));
int n=strlen(s+1);
for(int i=n;i>=1;i--)
for(int j=i+1;j<=n;j++)
{
dp[i][j]=dp[i+1][j-1];
if((s[i]=='('&&s[j]==')')||(s[i]=='['&&s[j]==']'))dp[i][j]+=2;
for(int k=i;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
}
cout<<dp[1][n]<<endl;
}
}
D - Coloring Brackets
给出的括号序列就是一个正确的序列,所以首先求出每个括号匹配的另一个括号的下标。
然后设dp[i][j][k][q] 代表i到j序列i涂色的状态为kj涂色的状态为q。分类讨论一下就行了。
#include<bits/stdc++.h>
using namespace std;
const int maxn=705;
const int mod=1e9+7;
char s[maxn];
int p[maxn];
int match[maxn];
void getmatch(int len)
{
int tot=0;
for(int i=1; i<=len; i++)
{
if(s[i]=='(')p[++tot]=i;
else
{
int idx=p[tot];
match[idx]=i;
match[i]=idx;
tot--;
}
}
//for(int i=1;i<=len;i++)
//cout<<match[i]<<endl;
}
long long dp[maxn][maxn][4][4];
void dfs(int l,int r)
{
if(l+1==r)
{
dp[l][r][0][1]=1;
dp[l][r][1][0]=1;
dp[l][r][0][2]=1;
dp[l][r][2][0]=1;
return ;
}
if(match[l]==r)
{
dfs(l+1,r-1);
for(int i=1; i<=2; i++)
for(int j=0; j<=2; j++)
for(int k=0; k<=2; k++)if(k!=i)
{
dp[l][r][0][i]=(dp[l+1][r-1][j][k]+dp[l][r][0][i])%mod;
dp[l][r][i][0]=(dp[l+1][r-1][k][j]+dp[l][r][i][0])%mod;
}
}
else
{
int u=match[l];
dfs(l,u);
dfs(u+1,r);
for(int i=0; i<=2; i++)
for(int j=1; j<=2; j++)
for(int k=0;k<=2;k++)if(k!=j)
dp[l][r][0][i]=((dp[l][u][0][j]*dp[u+1][r][k][i])%mod+dp[l][r][0][i])%mod;
for(int i=1;i<=2;i++)
for(int j=0;j<=2;j++)
for(int k=0;k<=2;k++)
dp[l][r][i][j]=((dp[l][u][i][0]*dp[u+1][r][j][k])%mod+dp[l][r][i][j])%mod;
}
}
int main()
{
scanf("%s",s+1);
int len=strlen(s+1);
getmatch(len);
dfs(1,len);
long long ans=0;
for(int i=0; i<=2; i++)
for(int j=0; j<=2; j++)
ans=(ans+dp[1][len][i][j])%mod;
cout<<ans<<endl;
}
E - Multiplication Puzzle
一开始看一份题解递推式子没看懂,换另一份后顿时豁然开朗。
设dp[i][j] 为区间i到j最小消耗,那么dp[i][j]=min(dp[i][k]+dp[k][j]+a[i]*a[k]*a[j]).
为什么是这样的呢,我们只要把k当成最后一个拿走的那张,那么就能理解了。
#include<iostream>
#include<algorithm>
#include<cstring>
#include<stdio.h>
using namespace std;
const int maxn=105;
int a[maxn];
long long dp[maxn][maxn];
int main()
{
int N;
scanf("%d",&N);
for(int i=1;i<=N;i++)
{
scanf("%d",&a[i]);
}
for(int i=N;i>=1;i--)
for(int j=i+2;j<=N;j++)
{
dp[i][j]=1e18;
for(int k=i+1;k<=j-1;k++)
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]+1LL*a[i]*a[k]*a[j]);
//cout<<i<<' '<<j<<' '<<dp[i][j]<<endl;
}
cout<<dp[1][N]<<endl;
}
F - Food Delivery
设dp[i][j][0]为送完i到j这个区间后工作人员在下标为i这个点。dp[i]j][1] 则代表在j这个点。
那么递推就很简单了,在递推过程中,我们要把所有人等待的值都要包括进去,所以得维护一个不高兴度的前缀和。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const int inf=0x3f3f3f3f;
struct node
{
int x,b;
node(int _x,int _b):x(_x),b(_b) {}
node() {}
bool operator<(const node&b)const
{
return x<b.x;
}
} nodes[maxn];
int sum[maxn];
int dp[maxn][maxn][2];
int qsum(int l,int r)
{
return sum[r]-sum[l];
}
int main()
{
int n,v,x;
while(~scanf("%d %d %d",&n,&v,&x))
{
memset(sum,0,sizeof(sum));
int x1,b;
for(int i=1; i<=n; i++)
{
scanf("%d %d",&x1,&b);
nodes[i]=node(x1,b);
}
nodes[++n]=node(x,0);
sort(nodes+1,nodes+1+n);
for(int i=1; i<=n; i++)
sum[i]=sum[i-1]+nodes[i].b;
int idx;
for(int i=1; i<=n; i++)
if(nodes[i].x==x&&nodes[i].b==0)
{
idx=i;
break;
}
memset(dp,inf,sizeof(dp));
dp[idx][idx][0]=dp[idx][idx][1]=0;
for(int i=idx; i>=1; i--)
for(int j=idx; j<=n; j++)
{
if(i==j)continue;
int tmp=qsum(0,i)+qsum(j,n);
int tmp2=qsum(0,i-1)+qsum(j-1,n);
//dp[i][j][0]=1e9;
//dp[i][j][1]=1e9;
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(nodes[i+1].x-nodes[i].x)*tmp);
dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(nodes[j].x-nodes[i].x)*tmp);
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(nodes[j].x-nodes[j-1].x)*tmp2);
dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(nodes[j].x-nodes[i].x)*tmp2);
//cout<<i<<' '<<j<<' '<<dp[i][j][0]<<' '<<dp[i][j][1]<<endl;
}
cout<<min(dp[1][n][0],dp[1][n][1])*v<<endl;
}
}
G - You Are the One
设dp[i][j] 为区间i到j不快乐度最小的值。我们设第i个人第k个走,那么i到i+k-1的人实际上都走了。基于这个我们就可以写出递推式子。
dp[i][j]=min(dp[i+1][i+k-1]+dp[i+k][j]+(k-1)*a[i]+(sum[j]-sum[i+k-1])*k)
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
int a[maxn];
int sum[maxn];
int dp[maxn][maxn];
int main()
{
int T;
scanf("%d",&T);
int cas=1;
while(T--)
{
memset(sum,0,sizeof(sum));
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
for(int i=1;i<=n;i++)sum[i]=sum[i-1]+a[i];
for(int i=n;i>=1;i--)
for(int j=i;j<=n;j++)
{
if(i==j){dp[i][j]=0;continue;}
dp[i][j]=dp[i+1][j]+sum[j]-sum[i];
for(int k=2;k<=j-i+1;k++)
dp[i][j]=min(dp[i][j],dp[i+1][i+k-1]+dp[i+k][j]+(k-1)*a[i]+k*(sum[j]-sum[i+k-1]));
}
printf("Case #%d: %d\n",cas++,dp[1][n]);
}
return 0;
}
H - String painter
也是独立完成的一道,看了别人写的突然发现自己写的复杂了。。
设dp[i][j][k]为区间i到j是否已经被刷成k的变成s2的最小次数,0代表还没被刷过。
然后记忆化搜索一下。
#include<bits/stdc++.h>
using namespace std;
const int maxn=105;
char s1[maxn],s2[maxn];
int dp[maxn][maxn][30];
int dfs(int l,int r,int k)
{
if(l>r)return 0;
if(l==r)
{
if(k==0)return s1[l]!=s2[l];
return k!=s2[l]-'a'+1;
}
if(dp[l][r][k]!=-1)return dp[l][r][k];
int res=1e9;
if(k)
{
if(k==s2[l]-'a'+1)res=min(res,dfs(l+1,r,k));
else res=min(res,dfs(l+1,r,k)+1);
}
else
{
if(s1[l]==s2[l])res=min(res,dfs(l+1,r,k));
else
res=min(res,dfs(l+1,r,k)+1);
}
for(int i=l+1;i<=r;i++)
{
if(s2[i]==s2[l])
{
if(k==0)
{
if(s1[i]==s2[i]&&s1[l]==s2[l])
res=min(res,dfs(l+1,i-1,k)+dfs(i+1,r,k));
else
res=min(res,dfs(l+1,i-1,s2[l]-'a'+1)+dfs(i+1,r,k)+1);
}
else
{
if(k==s2[i])
res=min(res,dfs(l+1,i-1,k)+dfs(i+1,r,k));
else
res=min(res,dfs(l+1,i-1,s2[l]-'a'+1)+dfs(i+1,r,k)+1);
}
}
}
//cout<<l<<' '<<r<<' '<<k<<' '<<res<<endl;
return dp[l][r][k]=res;
}
int main()
{
while(~scanf("%s %s",s1+1,s2+1))
{
memset(dp,-1,sizeof(dp));
int len=strlen(s1+1);
printf("%d\n",dfs(1,len,0));
}
}
写博客总结的时候,感觉题目简单了许多,可能事后诸葛亮吧,不论怎样dp还得做更多的题。