P188 购物问题
题目梗概:
n个物品,其中每个物品价格xi,但是某两个物品不能同时购买。
问最大的价格是多少?
思考与理解:
一开始并没有想到树形背包DP,只是一直在想是不是分组背包~
在之后瞅了瞅题解的思路之后,恍然大悟。
先把有限制的物品之间的关系转换为父子关系就可以进行DP了。
对于每个有限制的物品要不选 要么不选 选的话会有什么结果 不选的话有什么结果。
如果没有限制的话 那么肯定是要买的~
#include <cstdio> #include <algorithm> #include <cstring> #define up(a,b,c) for(register int c=a;c<=b;++c) int n,m; int dp[1005][3]; int maodun[1005][1005],value[1005],ans,son[1005][1005]; bool used[1005]; void dfs(int x){ used[x] = true; up(1,maodun[x][0],i){ if(!used[maodun[x][i]]){ son[x][++son[x][0]] = maodun[x][i]; dfs(maodun[x][i]); } } } void RunDp(int x){ if(son[x][0]==0){ dp[x][1]=value[x]; dp[x][0]=0; } else{ up(1,son[x][0],i) RunDp(son[x][i]); up(1,son[x][0],i){ int v = son[x][i]; dp[x][1]+=dp[v][2]; dp[x][2]+=std::max(dp[v][1],dp[v][2]); } dp[x][1]+=value[x]; } } int main(){ scanf("%d%d",&n,&m); up(1,n,i) scanf("%d",&value[i]); up(1,m,i){ int x,y; scanf("%d%d",&x,&y); maodun[x][++maodun[x][0]] = y; maodun[y][++maodun[y][0]] = x; } up(1,n,i){ if(maodun[i][0]==0){ ans+=value[i]; used[i] = 1; } else{ if(!used[i]){ dfs(i); RunDp(i); ans+=std::max(dp[i][1],dp[i][2]); } } } printf("%d\n",ans); return 0; }
P201 奥运大包围
题目梗概:
(题面什么鬼)问题就是求一个环从某个地方断开,然后求LIS,取LIS最小值即可.
思考:
LIS挺简单的,但必须用nlogn的二分做法,否则n^3会TLE。
#include<string.h> #include<stdio.h> #include<iostream> #include<algorithm> using namespace std; int dp[10001]; int num[10001]; int a[20001]; int nums; int ans; void cha(int x) { if(nums==0||x>=num[nums-1]) { num[nums++]=x; return ; } int l,r,mid; l=0,r=nums; mid=(l+r)/2; while(l<r) { if(num[mid]<=x)l=mid+1; else if(num[mid]>x) r=mid; mid=(l+r)/2; } num[mid]=x; } void dos(int l,int r) { nums=0; for(int i=l; i<r; i++) { cha(a[i]); } ans=min(ans,nums); } int main() { int n,i; scanf("%d",&n); ans=n; for(i=0; i<n; i++) { scanf("%d",&a[i]); } for(i=n; i<2*n; i++) { a[i]=a[i-n]; } for(i=0; i<n; i++) { dos(i,i+n); } cout<<ans<<endl; return 0; }
P202 奥运火炬登珠峰
题面梗概:
n个物品,每个物品都有一个x氧气含量,y氮气含量,z重量。
给出保障生命安全的a,b,问z的最小重量是多少。
思考:
普通的一个二位费用背包,没啥说的。只不过需要把上限a,b开大点。毕竟选的x,y可以超过这个下限a,b
#include <cstdio> #include <algorithm> #include <cstring> int dp[233][1005]; int A,T,n; int O[1005],N[1005],G[1005]; int Out=0x3f3f3f3f; int main(){ memset(dp,0x3f3f3f3f,sizeof(dp)); scanf("%d%d",&A,&T); scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d%d%d",&O[i],&N[i],&G[i]); dp[0][0]=0; for(int i=1;i<=n;i++){ for(int j=A+A;j>=O[i];j--){ for(int k=T+T;k>=N[i];k--){ dp[j][k]=std::min(dp[j][k],dp[j-O[i]][k-N[i]]+G[i]); //Out=std::min(Out,dp[j][k]); } } } for(int i=A+A;i>=A;i--){ for(int j=T+T;j>=T;j--){ Out=std::min(Out,dp[i][j]); //if(dp[i][j]==129) printf("%d %d\n",i,j); } } printf("%d\n",Out); return 0; }
P390 地震了
题面梗概:
猪脚要下楼,
n层楼 初始速度v 最多只能比别人快k,
再输入第2层到第n层的速度限制,
若能下楼,输出速度之和除以(N-1),保留两位小数。
若不能,给涛哥发个短信吧,输出“YI DING YAO JIAN CHI JI HUA SHENG YU”(不含引号)
思考:
状态挺好思考的,在当前楼层n 速度为v,你只能向(n-1,v) (n-1,v-1) (n-1,v+1)三个位置转移.
坑点是要特判一下在n层时,速度是否能满足当前楼层要求。
#include <cstdio> #include <algorithm> #include <cstring> int dp[105][205]; int n,v,k,limit[105],Max; double tot; int main(){ scanf("%d%d%d",&n,&v,&k); dp[1][v]=v; for(int i=n-1;i>=1;i--){ scanf("%d",&limit[i]); } if(v-k>limit[1] || v<limit[1]){ printf("YI DING YAO JIAN CHI JI HUA SHENG YU"); return 0; } for(int i=2;i<n;i++){ for(int j=0;j<203;j++){ if(j-limit[i]<=k && j>=limit[i]) dp[i][j] = std::max(dp[i-1][j],std::max(dp[i-1][j-1],dp[i-1][j+1])); else dp[i][j]=0; if(dp[i][j]) dp[i][j]+=j; } } for(int i=0;i<203;i++){ Max = std::max(Max,dp[n-1][i]); } if(Max==0){ printf("YI DING YAO JIAN CHI JI HUA SHENG YU"); } else{ tot = Max; tot/=(n-1); printf("%.2f\n",tot); } return 0; }
P311 乘积最大
题目梗概:
给出一个长度为n的数字串,给出k个乘号。
计算出最大乘积
思考:
dp[i][j] 表示位置为i j个乘号
dp[i][j]=max(dp[l][j-1]*sum[l+1][i],dp[i][j])
算是一道经典题目了。
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int maxn=50; long long sum[maxn][maxn],f[maxn][maxn]; int n,k; int main() { int temp[maxn]; scanf("%d%d",&n,&k); for(int i=1;i<=n;i++) scanf("%1d",&temp[i]); for(int i=1;i<=n;i++) for(int j=i;j<=n;j++) sum[i][j]=sum[i][j-1]*10+temp[j];//预处理,将i到j位的数字转化为数 for(int i=1;i<=n;i++) f[i][0]=sum[1][i]; for(int l=1;l<=k;l++)//枚举每一个乘号 for(int i=l+1;i<=n;i++)//枚举每一个数位 for(int j=l;j<i;j++)//找断开的部分 f[i][l]=max(f[i][l],f[j][l-1]*sum[j+1][i]); printf("%lld\n",f[n][k]); return 0; }
P329 刘翔!加油
P671 纯洁的买卖
题面梗概:
猪脚有m元经费,他会以x买入某件物品,以y的价格卖出。问最后会有多少钱。
思考:
背包问题,只不过需要处理一下xy的关系。
#include <cstdio> #include <algorithm> #include <cstring> int n,m; int dp[1000005]; int x,y; int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ scanf("%d%d",&x,&y); y-=x*2; for(int j=x;j<=m;j++){ if(j-x>=0)dp[j]=std::max(dp[j],dp[j-x]+y); //printf("dp[%d]:%d\n",j,dp[j]); } } printf("%d\n",dp[m]+m); return 0; }
P329 刘翔加油
题目梗概:
已经不想解释这种背包DP的题目了(给出链接自己看吧),都是一个套路。
思考:
二维费用背包,只不过按照题目要求。需要题目提前排序,所以需要结构体。
#include <cstdio> #include <algorithm> int n,m,t; struct node{ int value,time,h,w; int num; }now[105]; int dp[105][105]; bool CMP(const node &a,const node &b){ return a.value<b.value; } int main(){ scanf("%d%d%d",&n,&m,&t); for(int i=1;i<=n;i++){ scanf("%d%d%d%d",&now[i].value,&now[i].time,&now[i].h,&now[i].w); now[i].num=i; } std::sort(now+1,now+1+n,CMP); for(int i=1;i<=n;i++){ for(int j=m;j-now[i].h>=1;j--){ for(int k=t;k-now[i].time>=0;k--){ dp[j][k] = std::max(dp[j-now[i].h][k-now[i].time]+now[i].w,dp[j][k]); } } } printf("%d\n",dp[m][t]); }
P429 词链
题目梗概:
给出一些单词,从给定单词中选出某个单词是另一个单词前缀的情况并统计。求最大的数量。
思考:
trie树维护,差不多是模板题目。
#include<stdio.h> #include<string.h> #include<algorithm> #include<iostream> using namespace std; #define maxn 51*100000 struct list{ int id,num; struct list *next[27]; }*root; int ans; struct list *build(){ int i; struct list *p; p=new list; p->id=0; p->num=0; for(i=0;i<27;i++) p->next[i]=0; return p; } void query(char str[]){ int mn; mn=0; struct list *p; p=root; int n,j; n=strlen(str); for(j=0;j<n;j++){ int c=str[j]-'a'; if(p->next[c]==NULL) p->next[c]=build(); p=p->next[c]; mn=max(mn,p->num); } p->num=mn+1; ans=max(ans,mn+1); } int main(){ int n,i; char str[101]; scanf("%d",&n); ans=0; root=build(); for(i=0;i<n;i++){ scanf("%s",str); query(str); } printf("%d\n",ans); }
P329 炮兵阵地
题目梗概:
思考:
对于每行如果放置则为1,不放置则为0.
对于每行的情况来说,连续的炮兵必须间隔2格以上。所以得预处理一下。并统计一下这种情况的兵数量
先预处理出第一行的情况。
之后dp[i][j][k]=max(dp[i][j][k],dp[i-1][k][m]+num[i]) 表示第i行 当前状态为j 上一行状态为k
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 105; int Map[N]; int dp[N][65][65]; int s[N], num[N]; int n, m, p; bool check(int x) { if(x & (x >> 1)) return false; if(x & (x >> 2)) return false; return true; } int Count(int x) { int i = 1, ans = 0; while(i <= x) { if(x & i) ans++; i <<= 1; } return ans; } void Init() { p = 0; memset(s, 0, sizeof(s)); memset(num, 0, sizeof(num)); for(int i = 0; i < (1 << m); i++) { if(check(i)) { s[p] = i; num[p++] = Count(i); } } } int main() { char ch; scanf("%d%d", &n, &m); if(!n && !m) { printf("0\n"); return 0; } memset(dp, 0, sizeof(dp)); memset(Map, 0, sizeof(Map)); for(int i = 0; i < n; i++) { for(int j = 0; j < m; j++) { cin >> ch; if(ch == 'H') Map[i] = Map[i] | (1 << (m - 1 - j)); //P为0,H为1 } } Init(); for(int i = 0; i < p; i++) { //printf("num[i]:%d s[i]:%d\n",num[i],s[i]); if(!(Map[0] & s[i])) dp[0][i][0] = num[i]; } for(int i = 0; i < p; i++) { if(!(Map[1] & s[i])) { for(int j = 0; j < p; j++) { if((!(s[i] & s[j]))) { dp[1][i][j] = max(dp[1][i][j], dp[0][j][0] + num[i]); } } } } for(int r = 2; r < n; r++) { for(int i = 0; i < p; i++) { if(!(s[i] & Map[r])) { for(int j = 0; j < p; j++) { if(!(s[j] & Map[r-1])) { if(!(s[i] & s[j])) { for(int k = 0; k < p; k++) { if(!(s[k] & Map[r-2])) { if(!(s[j] & s[k])) { if(!(s[i] & s[k])) { dp[r][i][j] = max(dp[r][i][j], dp[r-1][j][k] + num[i]); } } } } } } } } } } int ans = 0; for(int i = 0; i < p; i++) { for(int j = 0; j < p; j++) { if(ans < dp[n-1][i][j]) ans = dp[n-1][i][j]; } } printf("%d\n", ans); return 0; }
P57 找啊找啊找GF
题面梗概:
n个MM,每个MM需要花费rmb,rp,time,猪脚有m块RMB,r的人品值。
在保证MM数量的前提下,最小花费时间是多少。
思考:
一开始想着只用一个二维费用背包搞,记录MM数量最大是多少,然后在这个数量中查找一个最小值时间。
但是发现莫名其妙WA了,看了看题解。
发现做法很妙:
如果MM数量多,就保留多的。
如果MM数量一样,那么久保留时间短的。
#include <cstdio> #include <cstring> #include <algorithm> int n,m,r; int dp[105][105]; int TIME[105][105]; int x[105],y[105],z[105]; int Min=0x3f3f3f3f; int main(){ scanf("%d",&n); for(int i=1;i<=n;i++){ scanf("%d%d%d",&x[i],&y[i],&z[i]); } scanf("%d%d",&m,&r); for(int i=1;i<=n;i++){ for(int j=m;j>=x[i];j--){ for(int k=r;k>=y[i];k--){ if(dp[j][k] < dp[j-x[i]][k-y[i]]+1){ dp[j][k] = dp[j-x[i]][k-y[i]]+1; TIME[j][k] = TIME[j-x[i]][k-y[i]]+z[i]; } if(dp[j][k] == dp[j-x[i]][k-y[i]]+1 && TIME[j][k] > TIME[j-x[i]][k-y[i]]+z[i]){ TIME[j][k] = TIME[j-x[i]][k-y[i]]+z[i]; } //printf("dp[%d][%d]:%d\n",j,k,dp[j][k]); } } } printf("%d",TIME[m][r]); return 0; }
P140 分配时间
题面梗概:
n个科目,t时间。
每个科目花费不同的时间可以得到不同的分数,但每门科目需要之前写名字花费x时间。
问最多可以拿到几分?
思考:
分组背包,只不过需要特出处理一下写名字的时间。
#include <cstdio> #include <algorithm> #include <cstring> int T,n,name; int num[23][233]; int dp[233333]; int main(){ scanf("%d%d%d",&T,&n,&name); //读入 for(int i=1;i<=n;i++){ for(int j=1;j<=T;j++){ scanf("%d",&num[i][j+name]); } } for(int k=1;k<=n;k++){ for(int i=T;i>=name;i--){ for(int j=i;j>=name;j--){ dp[i] = std::max(dp[i],dp[i-j]+num[k][j]); } } } printf("%d\n",dp[T]); return 0; }
P169 最小乘车费用
题面梗概:
每个车最多可以行驶10公里,行驶1,2,3,4...10公里的费用各不相同。
问行驶x公里的,最小费用是多少。
思考:
背包问题.
#include <cstdio> #include <algorithm> #include <cstring> int n,num[105]; int dp[233]; int main(){ memset(dp,0x3f3f3f3f,sizeof(dp)); for(int i=1;i<=10;i++) scanf("%d",&num[i]),dp[i]=num[i]; scanf("%d",&n); for(int i=1;i<=n;i++){ for(int j=1;j<=10;j++){ if(i>j) dp[i]=std::min(dp[i],dp[i-j]+num[j]); else break; //printf("dp[%d]:%d\n",i,dp[i]); } } printf("%d\n",dp[n]); return 0; }
P95 多多看DV(加强版)
题面梗概:
爷爷给出T单位时间,商店有n个碟片,叔叔必须买m个(不能多不能少),输入的每个碟片会花费x的时间,会得到y的快乐。
如果碟片没法在规定时间内看完输出0,否则输出今晚的总分.
思考:
题面需要转化一下,把时间当作一个物品属性。进行二维费用背包。
贪心的思考一下:如果n个物品中,m个物品的时间>T的话。直接输出0
#include <cstdio> #include <algorithm> #include <cstring> int n,limit,l; int t[105],m[105],fuck[105],Can[105]; int dp[233333][233]; int main(){ memset(dp,-0x3f3f3f3f,sizeof(dp)); scanf("%d%d%d",&n,&limit,&l); for(int i=1;i<=n;i++){ scanf("%d%d",&t[i],&m[i]); Can[i]=t[i]; fuck[i]=1; } std::sort(Can+1,Can+1+n); int ans=0; for(int i=1;i<=limit;i++){ ans+=Can[i]; } if(ans>l){ printf("0\n"); return 0; } for(int i=0;i<=l;i++) dp[i][0]=0; for(int i=1;i<=n;i++){ for(int j=l;j>=t[i];j--){ for(int k=limit;k>=1;k--){ dp[j][k] = std::max(dp[j][k],dp[j-t[i]][k-1]+m[i]); } } } printf("%d\n",dp[l][limit]); return 0; }