DP五十题
目录
-
1.合唱队形
俩遍LIS -
2.导弹拦截
nlogn LIS
某定理:
Dilworth定理的大概意思:最少的下降序列个数==整个序列最长上升子序列的长度 -
3.尼克的任务
倒序递推 -
4.丝绸之路
顺推 -
6.低价购买
LIS+DP套DP
在O(n^2)版的LIS的f数组上计数 -
7.回文字串
第二个数组为倒着的串
对这俩串做一遍LCS
大胆猜结论:结论:要添加的个数就是len-LCS的长度 -
[ 8.模板]最长公共子序列
映射,转化为求LIS -
10.创意吃鱼法
思路与最大子正方形相似,注意要考虑从左上到右下和右上到左下 -
[ 24.USACO08JAN]跑步Running
-
[30.HNOI2004]打鼹鼠
-
[32.CQOI2007]涂色
-
[ 33.BeijingWc2008]雷涛的小猫
-
[ 38.HAOI2012]音量调节
板子
范围:
集合:
转移:
终态:
初始化:
P1474 货币系统 Money Systems
完全背包求方案数
不限制每种货币取的个数,则需要使用完全背包,这里是统计方案数,根据加法原理(分步相加)得出转移方程:f[j]=f[j-a[i]]
.
记得初始化f[0]=1;
P1233 木棍加工
思想:归约:按照第一个关键字从大到小排序后就只用考虑另一个关键字的降序问题;
SOL:
要求的是第二个关键字的最长下降子序列的最小个数,根据导弹拦截第二问用到的定理Dilworth定理:最少的下降序列个数就等于整个序列最长上升子序列的长度,求一遍LIS,因为n<=5e3,我就没用nlogn,\(n^2\)跑一边就好了
const int N=5e3+10;
struct qujian{
int a,b;
bool operator <(const qujian &rhs)const{
if(a!=rhs.a)return a>rhs.a;
return b>rhs.b;
}
}mg[N];
int f[N],maxx,n;
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n);
rep(i,1,n){
rd(mg[i].a),rd(mg[i].b);
}
sort(mg+1,mg+n+1);
rep(i,1,n){
f[i]=1;
rep(j,1,i-1)
if(mg[j].b<mg[i].b)
f[i]=max(f[i],f[j]+1);
maxx=max(maxx,f[i]);
}
printf("%lld\n",maxx);
return 0;
}
好吧,还是悄咪咪复习一下O(nlogn)的做法
f[++len]=mg[1].b;
rep(i,2,n){
if(mg[i].b>f[len])f[++len]=mg[i].b;
else{
int p=lower_bound(f+1,f+len+1,mg[i].b)-f;//求按第二关键字的LIS
f[p]=mg[i].b;
}
}
printf("%lld\n",len);
P2904 USACO08MAR跨河River Crossing
【前置技能】:语文足够好
【独立思考+1A】
设f[i]表示1~i只牛全部运输过去的最小花费
预处理sum[i]:运i只牛的总花费
转移方程:f[i]=min(f[i],f[i-j]+sum[j]+M)
//加M只因为FJ还要回到对岸
记得最后答案再减去一个M,因为最后一次到对岸的时候FJ已经不用再去接Bessie们了
const int N=2510;
int m[N],sum[N],f[N];
int n,M,cnt;
int main(){
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n),rd(M);
sum[0]=M;
rep(i,1,n){
rd(m[i]);
sum[i]=sum[i-1]+m[i];
}
mem(f,0x3f);
f[0]=0;
rep(i,1,n){
rep(j,1,i){
f[i]=min(f[i],f[i-j]+sum[j]+M);
}
}
printf("%d",f[n]-M);
return 0;
}
P1336 最佳课题选择
范围:1e2的级别->\(O(n^3)\)的算法
集合:考虑物尽其用,给了m个课题就一个一个课题的去更新,记录前i个课题完成了j篇论文的最小时间。那么转移就可以根据m的阶段来划分,从m的上一个阶段递推。
转移:这道题的转移方程挺容易想的:f[i][j]=min(f[i][j],f[i−1][j−k]+a[i]∗k^b[i])
终态:f[m][n]
初始化:根据注意到i-1可能为0,那转移就从这里开始。
const int N=210;
int f[N][N];
int n,m;
int a[N],b[N];
inline int ksm(int a,int k){
int res=1;
for(;k;k>>=1){
if(k&1)res=res*a;
a=a*a;
}
return res;
}
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n),rd(m);//n:论文数,m:课题数
rep(i,1,m)rd(a[i]),rd(b[i]);
rep(i,1,n)f[0][i]=INT_MAX;
rep(i,1,m)
rep(j,1,n){
f[i][j]=INT_MAX;
rep(k,0,j)
f[i][j]=min(f[i][j],f[i-1][j-k]+a[i]*ksm(k,b[i]));
}
printf("%lld\n",f[m][n]);
return 0;
}
P2285 HNOI2004打鼹鼠
很妙的LIS!!!
朴素DP方法:f[i][j][k]
机器人第k时刻在i,j的最大值,but!1e3的n和1e4的m直接炸掉!
范围:1e4的级别->\(O(n^2)\)的算法
集合:发现地鼠的转移随时间递增(根据LIS的思想),能从上个地鼠转移到此时刻的地鼠的条件是在时间间隔内可以移动俩点的曼哈顿距离。
转移:
if(abs(x[i]-x[j])+abs(y[i]-y[j])<=t[i]-t[j])
f[i]=max(f[i],f[j]+1);
终态:记录f[i]的最大值
初始化:f[i]=1
但是此题不可以为了追求O(nlogn)而用 LIS 的优化,因为此题的序列没有传递性
const int N=1e4+10;
int x[N],y[N],t[N];
int f[N];
int n,m,maxx;
#undef int
int main(){
#define int long long
#ifdef WIN32
freopen("c.txt","r",stdin);
#endif
rd(n),rd(m);
rep(i,1,m){
rd(t[i]),rd(x[i]),rd(y[i]);
}
rep(i,1,m){
f[i]=1;
rep(j,1,i-1){
if(abs(x[i]-x[j])+abs(y[i]-y[j])<=t[i]-t[j])
f[i]=max(f[i],f[j]+1);
}
maxx=max(maxx,f[i]);
}
printf("%lld\n",maxx);
return 0;
}
P1063 能量项链
范围:1e2的级别->\(O(n^3)\)的算法。区间DP的标准复杂度。
集合:f[l][r]
表示目前已经合并l->r这段区间的最大值
转移:f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]);
终态:max{f[i][i+n]}
,这是个环形DP
初始化:f[i][i]=0
但是我懒得打。
int f[405][405];
int n,a[205];
int main(){
#ifdef WIN32
freopen("a.txt","r",stdin);
#endif
rd(n);
rep(i,1,n)
rd(a[i]),a[i+n]=a[i];
rep(len,1,n)
for(int l=1;l+len<=2*n;++l){
int r=l+len;
rep(k,l+1,r-1)
f[l][r]=max(f[l][r],f[l][k]+f[k][r]+a[l]*a[k]*a[r]);
}
int res=0;
rep(i,1,n)
res=max(res,f[i][n+i]);
printf("%d",res);
return 0;
}
P1077 摆花
因为是求方案数,我们考虑当前这一种方案的上一个状态是什么。第i种花可以取k:0~a[ i ]个,那么前i-1种花的方案数有f[i-1][j-k]
种,每一种都可以转移到当前状态,根据加法原理,直接相加。
范围:1e2的级别->\(O(n^3)\)的算法。区间DP的标准复杂度。
集合:f[i][j]
摆完前i种花共摆了j盆的方案。
转移:见代码
终态:RT,f[n][m]
初始化:f[0][0]=1
const int N=1e3+10;
const int mod=1000007;
int f[N][N];//摆完前i种花共摆了j盆的方案。
int a[N],n,m;
int main(){
rd(n),rd(m);
rep(i,1,n)rd(a[i]);
f[0][0]=1;
rep(i,1,n)
rep(j,0,m)
rep(k,0,a[i])
if(j-k>=0)
f[i][j]=(f[i][j]+f[i-1][j-k])%mod;
printf("%d",f[n][m]%mod);
return 0;
}
P1417 烹调方案
算法:01背包的带权变形,很显然嘛,如果不考虑t的影响,这题就是个裸的01背包,那么我们考虑t这个权值妖艳在哪里;
现在考虑相邻的两个物品x,y。假设现在已经耗费p的时间,那么分别列出先做x,y的代价:
a[x]-(t+c[x])* b[x]+a[y]-(t+c[x]+c[y])*b[y] (①)
a[y]-(t+c[y])* b[y]+a[x]-(t+c[y]+c[x])*b[x] (②)
对这两个式子化简,得到①>②的条件是c[x]* b[y]<c[y]*b[x].
发现只要满足这个条件的物品对(x,y),x在y前的代价永远更优。
因此可以根据这个条件进行排序,之后就是简单的01背包了。
注意开long long,会爆int!!!见祖宗了呢
const int N=55;
struct data{
int a,b,c;
bool operator <(const data &rhs)const {
return b*rhs.c>c*rhs.b;
}
}x[N];
int T,n,ans;
int f[100010];
#undef int
int main(){
#define int long long
rd(T),rd(n);
rep(i,1,n){rd(x[i].a);}
rep(i,1,n){rd(x[i].b);}
rep(i,1,n){rd(x[i].c);}
sort(x+1,x+n+1);
rep(i,1,n)
dwn(j,T,x[i].c){
f[j]=max(f[j],f[j-x[i].c]+x[i].a-x[i].b*j);
ans=max(ans,f[j]);
}
printf("%lld",ans);
return 0;
}
P1412 经营与开发
const int N=1e5+10;
int op[N],x[N];
double f[N];
int n,k,c,w;
int main(){
rd(n),rd(k),rd(c),rd(w);
rep(i,1,n){
rd(op[i]),rd(x[i]);
}
dwn(i,n,1){
if(op[i]==1)
f[i]=max(f[i+1],f[i+1]*(1-0.01*k)+x[i]);
else
f[i]=max(f[i+1],f[i+1]*(1+0.01*c)-x[i]);
}
printf("%.2lf",f[1]*w);
return 0;
}