A - Cake
题目大意:给你一个n个顶点(n<=100)的多边形和每两个点连边的消耗,让你求把这个多边形全部切成三角形所需要的最小消耗,如果这个多边形为凹多边形则输出无解。
思路:先求一个凸包,看凸包里的点是不是n个,不是n个输出无解,求完凸包之后,点都是按顺时针排的,我们用dp[ i ][ j ]表示,i 到 j 的折线和 i 连 j的直线围成的多边形
的最小消耗。
状态转移方程:dp[ i ][ j ]=min( dp[ i ][ j ] , dp[ i ][ k ]+dp[ k ][ j ]+cost[ i ][ k ]+cost[ k ][ j ] ) ( i<k<j )
则dp[ 0 ][ n-1 ]即为答案。
#include<bits/stdc++.h> using namespace std; const int N=305; const int inf=0x3f3f3f3f; int n,tot,dp[N][N],cost[N][N],mod; struct point { int x,y; point(int _x=0,int _y=0){x=_x; y=_y;} point operator -(const point &rhs)const { return point(x-rhs.x,y-rhs.y); } }p[N],cp[N]; typedef point vec; int cross(const vec &a,const vec &b) { return (a.x*b.y)-(a.y*b.x); } int dis(point a,point b) { return (a.x-b.x)*(a.x-b.x)+(a.y-b.y)*(a.y-b.y); } bool cmp(const point &a,const point &b) { int z=cross(a-p[0],b-p[0]); if(z>0||(z==0 && dis(p[0],a)<dis(p[0],b))) return 1; else return 0; } void get_cp() { int k=0; tot=0; for(int i=0;i<n;i++) if(p[i].y<p[k].y || p[i].y==p[k].y && p[i].x<p[k].x) k=i; swap(p[0],p[k]); sort(p+1,p+n,cmp); tot=2,cp[0]=p[0];cp[1]=p[1]; for(int i=2;i<n;i++) { while(tot>1 && cross(cp[tot-2]-cp[tot-1],p[i]-cp[tot-1])>0) tot--; cp[tot++]=p[i]; } } void init() { memset(dp,-1,sizeof(dp)); memset(cost,0,sizeof(cost)); } int solve(int l,int r) { if(dp[l][r]!=-1) return dp[l][r]; if(r-l<=2) return 0; dp[l][r]=inf; for(int k=l+1;k<=r-1;k++) dp[l][r]=min(solve(l,k)+solve(k,r)+cost[l][k]+cost[k][r],dp[l][r]); return dp[l][r]; } int main() { while(scanf("%d%d",&n,&mod)!=EOF) { for(int i=0;i<n;i++) scanf("%d%d",&p[i].x,&p[i].y); get_cp(); if(tot<n) { puts("I can't cut."); continue; } init(); for(int i=0;i<n;i++) for(int j=i+2;j<n;j++) cost[i][j]=cost[j][i]=abs(cp[i].x+cp[j].x)*abs(cp[i].y+cp[j].y)%mod; printf("%d\n",solve(0,n-1)); } return 0; }
B - Halloween Costumes
题目大意:有一个人去参加n场派对,不同种类的派对需要的衣服是不一样的,问你他最少需要多少件衣服,他最多可以套无限个衣服,脱掉的衣服不能重新利用。
思路:用dp[ i ][ j ]表示从第 i 个派对开始 到 第 j 个派对结束最少需要多少衣服。
状态转移方程:
dp[ i ][ j ]=dp[ i ][ j-1 ] + 1
dp[ i ][ j ]=min( dp[ i ][ j ] , dp[ i ][ k ]+dp[ k+1 ][ j-1 ]) ( i <k<j || 第k个派对种类和第 j 种相同)
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=101; int dp[N][N],n,a[N]; int solve(int l,int r) { if(dp[l][r]!=-1) return dp[l][r]; if(l==r) return 1; if(l>r) return 0; dp[l][r]=solve(l,r-1)+1; for(int i=l;i<r;i++) { if(a[i]!=a[r]) continue; dp[l][r]=min(dp[l][r],solve(l,i)+solve(i+1,r-1)); } return dp[l][r]; } int main() { int T; scanf("%d",&T); for(int cas=1;cas<=T;cas++) { memset(dp,-1,sizeof(dp)); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&a[i]); int ans=solve(1,n); printf("Case %d: %d\n",cas,ans); } return 0; }
C - Brackets
题目大意:给你一个括号的序列,问你最多的一种匹配里面由多少个括号。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=105; int dp[N][N],n; char s[N]; int solve(int i,int j) { if(dp[i][j]!=-1) return dp[i][j]; if(i>=j) return 0; dp[i][j]=max(solve(i+1,j),solve(i,j-1)); for(int k=i;k<j;k++) { if(s[j]==')' && s[k]=='(') dp[i][j]=max(dp[i][j],solve(i,k-1)+solve(k+1,j-1)+1); if(s[j]==']' && s[k]=='[') dp[i][j]=max(dp[i][j],solve(i,k-1)+solve(k+1,j-1)+1); } return dp[i][j]; } int main() { while(scanf("%s",s+1)!=EOF) { if(s[1]=='e') break; n=strlen(s+1); memset(dp,-1,sizeof(dp)); printf("%d\n",2*solve(1,n)); } return 0; }
D - Coloring Brackets
题目大意:给你一串匹配号的括号,让你给这些括号染色,染色的规则如下:
1.每个括号只能染红色,蓝色,或者不然色。
2.匹配的括号只能有一个被染色。
3.相邻的括号被染的颜色不能相同,但可以都不染色。
思路:用dp[ i ][ j ][ p ][ q ]表示区间 i 到 j 之间且第 i 个括号的染色情况为p ,第 j 个括号的染色情况为q 的方案数。
状态转移方程分i,j 是否匹配,如果匹配从i+1 到 j-1的区间转移过来。 否则在i 到 j 之间找与与i 匹配的k 进行转移。
#include<bits/stdc++.h> using namespace std; const int N=705; const int mod=1e9+7; int a[N],n; long long f[N][N][3][3]; char s[N]; stack<int> st; long long dp(int i,int j,int p,int q) { if(f[i][j][p][q]!=-1) return f[i][j][p][q]; if(i+1==j) { if((p==0 || q==0) && (p!=0 || q!=0)) return f[i][j][p][q]=1; else return f[i][j][p][q]=0; } f[i][j][p][q]=0; if(a[i]==a[j]) { if(p!=0 && q!=0 || p==0 && q==0) return 0; for(int u=0;u<=2;u++) for(int v=0;v<=2;v++) if((u!=p || (!u && !p)) && (q!=v || (!q && !v))) f[i][j][p][q]=(f[i][j][p][q]+dp(i+1,j-1,u,v))%mod; } else { for(int k=i+1;k<j;k++) { if(a[k]!=a[i]) continue; for(int u=0;u<=2;u++) for(int v=0;v<=2;v++) if(u!=v || (!u && !v)) f[i][j][p][q]=(f[i][j][p][q]+(dp(i,k,p,u)*dp(k+1,j,v,q))%mod)%mod; break; } } return f[i][j][p][q]; } int main() { memset(f,-1,sizeof(f)); scanf("%s",s+1); n=strlen(s+1); int cnt=0; for(int i=1;i<=n;i++) { if(s[i]=='(') st.push(i); else { int cur=st.top(); cnt++; st.pop(); a[cur]=a[i]=cnt; } } long long ans=dp(1,n,0,1)+dp(1,n,1,0)+dp(1,n,0,2)+dp(1,n,2,0)+dp(1,n,0,0)+dp(1,n,1,1)+dp(1,n,1,2)+dp(1,n,2,1)+dp(1,n,2,2); printf("%lld\n",ans%mod); return 0; }
E - Multiplication Puzzle
题目大意:给你n个数字,每次取不是端点的一个数字a[ i ],总消耗加上a[ i-1 ]*a[ i ]*a[ i+1 ],直到最后剩两个数字,问你总消耗最少是多少。
思路:对于一个区间来说最后剩下的肯定是两端的数字,那么我们用dp[ i ][ j ]表示区间 i 到 j最小的消耗。枚举这个区间最后被取走的数进行状态转移。
dp[ i ][ j ]=min(dp[ i ][ j ] , dp[ i ][ k-1]+dp[ k+1 ][ j ]+a[ i ]*a[ j ]*a[ k ])
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; const int N=105; const int inf=0x3f3f3f3f; int n,f[N][N],a[N]; int dp(int i,int j) { if(f[i][j]!=-1) return f[i][j]; if(j-i<=1) return 0; f[i][j]=inf; for(int k=i+1;k<=j-1;k++) f[i][j]=min(f[i][j],a[i]*a[k]*a[j]+dp(i,k)+dp(k,j)); return f[i][j]; } int main() { while(scanf("%d",&n)!=EOF) { memset(f,-1,sizeof(f)); for(int i=1;i<=n;i++) scanf("%d",&a[i]); printf("%d\n",dp(1,n)); } return 0; }
F - Food Delivery
题目大意:在x轴上有很多人同时点了外卖,给你他们的位置,再告诉你餐厅的位置,送外卖的人走1单位的长度 需要v秒,每个人等t秒 仇恨值就会增加 t * b[ i ],
让你求除最小的仇恨值。
思路:我们先将餐厅也作为一个点加入到点的集合中,按坐标的大小进行排序。
用dp[ i ][ j ][ 0 ]表示送完i 到 j的人,并最后在 i 的最小仇恨值, 用dp[ i ][ j ][ 1 ]表示送完i 到 j的人,并最后在 j 的最小仇恨值。
从餐厅的为止向外进行dp状态转移。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=1005; const int inf=0x3f3f3f3f; int dp[N][N][2],n,v,pos,sum[N]; struct node { int x,b; bool operator < (const node &rhs)const { return x<rhs.x; } }p[N]; int cal(int l,int r) { if(l>r) return 0; return sum[r]-sum[l-1]; } int main() { while(scanf("%d%d%d",&n,&v,&pos)!=EOF) { memset(dp,inf,sizeof(dp)); for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].b); n++; p[n].x=pos,p[n].b=0; sort(p+1,p+1+n); for(int i=1;i<=n;i++) { if(p[i].x==pos) { pos=i; break; } } for(int i=1;i<=n;i++) sum[i]=sum[i-1]+p[i].b; dp[pos][pos][0]=dp[pos][pos][1]=0; for(int i=pos;i>=1;i--) { for(int j=pos;j<=n;j++) { int daley=cal(1,i-1)+cal(j+1,n); if(i==j) continue; dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][0]+(p[i+1].x-p[i].x)*(daley+p[i].b)); dp[i][j][0]=min(dp[i][j][0],dp[i+1][j][1]+(p[j].x-p[i].x)*(daley+p[i].b)); dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][0]+(p[j].x-p[i].x)*(daley+p[j].b)); dp[i][j][1]=min(dp[i][j][1],dp[i][j-1][1]+(p[j].x-p[j-1].x)*(daley+p[j].b)); } } printf("%d\n",min(dp[1][n][0],dp[1][n][1])*v); } return 0; }
G - You Are the One
题目大意:有n个排好队的人要上台表演,第k个人上台表演的消耗为( k-1 )*d[ k ]。 现在有一个栈,你可以暂时把人压入栈中,之后再出来,问你最小的消耗为多少。
思路:用dp[ i ][ j ]表示,从i个人开始上台,第j个人结束的最小消耗。
对于每个区间,我们枚举第i个人的出场顺序,那么区间就被分成了两个,这样来进行状态转移。
#include<bits/stdc++.h> using namespace std; const int inf=0x3f3f3f3f; const int N=105; int f[N][N],n,sum[N],a[N]; int dp(int i,int j) { if(i>j) return 0; if(f[i][j]!=-1) return f[i][j]; f[i][j]=inf; for(int k=1;k<=j-i+1;k++) { f[i][j]=min(f[i][j],dp(i+1,i+k-1)+dp(i+k,j)+k*(sum[j]-sum[i+k-1])+a[i]*(k-1)); } return f[i][j]; } int main() { int T; scanf("%d",&T); for(int cas=1;cas<=T;cas++) { memset(f,-1,sizeof(f)); memset(sum,0,sizeof(sum)); 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]; printf("Case #%d: %d\n",cas,dp(1,n)); } return 0; }
H - String painter
题目大意:给你两个由小写字母构成的字符串,你每次可以将第一个串的一个连续区间改成同一个小写字母,问你最少需要多少次改变,就能将第一个串变成第二个。
思路:直接将第一个串变成第二个非常困难, 我们先考虑一个空白的串变成第二个串,这样的问题区间dp就可以解决,然后通过这个对第一个串再进行一次dp就能求出答案。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; const int N=105; const int inf=0x3f3f3f3f; int f[N][N],n,ans[N]; char a[N],b[N]; int dp(int i,int j) { if(f[i][j]!=-1) return f[i][j]; if(i==j) return 1; if(i>j) return 0; f[i][j]=dp(i,j-1)+1; for(int k=i;k<j;k++) { if(b[k]!=b[j]) continue; f[i][j]=min(f[i][j],dp(i,k)+dp(k+1,j-1)); } //printf("(%d,%d) %d\n",i,j,f[i][j]); return f[i][j]; } int main() { while(scanf("%s%s",a+1,b+1)==2) { memset(f,-1,sizeof(f)); memset(ans,0,sizeof(ans)); int n=strlen(a+1); for(int i=1;i<=n;i++) ans[i]=dp(1,i); for(int i=1;i<=n;i++) { if(a[i]==b[i]) ans[i]=ans[i-1]; else { for(int j=1;j<i;j++) ans[i]=min(ans[i],ans[j]+dp(j+1,i)); } } printf("%d\n",ans[n]); } return 0; }