dp
dp题单:https://ac.nowcoder.com/acm/problem/collection/576?
0星
滑雪
dfs更简单,按照题意。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; int dp[2100][2100],a[2100][2100],m,n,ans=1; int dx[4]={0,0,1,-1}; int dy[4]={1,-1,0,0}; int dfs(int x,int y){ if(dp[x][y]) return dp[x][y]; else dp[x][y]=1; for(int i=0;i<4;i++){ int fx=x+dx[i]; int fy=y+dy[i]; if(fx<1||fx>m||fy<1||fy>n||a[x][y]>=a[fx][fy]) continue; dp[x][y]=max(dp[x][y],dfs(fx,fy)+1); } return dp[x][y]; } int main(){ cin>>m>>n; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ cin>>a[i][j]; } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ ans=max(ans,dfs(i,j)); } } cout<<ans<<endl; }
1星
石子合并
类似区间dp,dp[i][j]的意义为从i到j最优解,跟取数游戏2那个思路上有类似的地方,但是它又多了一步,其实也是暴力。
状态转移方程:dp[j][j+i-1]=min(dp[j][j+i-1],dp[j][k]+dp[k+1][j+i-1]+sum[j][j+i-1]);
sum[i][j]为从i到j的石子和
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int dp[500][500],sum[500][500],a[500],n; int main(){ int n; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ for(int j=i;j<=n;j++){ sum[i][j]+=sum[i][j-1]+a[j]; } } memset(dp,0x3f3f3f3f,sizeof(dp)); for(int i=1;i<=n;i++) dp[i][i]=0; for(int i=2;i<=n;i++){ for(int j=1;j<=n-i+1;j++){ for(int k=j;k<j+i-1;k++){ dp[j][j+i-1]=min(dp[j][j+i-1],dp[j][k]+dp[k+1][j+i-1]+sum[j][j+i-1]); // cout<<dp[j][k]<<' '<<dp[k+1][j+i-1]<<' '<<i<<' '<<j<<' '<<k<<endl; } } } /*for(int i=1;i<=n;i++){ for(int j=i+1;j<=n;j++){ cout<<dp[i][j]<<' '; } cout<<endl; }*/ cout<<dp[1][n]<<endl; }
[木]迷雾森林
直接看代码吧
AC代码:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; const int M=2333; int a[3100][3100],dp[3100][3100]; template<class T>inline void read(T &res){ char c;T flag=1; while((c=getchar())<'0'||c>'9')if(c=='-')flag=-1;res=c-'0'; while((c=getchar())>='0'&&c<='9')res=res*10+c-'0';res*=flag; } int main(){ int n,m; read(m);read(n); for(int i=m-1;i>=0;i--){ for(int j=0;j<n;j++){ read(a[i][j]); } } dp[0][0]=1; for(int i=0;i<m;i++){ for(int j=0;j<n;j++){ if(a[i][j]==1) dp[i][j]=0; else{ if(i-1>=0) dp[i][j]+=dp[i-1][j]; if(j-1>=0) dp[i][j]+=dp[i][j-1]; } dp[i][j]%=M; } } cout<<dp[m-1][n-1]%M<<endl; return 0; }
2星
取数游戏2
以前第一次接触dp做过的一道题目,只想出了一半。
它运用了区间dp,dp一般是从小的最优子结构推出大的最优结构,而这个题目则是从区间长度为1开始慢慢推到整个区间,当然每个具有相同长度的小区间都求完之后(也就是遍历)才会去推下一个区间。需要注意的是它是由内向外推,改了好久才发现。
和本题类似的有3星矩阵取数游戏。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int a[3100],b[3100],dp[3100][3100]; int main(){ int t,n; cin>>t; while(t--){ memset(a,0,sizeof(a)); memset(b,0,sizeof(b)); memset(dp,0,sizeof(dp)); cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++) cin>>b[i]; for(int i=1;i<=n;i++) dp[i][i]=b[n]*a[i]; for(int j=1;j<=n;j++){ for(int i=1;i<=n;i++){ if(i+j<=n) dp[i][i+j]=max(dp[i+1][i+j]+a[i]*b[n-j],a[i+j]*b[n-j]+dp[i][j+i-1]); else break; // cout<<i<<' '<<i+j<<endl; // cout<<dp[i+1][i+j]<<'+'<<a[i]<<'*'<<b[n-j]<<' '<<a[i+j]<<'*'<<b[n-j]<<'+'<<dp[i][j+i-1]<<endl; } } cout<<dp[1][n]<<endl; } }
传球游戏
这道题不要想太多,直接递推就可以,就是需要注意它是一个圆,还有初始化。
AC代码:
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 using namespace std; 5 int dp[1100][1100]; 6 int main(){ 7 int n,m; 8 cin>>n>>m; 9 dp[0][1]=1; 10 for(int i=1;i<=m;i++){ 11 for(int j=1;j<=n;j++){ 12 if(j==1) dp[i][j]=dp[i-1][2]+dp[i-1][n]; 13 else if(j==n) dp[i][j]=dp[i-1][n-1]+dp[i-1][1]; 14 else dp[i][j]=dp[i-1][j-1]+dp[i-1][j+1]; 15 } 16 } 17 cout<<dp[m][1]<<endl; 18 }
方格取数
这道题也是很神奇,暴力出奇迹(因为数据比较小),4个for循环。
状态转移方程:dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])))+a[i][j]+a[k][l];
它们的步数一定相同,状态转移要考虑两个人,所以简单的贪心不太可,需要同时考虑两个人(重点),两个人都走一步。
要大胆开多维数组
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int dp[21][21][21][21],a[2100][2100],pre[1100]; int main(){ int n,x,y,w; cin>>n; while(scanf("%d%d%d",&x,&y,&w)){ if(x==0&&y==0&&w==0) break; a[x][y]=w; } // pre[1]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=n;j++){ for(int k=1;k<=n;k++){ for(int l=1;l<=n;l++){ if(i+j!=k+l) continue; dp[i][j][k][l]=max(dp[i-1][j][k-1][l],max(dp[i-1][j][k][l-1],max(dp[i][j-1][k-1][l],dp[i][j-1][k][l-1])))+a[i][j]+a[k][l]; if(l==j&&k==i) dp[i][j][k][l]-=a[i][j]; } } } } cout<<dp[n][n][n][n]<<endl; }
CSL分苹果
背包问题,就是在不超过sum/2的情况下,尽可能取多的苹果。与01背包相比,01背包的空间=苹果的质量,01背包的价值也=苹果的质量
状态转移方程:dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]);
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int a[11100],dp[11000][11000]; int main(){ int n,sum=0; cin>>n; for(int i=1;i<=n;i++){ cin>>a[i]; sum+=a[i]; } for(int i=1;i<=n;i++){ for(int j=1;j<=sum/2;j++){ dp[i][j]=dp[i-1][j]; if(j-a[i]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]); } } cout<<dp[n][sum/2]<<' '<<sum-dp[n][sum/2]<<endl; }
Rabbit的工作(1)
这道题主要是在dp数组开的意义,dp[i][j][k]表示第i天工作j天此时连续工作k天,但是多开一维,容易超内存,滚动循环。
只要数组开出来之后 ,就应该会推了,状态转移方程:
if(s[i]=='1'){dp[j][0]=max(dp[j][0],dp[j][k]);dp[j][k]=min(dp[j][k],dp[j-1][k-1]+k);}//两种情况,休息或者继续工作
else dp[j][0]=max(dp[j][0],dp[j][k]);//只能休息,需要注意的是dp[j][0]=min(dp[j][x])x属于[0,j]
建议自己再写一个三维的更好理解。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; char s[1100]; int dp[1100][1100]; int main(){ int n,K; cin>>n>>K; cin>>s+1; memset(dp,0x3f3f3f3f,sizeof(dp)); dp[0][0]=0; for(int i=1;i<=n;i++){ for(int j=i;j;j--){ for(int k=1;k<=j;k++){ dp[j][0]=min(dp[j][0],dp[j][k]); if(s[i]=='1'){ dp[j][k]=min(dp[j][k],dp[j-1][k-1]+k); } } } } int ans=0; for(int i=0;i<=n;i++){ for(int j=0;j<=i;j++){ if(dp[i][j]<=K) ans=max(ans,i); } } cout<<ans<<endl; return 0; }
数字三角形
看代码就好
AC代码:
#include<iostream> #include<cstdio> #include<cstring> using namespace std; int main(){ int n,cnt=0; cin>>n; for(int i=1;i<=n;i++){ for(int j=1;j<=i;j++){ printf("%4d",++cnt); } cout<<endl; } return 0; }
小A买彩票
为数不多自己写出的题目,也是按照题意来即可。
dp[i][j]的含义为当前选择为第i次得分为j时,所总共有的方法数目。
状态转移方程:if(j>=1) dp[i][j]+=dp[i-1][j-1];
if(j>=2) dp[i][j]+=dp[i-1][j-2];
if(j>=3) dp[i][j]+=dp[i-1][j-3];
if(j>=4) dp[i][j]+=dp[i-1][j-4];
最后总的方法数与可能的方法数gcd即为结果。
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; typedef long long ll; ll dp[37][3100]; ll gcd(ll a,ll b){ return b==0?a:gcd(b,a%b); } int main(){ int n; cin>>n; if(n==1){ cout<<"1/2"<<endl; return 0; }else if(n==0){ cout<<"1/1"<<endl; return 0; } dp[1][1]=dp[1][2]=dp[1][3]=dp[1][4]=1; for(int i=1;i<=n;i++){ for(int j=1;j<=i*4;j++){ if(j>=1) dp[i][j]+=dp[i-1][j-1]; if(j>=2) dp[i][j]+=dp[i-1][j-2]; if(j>=3) dp[i][j]+=dp[i-1][j-3]; if(j>=4) dp[i][j]+=dp[i-1][j-4]; } } ll ans=0; for(int i=3*n;i<=4*n;i++){ ans+=dp[n][i]; } ll k=(ll)1; for(int i=1;i<=n;i++){ k*=4; } cout<<ans/gcd(k,ans)<<"/"<<k/gcd(k,ans)<<endl; return 0; }
三星
合并回文子串
经典的区间dp,利用小的最优子区间去推大的最优子区间,最终达到整个区间。
dp[r1][l1][r2][l2]表示的是字符串1从r1到l1,字符串2从r2到l2是否能够构成一个回文子串,利用丨(或)运算较为方便。
状态转移方程:if(i+j<=1) dp[r1][l1][r2][l2]=1;
else{
dp[r1][l1][r2][l2]=0;
if(a[r1]==a[l1]&&i>1) dp[r1][l1][r2][l2]|=dp[r1+1][l1-1][r2][l2];
if(a[r1]==b[l2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1+1][l1][r2][l2-1];
if(b[r2]==b[l2]&&j>1) dp[r1][l1][r2][l2]|=dp[r1][l1][r2+1][l2-1];
if(a[l1]==b[r2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1][l1-1][r2+1][l2];
}
每次判断完再比较与ans的值;当然其中也有很多细节,不多赘述。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int dp[60][60][60][60]; int main(){ int n; cin>>n; while(n--){ char a[311],b[311]; cin>>a+1>>b+1; memset(dp,0,sizeof(dp)); int len1=strlen(a+1),len2=strlen(b+1),ans=0; for(int i=0;i<=len1;i++){ for(int j=0;j<=len2;j++){ for(int r1=1,l1=i;l1<=len1;r1++,l1++){ for(int r2=1,l2=j;l2<=len2;r2++,l2++){ if(i+j<=1) dp[r1][l1][r2][l2]=1; else{ dp[r1][l1][r2][l2]=0; if(a[r1]==a[l1]&&i>1) dp[r1][l1][r2][l2]|=dp[r1+1][l1-1][r2][l2]; if(a[r1]==b[l2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1+1][l1][r2][l2-1]; if(b[r2]==b[l2]&&j>1) dp[r1][l1][r2][l2]|=dp[r1][l1][r2+1][l2-1]; if(a[l1]==b[r2]&&i&&j) dp[r1][l1][r2][l2]|=dp[r1][l1-1][r2+1][l2]; } if(dp[r1][l1][r2][l2]) ans=max(ans,i+j); } } } } cout<<ans<<endl; } return 0; }
Butterfly
这道题做起来更像一道暴力题,并不需要dp
首先设了正上,左上,右上,左下,右下,各自方向连续X数目的多少zt[2100][2100],zs[2100][2100],ys[2100][2100],zx[2100][2100],yx[2100][2100];
然后进行遍历判断,找最大的蝴蝶翅膀的数目。
当然蝴蝶的中心可以是O所以也需要判断,需要注意这个细节问题。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; char s[2100][2100]; int zt[2100][2100],zs[2100][2100],ys[2100][2100],zx[2100][2100],yx[2100][2100]; int main(){ int n,m,tt=0; cin>>m>>n; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ cin>>s[i][j]; } } for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ if(s[i][j]=='X'){ zs[i][j]=zs[i-1][j-1]+1; zt[i][j]=zt[i-1][j]+1; tt=max(tt,zt[i][j]); } } } for(int i=m;i>=1;i--){ for(int j=n;j>=1;j--){ if(s[i][j]=='X'){ yx[i][j]=yx[i+1][j+1]+1; } } } for(int i=m;i>=1;i--){ for(int j=1;j<=n;j++){ if(s[i][j]=='X'){ zx[i][j]=zx[i+1][j-1]+1; } } } for(int i=1;i<=m;i++){ for(int j=n;j>=1;j--){ if(s[i][j]=='X'){ ys[i][j]=ys[i-1][j+1]+1; } } } int ans=0; for(int i=1;i<=m;i++){ for(int j=1;j<=n;j++){ int t=min(min(ys[i][j],yx[i][j]),min(zs[i][j],zx[i][j])); if(s[i][j]=='X'){ for(int k=min(t,tt);k>=ans;k--){ if(zt[i+k-1][j-k+1]>=2*k-1&&zt[i+k-1][j+k-1]>=2*k-1){ ans=k;break; } } } else{ if(i-1<1||i+1>m||j-1<1||j+1>n) continue; int kong=min(min(zs[i-1][j-1],ys[i-1][j+1]),min(zx[i+1][j-1],yx[i+1][j+1])); for(int k=min(kong,tt);k>=ans;k--){ if(zt[i+k-1][j-k+1]>=2*k-1&&zt[i+k-1][j+k-1]>=2*k-1){ ans=k;break; } } } } } cout<<max(0,ans*2-1)<<endl; } /* 5 6 OOOOXX XXXXOO OXOXOX XOXOXO XOXOXO */
矩阵取数游戏
又到了我们的取数游戏了,这个题跟上边的取数游戏2几乎一样,不过输入输出有些大,有我自己写的dp,和从别的地方偷的输入输出(狗头保命)
输入输出代码:
inline __int128_t read() { __int128_t x=0,f=1; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar(); return f*x; } void print(__int128_t x) { if(x < 0) {putchar('-');x = -x;} if(x/10) print(x/10); putchar(x%10+'0'); }
状态转移方程:dp[i][k][k+j]=max(dp[i][k+1][k+j]+a[i][k]*f[m-j],dp[i][k][k+j-1]+a[i][j+k]*f[m-j]);
AC代码:
#include<iostream> #include<algorithm> #include<cstdio> using namespace std; typedef __int128_t ll; ll f[100],dp[100][100][100],a[100][100]; inline __int128_t read() { __int128_t x=0,f=1; char c=getchar(); while(c<'0'||c>'9') {if(c=='-') f=-1;c=getchar();} while(c>='0'&&c<='9') x=(x<<1)+(x<<3)+c-'0',c=getchar(); return f*x; } void print(__int128_t x) { if(x < 0) {putchar('-');x = -x;} if(x/10) print(x/10); putchar(x%10+'0'); } void get(){ f[0]=1; for(int i=1;i<=90;i++){ f[i]=f[i-1]*2; } } int main(){ ll n,m,ans=0; n=read();m=read(); for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ a[i][j]=read(); } } get(); /*for(int i=1;i<=80;i++){ cout<<f[i]<<' '<<i<<endl; }*/ for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ dp[i][j][j]=a[i][j]*f[m]; } } for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ for(int k=1;k<=m;k++){ if(j+k<=m) dp[i][k][k+j]=max(dp[i][k+1][k+j]+a[i][k]*f[m-j],dp[i][k][k+j-1]+a[i][j+k]*f[m-j]); else break; } } ans+=dp[i][1][m]; } print(ans); }
采药
01背包裸的板子
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int dp[4000][3100],a[3100],w[3100]; int main(){ int t,m; cin>>t>>m; for(int i=1;i<=m;i++){ cin>>a[i]>>w[i]; } for(int i=1;i<=m;i++){ for(int j=0;j<=t;j++){ dp[i][j]=dp[i-1][j]; if(j-a[i]>=0) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+w[i]); } } /*for(int i=1;i<=m;i++){ for(int j=0;j<=t;j++){ cout<<dp[i][j]<<' '; } cout<<endl; }*/ cout<<dp[m][t]<<endl; }
过河
这道题难在离散化,而不是dp(一看数据显然需要离散化1e9),当然也有不好好读题的小朋友(包括我),求踩石子最少的数目(细节)。
它可以到达大于90(也可以71,洛谷上有以为位大佬的详细证明,当然我们也可以猜的大于某个数)的任意距离,所以一但距离大于这个数我们可以令它等于90。
离散化代码:
for(int i=1;i<=m;i++){ ll dis=a[i]-a[i-1]; if(dis>=90) dis=90; b[i]=b[i-1]+dis; vis[b[i]]=1; }
当然如果s==t,直接进行判读,代码:
if(s==t){ ll cnt=0; for(int i=1;i<=m;i++){ if(a[i]%s==0) cnt++; } cout<<cnt<<endl; return 0; }
之后就是距离外循环,步数内循环,dp了
也得防止跳过最后一个石子后可以直接跳出,代码:
for(ll i=b[m];i<=l;i++) ans=min(ans,dp[i]);
AC代码:
#include<iostream> #include<algorithm> #include<cstdio> #include<cstring> using namespace std; typedef long long ll; const int INF=0x3f3f3f3f; ll a[2100],vis[310000],dp[310000],b[2100]; int main(){ ll l,s,t,m; cin>>l>>s>>t>>m; for(int i=1;i<=m;i++) cin>>a[i]; sort(a+1,a+m+1); if(s==t){ ll cnt=0; for(int i=1;i<=m;i++){ if(a[i]%s==0) cnt++; } cout<<cnt<<endl; return 0; } for(int i=1;i<=m;i++){ ll dis=a[i]-a[i-1]; if(dis>=90) dis=90; b[i]=b[i-1]+dis; vis[b[i]]=1; } l=b[m]+90; memset(dp,INF,sizeof(dp)); dp[0]=0; for(ll i=1;i<=l;i++){ for(ll j=s;j<=t;j++){ if(i-j>=0){ //if(i==2) cout<<dp[i]<<endl; if(vis[i]) dp[i]=min(dp[i],dp[i-j]+1); else dp[i]=min(dp[i],dp[i-j]); } } } ll ans=INF; for(ll i=b[m];i<=l;i++) ans=min(ans,dp[i]); //for(ll i=1;i<=l;i++) cout<<dp[i]<<' '<<i<<endl; cout<<ans<<endl; }
合唱队形
曾经做到过的原题
求两个最长递增(递减)子序列,dp[i]表示到i的最长子序列,dp1[i]表示大于i的最长递减序列,总人数减去两者相加最大值即为结果。
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int dp[2100],dp1[2100],a[2100]; int main(){ int n; cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=n;i++){ dp[i]=1; } for(int i=1;i<=n;i++){ for(int j=1;j<i;j++){ if(a[i]>a[j]) dp[i]=max(dp[i],dp[j]+1); } } for(int i=n;i>=1;i--){ for(int j=n;j>i;j--){ if(a[i]>a[j]) dp1[i]=max(dp1[i],dp1[j]+1); } } int ans=0; //cout<<dp[4]<<' '<<dp[6]<<endl; for(int i=1;i<=n;i++){ ans=max(ans,dp[i]+dp1[i]); } cout<<n-ans<<endl; }
开心的金明
01背包的裸板子
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int dp[32][31000],a[32],w[32]; int main(){ int N,m; cin>>N>>m; for(int i=1;i<=m;i++) cin>>a[i]>>w[i]; for(int i=1;i<=m;i++){ for(int j=0;j<=N;j++){ dp[i][j]=dp[i-1][j]; if(j>=a[i]) dp[i][j]=max(dp[i][j],dp[i-1][j-a[i]]+a[i]*w[i]); } } cout<<dp[m][N]<<endl; }
金明的预算方案
有依赖的背包,注意每个主件只会有两个附件,所以总共会有五种情况:
1,不选 2,只选主件 3,选主件和附件A 4,选主件和附件B 5,选主件和附件A,B
主附件的绑定代码:
for(int i=1;i<=m;i++){ if(q[i]){ vis[q[i]][++b[q[i]]]=i; } }
vis[i][1]和vis[i][2]表示主件 i 附件的位置(如果存在的话)
之后是分组背包的感觉,代码:
if(j-v[i]>=0) dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]); if(t1&&j-v[i]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[t1]-v[i]]+v[t1]*p[t1]+v[i]*p[i]); if(t2&&j-v[i]-v[t2]>=0) dp[j]=max(dp[j],dp[j-v[t2]-v[i]]+v[t2]*p[t2]+v[i]*p[i]); if(t1&&t2&&j-v[i]-v[t2]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[i]-v[t1]-v[t2]]+v[i]*p[i]+v[t1]*p[t1]+v[t2]*p[t2]);
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; int vis[100][5],dp[36000],b[100],v[100],p[100],q[100]; int main(){ int N,m; cin>>N>>m; for(int i=1;i<=m;i++){ cin>>v[i]>>p[i]>>q[i]; } for(int i=1;i<=m;i++){ if(q[i]){ vis[q[i]][++b[q[i]]]=i; } } for(int i=1;i<=m;i++){ if(!q[i]){ int t1=vis[i][1],t2=vis[i][2]; for(int j=N;j>=0;j--){ if(j-v[i]>=0) dp[j]=max(dp[j],dp[j-v[i]]+v[i]*p[i]); if(t1&&j-v[i]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[t1]-v[i]]+v[t1]*p[t1]+v[i]*p[i]); if(t2&&j-v[i]-v[t2]>=0) dp[j]=max(dp[j],dp[j-v[t2]-v[i]]+v[t2]*p[t2]+v[i]*p[i]); if(t1&&t2&&j-v[i]-v[t2]-v[t1]>=0) dp[j]=max(dp[j],dp[j-v[i]-v[t1]-v[t2]]+v[i]*p[i]+v[t1]*p[t1]+v[t2]*p[t2]); } } } cout<<dp[N]<<endl; return 0; }
过河卒
dp入门题,需要注意障碍物
障碍物代码:
flag[x][y]=1; if(x>=1&&y>=2) flag[x-1][y-2]=1;if(x>=2&&y>=1) flag[x-2][y-1]=1; if(x>=1&&y+2<=m) flag[x-1][y+2]=1;if(x>=2&&y+1<=m) flag[x-2][y+1]=1; if(x+1<=n&&y+2<=m) flag[x+1][y+2]=1;if(x+2<=n&&y+1<=m) flag[x+2][y+1]=1; if(x+1<=n&&y>=2) flag[x+1][y-2]=1;if(x+2<=n&&y>=1) flag[x+2][y-1]=1;
遇到障碍物则方法数为零
AC代码:
#include<iostream> #include<cstdio> using namespace std; typedef long long ll; ll dp[31][31],flag[31][31]; int main(){ ll n,m,x,y; cin>>n>>m>>x>>y; dp[0][0]=1; flag[x][y]=1; if(x>=1&&y>=2) flag[x-1][y-2]=1;if(x>=2&&y>=1) flag[x-2][y-1]=1; if(x>=1&&y+2<=m) flag[x-1][y+2]=1;if(x>=2&&y+1<=m) flag[x-2][y+1]=1; if(x+1<=n&&y+2<=m) flag[x+1][y+2]=1;if(x+2<=n&&y+1<=m) flag[x+2][y+1]=1; if(x+1<=n&&y>=2) flag[x+1][y-2]=1;if(x+2<=n&&y>=1) flag[x+2][y-1]=1; for(int i=0;i<=n;i++){ for(int j=0;j<=m;j++){ if(flag[i][j]){ dp[i][j]=0; continue; } if(i>=1) dp[i][j]+=dp[i-1][j]; if(j>=1) dp[i][j]+=dp[i][j-1]; } } cout<<dp[n][m]<<endl; }
拦截导弹
以前做过的训练题
首先求一个最长递减子串,然后用一个储备数组来储备出现的系统接下来能接受的最大高度
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> using namespace std; const int INF=0x3f3f3f3f; int dp[310000],dp1[310000],a[310000]; int main(){ int x,n=0,cnt=1; while(scanf("%d",&x)!=EOF){ a[n++]=x; } dp[0]=a[0]; for(int i=0;i<=n;i++) dp1[i]=INF; for(int i=1;i<n;i++){ int t=0; for(int j=0;j<cnt;j++){ if(a[i]<dp[j]){ dp[j]=a[i]; t=1;break; } } if(!t){ dp[cnt++]=a[i]; } } for(int i=n-1;i>=0;i--){ *lower_bound(dp1,dp1+n,a[i])=a[i]; } cout<<lower_bound(dp1,dp1+n,INF)-dp1<<endl<<cnt<<endl; }
简单瞎搞题
bitset秀到我了,初始是会有几个1的存在,然后每次加数左移很所加数的位数(就是加几个零)然后每次丨运算如果两种方式所加出来的数相同,那么它肯定在所对应的相同的位数上是1。
最后数1,ans.count().
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<bitset> using namespace std; const int M=1e6+5; int main(){ int n; cin>>n; bitset<M>ans; ans[0]=1; while(n--){ int r,l; cin>>r>>l; bitset<M>temp; for(int i=r;i<=l;i++) temp|=ans<<i*i; ans=temp; } cout<<ans.count()<<endl; return 0; }
花店橱窗
把给出的数据看成一个图,起点为第一行的任意位置,终点为最后一行的任意位置(有一定的限制),只能向下同时向右走,找最大值。
然后就是我之前写过的的链表方法记录走到最大值的路径。
循环中记录每次记录路径的代码:
vis[(i-1)*v+j]=(i-2)*v+k
寻找真正的最大值路径代码:
int ans=dp[f][v],t=f*v; for(int i=1;i<v;i++){ if(ans<dp[f][i]){ ans=dp[f][i]; t=(f-1)*v+i; } }
因为是倒序的所以重新记录每个位置的代码:
while(vis[t]!=t){ c[cnt++]=(t%v==0?v:t%v); t=vis[t]; }
AC代码:
#include<iostream> #include<cstdio> #include<algorithm> #include<cstring> using namespace std; int vis[4100],a[310][310],dp[310][310],c[110]; int main(){ int f,v; cin>>f>>v; for(int i=1;i<=f;i++){ for(int j=1;j<=v;j++){ cin>>a[i][j]; } } memset(dp,-0x3f3f3f3f,sizeof(dp)); for(int i=1;i<=v;i++){ dp[1][i]=a[1][i]; vis[i]=i; } for(int i=2;i<=f;i++){ for(int j=1;j<=v;j++){ for(int k=1;k<j;k++){ if(dp[i][j]<dp[i-1][k]+a[i][j]){ dp[i][j]=dp[i-1][k]+a[i][j]; vis[(i-1)*v+j]=(i-2)*v+k; } } } } int ans=dp[f][v],t=f*v; for(int i=1;i<v;i++){ if(ans<dp[f][i]){ ans=dp[f][i]; t=(f-1)*v+i; } } cout<<ans<<endl; int cnt=1; while(vis[t]!=t){ c[cnt++]=(t%v==0?v:t%v); t=vis[t]; } cout<<t<<' '; for(int i=cnt-1;i>=1;i--) cout<<c[i]<<' '; cout<<endl; }
总结:区间dp的题目:取数游戏2,矩阵取数游戏,合并回文字符串,石子合并
01背包的灵活运用:CSL分苹果
有依赖的背包:金明的预算方案
多维dp:方格取数
直接递推:小A的彩票,传球游戏
有思维含量的dp数组设置:Rabbit的工作(1)
离散化:过河
拼接结论:拦截导弹,合唱队列
链表记录路径:花店橱窗
混入的dfs:滑雪