一本通动态规划篇解题报告
一本通动态规划篇解题报告
数位 dp
0. 过程模板
求解数位 dp 时,用
- 分解原数位
( 是最高位) - 加上所有长度长度小于
的, - 加上所有长度等于
,且最高位小于 的, - 加上所有前
位与原数相等,第 位小于原数的, ,如果到某一位的时候前面若干位组成的数已经不满足题目要求,则直接退出
这样做可以统计出区间
I. Amount of Degrees
思路分析
考虑拆成两个前缀和,然后答案转化为统计
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int k,b;
inline int C(int n,int m) {
int res=1;
for(int x=n;x>n-m;--x) res*=x;
for(int x=1;x<=m;++x) res/=x;
return res;
}
inline int calc(int x) {
int len=0,a[35],res=0;
while(x) {
a[++len]=x%b;
x/=b;
}
for(int i=len;i>=1;--i) {
if(a[i]>1) {
for(int j=i;j>=1;--j) {
a[j]=1;
}
break;
}
}
int t=k;
for(int i=len;i>=1;--i) {
if(!a[i]) continue;
res+=C(i-1,t);
--t; if(t<0) break;
}
return res;
}
signed main() {
int x,y;
scanf("%lld%lld%lld%lld",&x,&y,&k,&b);
printf("%lld\n",calc(y+1)-calc(x));
return 0;
}
II. 数字游戏
思路分析
拆前缀和然后套模板,预处理 dp 的状态转移方程如下:
代码呈现
#include<bits/stdc++.h>
#define int long long
#define f puts("comehere");
using namespace std;
int dp[11][10];
inline int calc(int x) {
int len=0,a[11],res=0;
while(x) {
a[++len]=x%10;
x/=10;
}
for(int i=1;i<len;++i) {
for(int j=1;j<=9;++j) {
res+=dp[i][j];
}
}
for(int i=1;i<a[len];++i) res+=dp[len][i];
for(int i=len-1;i>=1;--i) {
for(int j=a[i+1];j<a[i];++j) {
res+=dp[i][j];
}
if(a[i]<a[i+1]) break;
}
return res;
}
signed main() {
for(int i=0;i<=9;++i) dp[1][i]=1;
for(int i=2;i<=10;++i) {
for(int j=0;j<=9;++j) {
for(int k=j;k<=9;++k) {
dp[i][j]+=dp[i-1][k];
}
}
}
int a,b;
while(scanf("%lld%lld",&a,&b)!=EOF) printf("%lld\n",calc(b+1)-calc(a));
return 0;
}
III.Windy 数
思路分析
同样模板,状态转移方程如下:
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[20][20],a[20];
inline int calc(int x) {
int len=0,res=0;
while(x) a[++len]=x%10,x/=10;
for(int i=1;i<a[len];++i) res+=dp[len][i];
for(int l=1;l<len;++l) {
for(int i=1;i<=9;++i) res+=dp[l][i];
}
for(int l=len-1;l>0;--l) {
for(int i=0;i<a[l];++i) {
if(abs(i-a[l+1])>=2) res+=dp[l][i];
}
if(abs(a[l+1]-a[l])<2) break;
}
return res;
}
signed main() {
for(int i=0;i<=9;++i) dp[1][i]=1;
for(int i=2;i<=10;++i) {
for(int j=0;j<=9;++j) {
for(int k=0;k<=9;++k) {
if(abs(j-k)>=2) dp[i][j]+=dp[i-1][k];
}
}
}
int start,end;
scanf("%lld%lld",&start,&end);
printf("%lld\n",calc(end+1)-calc(start));
return 0;
}
IV. 数字游戏
思路分析
略有不同,设
转移时用刷表法更为方便,注意对于多组数据要分别处理
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[11][10][100],mod;
inline int calc(int x) {
int len=0,a[11],res=0;
while(x) {
a[++len]=x%10;
x/=10;
}
for(int i=1;i<len;++i) {
for(int j=1;j<=9;++j) {
res+=dp[i][j][0];
}
}
for(int i=1;i<a[len];++i) res+=dp[len][i][0];
int sum=mod-a[len]%mod; sum=(sum+mod)%mod;
for(int i=len-1;i>=1;--i) {
for(int j=0;j<a[i];++j) {
res+=dp[i][j][sum];
}
sum+=mod-a[i]%mod;
sum=(sum+mod)%mod;
}
return res;
}
signed main() {
int a,b;
while(scanf("%lld%lld%lld",&a,&b,&mod)!=EOF) {
memset(dp,0,sizeof(dp));
for(int i=0;i<=9;++i) dp[1][i][i%mod]=1;
for(int i=2;i<=10;++i) {
for(int j=0;j<=9;++j) {
for(int k=0;k<=9;++k) {
for(int r=0;r<mod;++r) {
dp[i][j][(r+j)%mod]+=dp[i-1][k][r];
}
}
}
}
printf("%lld\n",calc(b+1)-calc(a));
}
return 0;
}
V. 不要 62
思路分析
模板 dp,考虑前后两位之间的关系就可以去除含
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[11][10];
inline int calc(int x) {
int len=0,a[11],res=0;
while(x) {
a[++len]=x%10;
x/=10;
}
for(int i=1;i<len;++i) {
for(int j=1;j<=9;++j) {
res+=dp[i][j];
}
}
for(int i=1;i<a[len];++i) res+=dp[len][i];
if(a[len]==4) return res;
for(int i=len-1;i>=1;--i) {
for(int j=0;j<a[i];++j) {
if(a[i+1]==6&&j==2) continue;
res+=dp[i][j];
}
if((a[i]==4)||(a[i+1]==6&&a[i]==2)) break;
}
return res;
}
signed main() {
for(int i=0;i<=9;++i) if(i!=4) dp[1][i]=1;
for(int i=2;i<=10;++i) {
for(int j=0;j<=9;++j) {
if(j==4) continue;
for(int k=0;k<=9;++k) {
if(j==6&&k==2) continue;
dp[i][j]+=dp[i-1][k];
}
}
}
int a,b;
while(true) {
scanf("%lld%lld",&a,&b);
if(a==0&&b==0) break;
printf("%lld\n",calc(b+1)-calc(a));
}
return 0;
}
VI. 恨 7 不成妻
思路分析
数位 dp?大模拟!(误)
考虑
转移平方和的时候记得用完全平方和展开,这里直接贴转移方程的 C++ 源码了(实在是太长了)
for(int i=0;i<=9;++i) {
if(i==7) continue;
dp[1][i][i%7][i%7][0]=1;
dp[1][i][i%7][i%7][1]=i;
dp[1][i][i%7][i%7][2]=i*i;
}
for(int i=2;i<=19;++i) {
for(int j=0;j<=9;++j) {
if(j==7) continue;
for(int k=0;k<=9;++k) {
for(int dig=0;dig<7;++dig) {
for(int sum=0;sum<7;++sum) {
int con=j*pow10(i-1),t=con%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][0]+=dp[i-1][k][dig][sum][0];
dp[i][j][(dig+j)%7][(sum+con%7)%7][0]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][0]*t%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][1];
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*t%MOD*dp[i-1][k][dig][sum][0]%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*dp[i-1][k][dig][sum][1]%MOD*2%MOD; dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=dp[i-1][k][dig][sum][2];
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
}
}
}
}
}
注意:一定一定一定要注意是否会溢出
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MOD=1e9+7;
int dp[21][10][7][7][3];
//length start_digit digit_sum%7 number%7 answer_power
inline int pow10(int x) {
int res=1;
for(int i=1;i<=x;++i) res=res*10;
return res;
}
inline int calc(int x) {
int len=0,a[21],res=0;
while(x) {
a[++len]=x%10;
x/=10;
}
for(int i=1;i<len;++i) {
for(int j=1;j<=9;++j) {
for(int dig=1;dig<7;++dig) {
for(int sum=1;sum<7;++sum) {
res=(res+dp[i][j][dig][sum][2])%MOD;
}
}
}
}
for(int i=1;i<a[len];++i) {
for(int dig=1;dig<7;++dig) {
for(int sum=1;sum<7;++sum) {
res=(res+dp[len][i][dig][sum][2])%MOD;
}
}
}
int got_dig=a[len]%7,con=a[len]*pow10(len-1)%MOD,got_sum=a[len]*pow10(len-1)%7;
if(a[len]==7) return res;
for(int i=len-1;i>=1;--i) {
for(int j=0;j<a[i];++j) {
for(int dig=0;dig<7;++dig) {
if((dig+got_dig)%7==0) continue;
for(int sum=0;sum<7;++sum) {
if((sum+got_sum)%7==0) continue;
res+=dp[i][j][dig][sum][0]*con%MOD*con%MOD; res%=MOD;
res+=2*con%MOD*dp[i][j][dig][sum][1]%MOD; res%=MOD;
res+=dp[i][j][dig][sum][2]; res%=MOD;
}
}
}
if(a[i]==7) break;
got_dig=(got_dig+a[i])%7;
con=(con+a[i]*pow10(i-1))%MOD;
got_sum=(got_sum+a[i]*pow10(i-1))%7;
}
return res;
}
signed main() {
for(int i=0;i<=9;++i) {
if(i==7) continue;
dp[1][i][i%7][i%7][0]=1;
dp[1][i][i%7][i%7][1]=i;
dp[1][i][i%7][i%7][2]=i*i;
}
for(int i=2;i<=19;++i) {
for(int j=0;j<=9;++j) {
if(j==7) continue;
for(int k=0;k<=9;++k) {
for(int dig=0;dig<7;++dig) {
for(int sum=0;sum<7;++sum) {
int con=j*pow10(i-1),t=con%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][0]+=dp[i-1][k][dig][sum][0];
dp[i][j][(dig+j)%7][(sum+con%7)%7][0]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][0]*t%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]+=dp[i-1][k][dig][sum][1];
dp[i][j][(dig+j)%7][(sum+con%7)%7][1]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*t%MOD*dp[i-1][k][dig][sum][0]%MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=t*dp[i-1][k][dig][sum][1]%MOD*2%MOD; dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]+=dp[i-1][k][dig][sum][2];
dp[i][j][(dig+j)%7][(sum+con%7)%7][2]%=MOD;
}
}
}
}
}
int T;
scanf("%lld",&T);
while(T--) {
int a,b; scanf("%lld%lld",&a,&b);
printf("%lld\n",((calc(b+1)%MOD-calc(a)%MOD)%MOD+MOD)%MOD);
}
return 0;
}
VII. 数字计数
思路分析
经典数位 dp,设
注意计数的时候要考虑数字第
代码呈现
#include<bits/stdc++.h>
#define int __int128
using namespace std;
inline int read() {
int x=0;char c=getchar();
while(!isdigit(c)) c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+c-'0',c=getchar();
return x;
}
inline void write(int x) {
if(x>=10) write(x/10);
putchar(x%10+'0');
return ;
}
int dp[15][10][10];
/*
dp[i][j][k]:
i-digit j-end k-count which digit
*/
int a[15],pw[15];
inline int calc(int x,int digit) {
int res=0,len=0;
while(x) {
a[++len]=x%10,x/=10;
}
for(int i=1;i<a[len];++i) res+=dp[len][i][digit];
for(int l=1;l<len;++l) {
for(int i=1;i<=9;++i) res+=dp[l][i][digit];
}
for(int l=len-1;l>0;--l) {
for(int i=0;i<a[l];++i) res+=dp[l][i][digit];
for(int i=len;i>l;--i) if(a[i]==digit) res+=pw[l-1]*a[l];
}
return res;
}
signed main() {
for(int i=0,r=1;i<=12;++i,r*=10) pw[i]=r;
for(int i=0;i<=9;++i) dp[1][i][i]=1;
for(int l=2;l<=12;++l) {
for(int i=0;i<=9;++i) {
dp[l][i][i]+=pw[l-1];
for(int d=0;d<=9;++d) {
for(int j=0;j<=9;++j) {
//[ij...]
dp[l][i][d]+=dp[l-1][j][d];
}
}
}
}
int a=read(),b=read();
for(int i=0;i<=9;++i) {
write(calc(b+1,i)-calc(a,i));
putchar(' ');
}
puts("");return 0;
}
状压 dp
I. 国王
思路分析
如果某行的某个位置有国王则其状态的二进制下对应位为
设
设单行的可能放置方案的集合为
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=11;
int king[1<<MAXN],dp[MAXN][1<<MAXN][MAXN*MAXN],ans,n,l;
vector <int> choice;
signed main() {
scanf("%lld%lld",&n,&l);
for(int i=0;i<(1<<n);++i) {
if((i<<1)&i||(i>>1)&i) continue;
choice.push_back(i);
int j=i;
king[i]=__builtin_popcount(i);
}
for(int i:choice) if(king[i]<=l) ++dp[1][i][king[i]];
for(int i=2;i<=n;++i) {
for(int j:choice) {
for(int k:choice) {
if((k&j)||((k<<1)&j)||((k>>1)&j)) continue;
for(int h=1;h<=l;++h) {
if(h+king[j]>l) continue;
dp[i][j][h+king[j]]+=dp[i-1][k][h];
}
}
}
}
for(int i=1;i<=n;++i) {
for(int j:choice) {
ans+=dp[i][j][l];
}
}
printf("%lld",ans);
return 0;
}
II. 牧场的安排
思路分析
如果某行的某个位置被选取了则其状态的二进制下对应位为
设
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MOD=1e8;
int a[15],state[151],dp[151][15];
signed main() {
int n,m,tot=0;
scanf("%d%d",&n,&m);
for(int s=0;s<(1<<m);++s) {
if((s<<1)&s||(s>>1)&s) continue;
state[++tot]=s;
}
for(int i=1;i<=n;++i) {
for(int j=0;j<m;++j) {
int x;
scanf("%d",&x);
a[i]=(a[i]<<1)+(!x);
}
}
for(int i=1;i<=tot;++i) {
if(state[i]&a[1]) continue;
dp[i][1]=1;
}
for(int r=2;r<=n;++r) {
for(int i=1;i<=tot;++i) {
if(a[r-1]&state[i]) continue;
for(int j=1;j<=tot;++j) {
if(a[r]&state[j]) continue;
if(state[i]&state[j]) continue;
dp[j][r]=(dp[j][r]+dp[i][r-1])%MOD;
}
}
}
int res=0;
for(int i=1;i<=tot;++i) res=(res+dp[i][n])%MOD;
printf("%d\n",res);
return 0;
}
III. 涂抹果酱
思路分析
三进制状态压缩,某行的某个位置的颜色(
设
计算答案的时候,将第
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e5+1,MAXS=243,MOD=1e6;
int dp[MAXN][243],pw[6]={1,3,9,27,81,243};
inline int calc(int s,int pos) {
return (s/pw[pos])%3;
}
signed main() {
int n,m,k,st=0;
scanf("%lld%lld%lld",&n,&m,&k);
vector <int> state;
for(int i=0;i<pw[m];++i) {
bool flg=true;
for(int j=1;j<m;++j) {
if(calc(i,j-1)==calc(i,j)) {
flg=false;
break;
}
}
if(flg) state.push_back(i);
}
for(int i=0;i<m;++i) {
int col; scanf("%lld",&col);
st=st*3+col-1;
}
if((*lower_bound(state.begin(),state.end(),st))!=st) return 0&puts("0");
dp[1][st]=1;
for(int i=2;i<=max(n-k+1,k);++i) {
for(int s:state) {
for(int l:state) {
for(int p=0;p<m;++p) if(calc(s,p)==calc(l,p)) goto invalid;
dp[i][s]=(dp[i][s]+dp[i-1][l])%MOD;
invalid:;
}
}
}
int res1=0,res2=0;
for(int s:state) res1=(res1+dp[k][s])%MOD,res2=(res2+dp[n-k+1][s])%MOD;
printf("%lld\n",res1*res2%MOD);
return 0;
}
IV. 炮兵阵地
思路分析
某行的某个位置如果有炮兵阵地,则该行状态的对应位置为
设
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int a[105],state[63],cost[63],dp[63][63][105];
char str[105];
inline int calc(int x) {
return __builtin_popcount(x);
}
signed main() {
int n,m,tot=0;
scanf("%lld%lld",&n,&m);
for(int s=0;s<(1<<m);++s) {
if((s<<2)&s||(s<<1)&s||(s>>1)&s||(s>>2)&s) continue;
state[++tot]=s;
cost[tot]=calc(s);
}
for(int i=1;i<=n;++i) {
scanf("%s",str);
for(register int j=0;j<m;++j) {
a[i]=(a[i]<<1)+(str[j]=='H'?1:0);
}
}
for(int i=1;i<=tot;++i) {
if(state[i]&a[1]) continue;
dp[0][i][1]=cost[i];
}
for(int i=1;i<=tot;++i) {
if(state[i]&a[1]) continue;
for(int j=1;j<=tot;++j) {
if(state[j]&a[2]) continue;
if(state[i]&state[j]) continue;
dp[i][j][2]=max(dp[i][j][2],dp[0][i][1]+cost[j]);
}
}
for(int r=3;r<=n;++r) {
for(int i=1;i<=tot;++i) {
if(state[i]&a[r-2]) continue;
for(int j=1;j<=tot;++j) {
if(state[j]&a[r-1]) continue;
if(state[i]&state[j]) continue;
for(int k=1;k<=tot;++k) {
if(state[k]&a[r]) continue;
if(state[k]&state[i]||state[k]&state[j]) continue;
dp[j][k][r]=max(dp[j][k][r],dp[i][j][r-1]+cost[k]);
}
}
}
}
int res=0;
for(int i=0;i<=tot;++i) {
for(int j=0;j<=tot;++j) {
res=max(res,dp[i][j][n]);
}
}
printf("%lld\n",res);
return 0;
}
V. 动物园
题目大意
令
转移时保证前四位不变的前提下枚举第
思路分析
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+1,INF=1e9;
int dp[MAXN][32],val[MAXN][32];
signed main() {
int n,m,res=0;
scanf("%d%d",&n,&m);
for(int t=1;t<=m;++t) {
int a,b,c,x=0,y=0;
scanf("%d%d%d",&a,&b,&c);
for(int i=1;i<=b;++i) {
int u;
scanf("%d",&u);
x+=1<<((u-a+n)%n);
}
for(int i=1;i<=c;++i) {
int u;
scanf("%d",&u);
y+=1<<((u-a+n)%n);
}
for(int s=0;s<32;++s) if(((~s)&x)||(s&y)) ++val[a][s];
}
for(int start=0;start<32;++start) {
for(int i=0;i<32;++i) dp[0][i]=i==start?0:-INF;
for(int i=1;i<=n;++i) {
for(int s=0;s<32;++s) {
dp[i][s]=max(dp[i-1][(s&15)<<1],dp[i-1][(s&15)<<1|1])+val[i][s];
}
}
res=max(res,dp[n][start]);
}
printf("%d\n",res);
return 0;
}
树形 dp
0. 约定
I. 二叉苹果树
思路分析
经典树上背包,枚举每个儿子保留多少条边,父亲保留多少条边
结合 01 背包的思想,设
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
int dp[101][101],val[101],n,q;
struct node {
int des,val;
};
vector <node> edge[101];
inline void dfs(int p,int f) {
if(edge[p].size()==1) return ;
for(node t:edge[p]) {
int v=t.des;
if(v==f) continue;
dfs(v,p);
for(int i=q;i>0;--i) {
for(int j=0;j<i;++j) {
dp[p][i]=max(dp[p][i],dp[v][j]+dp[p][i-j-1]+t.val);
}
}
}
}
signed main() {
scanf("%lld%lld",&n,&q);
for(int i=1;i<n;++i) {
int u,v,w;
scanf("%lld%lld%lld",&u,&v,&w);
edge[u].push_back((node){v,w});
edge[v].push_back((node){u,w});
}
dfs(1,0);
printf("%lld",dp[1][q]);
return 0;
}
II. 选课
思路分析
树形 dp,以
同样 01 背包,设
注意初始值所有节点的
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=105;
int w[MAXN],dp[MAXN][MAXN],m,n;
vector <int> edge[MAXN];
inline void dfs(int x) {
for(int i=1;i<=m;++i) dp[x][i]=w[x];
for(int i=0;i<edge[x].size();++i) {
int s=edge[x][i];
dfs(s);
for(int j=m;j>0;--j) {
for(int k=1;k<j;++k) {
dp[x][j]=max(dp[x][j],dp[x][j-k]+dp[s][k]);
}
}
}
return ;
}
int main() {
scanf("%d%d",&n,&m);
++m;
for(int i=1;i<=n;++i) {
int input;
scanf("%d%d",&input,&w[i]);
edge[input].push_back(i);
}
dfs(0);
printf("%d\n",dp[0][m]);
return 0;
}
III. 数字转换
思路分析
按题目要求将节点
设
求答案时枚举每一个节点为根时的最长链长度与次长链长度相加即可
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=5e4+1;
vector <int> edge[MAXN];
int dp[MAXN][2],ans=0;
bool vis[MAXN];
inline int calc(int x) {
int res=0;
for(int i=1;i*i<=x;++i) {
if(x%i) continue;
res+=i+(x/i);
if(i*i==x) res-=i;
}
return res-x;
}
inline void dfs(int p,int f) {
vis[p]=true;
vector <int> res;
for(int v:edge[p]) {
if(v==f) continue;
dfs(v,p);
res.push_back(dp[v][0]+1);
}
sort(res.begin(),res.end(),greater<int>());
if(res.size()>0) dp[p][0]=res[0];
if(res.size()>1) dp[p][1]=res[1];
ans=max(ans,dp[p][0]+dp[p][1]);
return ;
}
signed main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) {
int t=calc(i);
if(t>=i||t<=0) continue;
edge[t].push_back(i);
edge[i].push_back(t);
}
for(int i=1;i<=n;++i) if(!vis[i]) dfs(i,0);
printf("%d\n",ans);
return 0;
}
IV. 战略游戏
思路分析
经典的树上覆盖问题,考虑 dp,设
如果节点
最终答案为
代码呈现
#include<bits/stdc++.h>
using namespace std;
int dp[1505][2];
vector <int> edge[1505];
void dfs(int x) {
dp[x][1]=1;
dp[x][0]=0;
for(register int i=0;i<edge[x].size();++i) {
int s=edge[x][i];
dfs(s);
dp[x][0]+=dp[s][1];
dp[x][1]+=min(dp[s][0],dp[s][1]);
}
}
int main() {
int n;
scanf("%d",&n);
for(register int i=0;i<n;++i) {
int now,tot;
scanf("%d%d",&now,&tot);
for(register int j=1;j<=tot;++j) {
int v;
scanf("%d",&v);
edge[now].push_back(v);
}
}
dfs(0);
printf("%d",min(dp[0][0],dp[0][1]));
}
V. 皇宫看守
思路分析
遇上一题类似,不过这一题是覆盖相邻点,所以每个点
的父亲覆盖了 , 的儿子可能是自身覆盖或者被儿子覆盖 的儿子覆盖了 , 的儿子可能是被自身覆盖或者被儿子覆盖,注意转移时必须保证至少覆盖了一个儿子 本身被覆盖了, 的儿子三种覆盖情况都有可能
设
注:转移式
最终的答案是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1501,INF=INT_MAX;
int dp[MAXN][3],w[MAXN];
vector <int> edge[MAXN];
void dfs(int p,int f) {
int val=INF;
for(int v:edge[p]) {
if(v==f) continue;
dfs(v,p);
dp[p][0]+=min(dp[v][1],dp[v][2]);
dp[p][1]+=min(dp[v][1],dp[v][2]);
val=min(val,dp[v][2]-min(dp[v][1],dp[v][2]));
dp[p][2]+=min(dp[v][0],min(dp[v][1],dp[v][2]));
}
dp[p][1]+=val,dp[p][2]+=w[p];
return ;
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) {
int u,tot;
scanf("%d",&u);scanf("%d%d",&w[u],&tot);
for(int j=1;j<=tot;++j) {
int v;
scanf("%d",&v);
edge[u].push_back(v);
edge[v].push_back(u);
}
}
dfs(1,0);
printf("%d",min(dp[1][1],dp[1][2]));
}
VI. 加分二叉树
思路分析
伪装成树形 dp 的区间 dp(误)
不考虑树形 dp,考虑区间 dp 设
记得记录转移方式然后 dfs 输出先序遍历,时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
long long f[50][50],root[50][50],val[50];
void print(int start,int end) {
if(start<=end) {
printf("%d ",root[start][end]);
if(start<end) {
print(start,root[start][end]-1);
print(root[start][end]+1,end);
}
}
return ;
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&val[i]);
root[i][i]=i;
f[i][i]=val[i];
}
for(int l=1;l<=n;l++) {
for(int i=1;i<=n-l;i++) {
int j=l+i;
f[i][j]=f[i+1][j]+val[i];
root[i][j]=i;
for(int k=i+1;k<j;k++) {
if(f[i][j]<f[i][k-1]*f[k+1][j]+val[k]) {
root[i][j]=k;
f[i][j]=f[i][k-1]*f[k+1][j]+val[k];
}
}
if(f[i][j]<f[i][j-1]+val[j]) {
root[i][j]=j;
f[i][j]=f[i][j-1]+val[j];
}
}
}
printf("%d\n",f[1][n]);
print(1,n);
printf("\n");
return 0;
}
VII. 旅游规划
思路分析
求树的直径覆盖的点,首先 dp 求出每个点的最长链和树的直径,然后 dfs 输出所有可能的答案即可,时间复杂度
代码呈现
#include<bits/stdc++.h>
#define pii pair<int,int>
using namespace std;
const int MAXN=2e5+1;
int dp[MAXN][2],tr[MAXN][2],ans;
vector <int> edge[MAXN],res;
vector <pii> tmp[MAXN];
inline void dfs(int p,int f) {
for(int v:edge[p]) {
if(v==f) continue;
dfs(v,p);
tmp[p].push_back(make_pair(dp[v][0]+1,v));
}
sort(tmp[p].begin(),tmp[p].end(),greater<pii>());
if(tmp[p].size()>0) dp[p][0]=tmp[p][0].first;
if(tmp[p].size()>1) dp[p][1]=tmp[p][1].first;
ans=max(ans,dp[p][0]+dp[p][1]);
return ;
}
inline void go(int p) {
res.push_back(p);
if(tmp[p].empty()) return ;
go(tmp[p][0].second);
for(int i=1;i<tmp[p].size();++i) {
if(tmp[p][i].first!=tmp[p][0].first) break;
go(tmp[p][i].second);
}
}
inline void print(int p,int f) {
if(dp[p][0]+dp[p][1]==ans) {
res.push_back(p);
if(tmp[p].size()>0) {
go(tmp[p][0].second);
for(int i=1;i<tmp[p].size();++i) {
if(tmp[p][i].first!=tmp[p][0].first) break;
go(tmp[p][i].second);
}
}
if(tmp[p].size()>1) {
go(tmp[p][1].second);
for(int i=2;i<tmp[p].size();++i) {
if(tmp[p][i].first!=tmp[p][1].first) break;
go(tmp[p][i].second);
}
}
}
for(int v:edge[p]) {
if(v==f) continue;
print(v,p);
}
}
signed main() {
memset(tr,-1,sizeof(tr));
int n;
scanf("%d",&n);
for(int i=1;i<n;++i) {
int u,v;
scanf("%d%d",&u,&v);
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(0,-1);
print(0,-1);
sort(res.begin(),res.end());
res.erase(unique(res.begin(),res.end()),res.end());
for(int v:res) printf("%d\n",v);
return 0&puts("");
}
VIII. 周年纪念晚会
思路分析
没有上司的舞会,经典树形 dp 题,设
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
vector <int> v[6005];
int f[6005][2];
bool vis[6005],isRoot[6005];
void dp(int root) {
vis[root]=true;
for(int i=0;i<v[root].size();i++) {
if(!vis[v[root][i]]) {
dp(v[root][i]);
}
f[root][1]+=f[v[root][i]][0];
f[root][0]+=max(f[v[root][i]][0],f[v[root][i]][1]);
}
}
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%d",&f[i][1]);
}
for(int i=1;i<n;i++) {
int l,k;
scanf("%d%d",&l,&k);
isRoot[l]=true;
v[k].push_back(l);
}
for(int i=1;i<=n;i++) {
if(!isRoot[i]) {
dp(i);
printf("%d\n",max(f[i][0],f[i][1]));
break;
}
}
return 0;
}
XI. 叶子的染色
思路分析
根节点的位置并不影响答案,所以任选一个非叶节点进行 dp,设
注:
从 转移而来时,可以不用在 染色,因为 同样染色了,可以直接贡献 的子树,所以转移时 , 的转移同理 的边界条件是 ,因为 如果不染色,其他节点至少要有一个染色
最终答案是
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e4+1,INF=1e9;
int c[MAXN],dp[MAXN][3],n,m;
vector <int> edge[MAXN];
inline void dfs(int p,int f) {
if(p<=n) {
dp[p][c[p]]=dp[p][2]=1;
dp[p][c[p]^1]=INF;
} else {
dp[p][0]=dp[p][1]=1;
}
for(int v:edge[p]) {
if(v==f) continue;
dfs(v,p);
dp[p][0]+=min(dp[v][0]-1,min(dp[v][1],dp[v][2]));
dp[p][1]+=min(dp[v][1]-1,min(dp[v][0],dp[v][2]));
dp[p][2]+=min(dp[v][2],min(dp[v][0],dp[v][1]));
}
return ;
}
signed main() {
scanf("%d%d",&m,&n);
for(int i=1;i<=n;++i) scanf("%d",&c[i]);
for(int i=1;i<m;++i) {
int u,v;
scanf("%d%d",&u,&v);
edge[u].push_back(v);
edge[v].push_back(u);
}
dfs(n+1,0);
printf("%d\n",min(dp[n+1][2],min(dp[n+1][0],dp[n+1][1])));
return 0;
}
X. 骑士
思路分析
基环树上 dp,如果将每个骑士与仇恨的人连一条边,则每条边上至多选一个人,转化成普通 dp 即可,套用周年纪念晚会的模板即可
注意:
- 本题的图是基环树森林
- 基环树上 dp 需要先找到环,然后将环上任意两个节点断开进行 dp
- 如果将每个骑士单向连接至他的仇人,则基环树是一颗内向基环树,在内向基环树上找环更方便,不用构造无向边
- 如果将每个骑士单向连接至仇恨他的人,则基环树是一颗外向基环树,在外向基环树上 dp 更加方便
- dp 的时候如果通过环上的边连回树根时,不能从树根状态转移
- 为了方便起见,我们在 dp 的时候不选树根,防止环上的其他节点与树根互相仇恨
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int MAXN=1e6+1;
vector <int> edge[MAXN];
int f[MAXN],w[MAXN],dp[MAXN][2],root;
bool vis[MAXN];
inline void dfs(int p) {
vis[p]=true;
dp[p][0]=0,dp[p][1]=w[p];
for(int v:edge[p]) {
if(v==root) continue;
dfs(v);
dp[p][0]+=max(dp[v][0],dp[v][1]);
dp[p][1]+=dp[v][0];
}
return ;
}
signed main() {
int n,res=0; scanf("%lld",&n);
for(int i=1;i<=n;++i) {
int v;
scanf("%lld%lld",&w[i],&v);
edge[v].push_back(i);
f[i]=v;
}
for(int i=1;i<=n;++i) {
if(vis[i]) continue;
int x=i;
while(!vis[f[x]]) vis[x]=true,x=f[x];
int lst=0;
dfs(root=x); lst=dp[x][0];
dfs(root=f[x]);
res+=max(lst,dp[f[x]][0]);
}
printf("%lld\n",res);
return 0;
}
区间 dp
I. 石子合并
思路分析
首先拆环为链,然后再在链上考虑区间 dp,令
不难的出如下状态转移方程:
按区间长度从小到大枚举,对于每个区间枚举中间断点
注意:拆环为链之后,每个长度为
代码呈现
#include<bits/stdc++.h>
using namespace std;
int fMin[210][210],fMax[210][210],sum[210],a[210];
int main() {
int n;
cin>>n;
for(int i=1;i<=n;i++) {
cin>>a[i];
a[i+n]=a[i];
}
for(int i=1;i<=n*2;i++) {
sum[i]=sum[i-1]+a[i];
}
for(int l=1;l<n;l++) {
for(int i=1;i<2*n-l;i++) {
int j=i+l;
fMin[i][j]=fMin[i][i]+fMin[i+1][j]+sum[j]-sum[i-1];
for(int k=i;k<j;k++) {
fMax[i][j]=max(fMax[i][k]+fMax[k+1][j]+sum[j]-sum[i-1],fMax[i][j]);
fMin[i][j]=min(fMin[i][k]+fMin[k+1][j]+sum[j]-sum[i-1],fMin[i][j]);
}
}
}
int ansMax=0,ansMin=fMin[1][n];
for(int i=1;i<=n;i++) {
ansMax=max(ansMax,fMax[i][i+n-1]);
ansMin=min(ansMin,fMin[i][i+n-1]);
}
cout<<ansMin<<endl<<ansMax<<endl;
return 0;
}
II. 能量项链
思路分析
和上一题一样的区间 dp 模板题,直接拆环为链,令
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=3e2+1;
int dp[MAXN][MAXN],s[MAXN],ans;
int main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) {
scanf("%d",&s[i]);
s[i+n]=s[i];
}
int m=n<<1;
for(int l=1;l<m;++l) {
for(int i=1;i+l<=m;++i) {
int j=i+l;
for(int k=i;k<j;++k) {
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]+s[i]*s[j+1]*s[k+1]);
}
}
}
for(int i=1;i<=n;++i) {
ans=max(ans,dp[i][i+n-1]);
}
printf("%d\n",ans);
return 0;
}
III. 凸多边形的划分
思路分析
同样环上区间 dp,但是不用拆环为链
注意到如果将凸多边形的第
所以可以令
记得开 __int128
或者高精,时间复杂度
代码呈现
#include<bits/stdc++.h>
#define ll __int128
const ll INF=1e30;
inline ll read() {
ll x=0;char ch=getchar();
while(!isdigit(ch)) ch=getchar();
while(isdigit(ch)) {
x=x*10+(ch-'0');
ch=getchar();
}
return x;
}
inline void write(ll x) {
if(x>=10) write(x/10);
putchar((char)(x%10+'0'));
return ;
}
ll a[55],dp[55][55];
signed main() {
int n;
scanf("%d",&n);
for(int i=1;i<=n;++i) a[i]=read();
for(int len=2;len<n;++len) {
for(int l=1,r=l+len;r<=n;++l,++r) {
dp[l][r]=INF;
for(int i=l+1;i<=r-1;++i) {
dp[l][r]=std::min(dp[l][r],dp[l][i]+dp[i][r]+a[i]*a[l]*a[r]);
}
}
}
write(dp[1][n]);
return 0;
}
IV. 括号配对
思路分析
很像 CSP-S2021 T2 的题,同样考虑区间 dp,设
类似 CSP-S2021 T2,每次转移的时候同样考虑需不需要将两边的括号通过添加来配对,得到如下方案:
综合两种转移,时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
char s[105];
int dp[105][105];
signed main() {
int n;
scanf("%s",s+1);
n=strlen(s+1);
memset(dp,0x3f,sizeof(dp));
for(int i=1;i<=n;++i) dp[i][i]=1;
for(int i=1;i<n;++i) dp[i+1][i]=0;
for(int len=1;len<n;++len) {
for(int l=1,r=l+len;r<=n;++l,++r) {
if(s[l]=='('&&s[r]==')') dp[l][r]=min(dp[l][r],dp[l+1][r-1]);
else if(s[l]=='(') dp[l][r]=min(dp[l][r],dp[l+1][r]+1);
else if(s[r]==')') dp[l][r]=min(dp[l][r],dp[l][r-1]+1);
if(s[l]=='['&&s[r]==']') dp[l][r]=min(dp[l][r],dp[l+1][r-1]);
else if(s[l]=='[') dp[l][r]=min(dp[l][r],dp[l+1][r]+1);
else if(s[r]==']') dp[l][r]=min(dp[l][r],dp[l][r-1]+1);
for(int k=l;k<r;++k) dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]);
}
}
printf("%d\n",dp[1][n]);
return 0;
}
V. 分离与合体
思路分析
考验阅读理解的区间 dp 题(误)
同样设
记得纪录转移方案,输出方案的时候请使用 BFS,时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
#define pii pair<int,int>
using namespace std;
int a[301],dp[301][301],f[301][301];
signed main() {
int n;
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
memset(dp,-0x3f,sizeof(dp));
for(int i=1;i<=n;++i) dp[i][i]=0;
for(int len=1;len<n;++len) {
for(int l=1,r=l+len;r<=n;++l,++r) {
for(int k=l;k<r;++k) {
int w=dp[l][k]+dp[k+1][r]+(a[l]+a[r])*a[k];
if(w>dp[l][r]) dp[l][r]=w,f[l][r]=k;
}
}
}
printf("%lld\n",dp[1][n]);
queue <pii> q;
q.push(make_pair(1,n));
while(!q.empty()) {
pii p=q.front();q.pop();
if(p.first==p.second) continue;
int tr=f[p.first][p.second];
printf("%lld ",tr);
q.push(make_pair(p.first,tr));
q.push(make_pair(tr+1,p.second));
}
return 0&putchar('\n');
}
VI. 矩阵取数游戏
思路分析
经典区间 dp,对于每行分开考虑,设
本题对
另注:本题需要 __int128
或高精
代码呈现
#include<bits/stdc++.h>
using namespace std;
__int128 ans=0,f[100][100],mp[100][100];
__int128 read() {
__int128 x=0;
int f=1;
char input=getchar();
while(!isdigit(input)) {
if(input=='-') {
f=-1;
}
input=getchar();
}
while(isdigit(input)) {
x=x*10+input-'0';
input=getchar();
}
return x*f;
}
void write(__int128 x) {
if(x>10) {
write(x/10);
}
putchar(x%10+'0');
return ;
}
int main() {
int n,m;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
for(int j=1;j<=m;j++) {
mp[i][j]=read();
}
}
for(int row=1;row<=n;row++) {
for(int i=1;i<=m;++i) f[i][i]=2*mp[row][i];
for(int l=1;l<=m;l++) {
for(int i=1;i+l<=m;i++) {
int j=i+l;
f[i][j]=max(2*f[i+1][j]+2*mp[row][i],2*f[i][j-1]+2*mp[row][j]);
}
}
ans+=f[1][m];
}
write(ans);
return 0;
}
单调队列优化 dp
I. 滑动窗口
思路分析
单调队列模板题,在维护的队列中元素满足单调性的前提下,记得从队首按时序要求删除不符合要求的答案
时间复杂度
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=1e6+1;
struct node {
int data,rank;
};
deque <node> q[2];
int ans[MAXN][2];
int main()
{
int n,m,input,turn=0;
scanf("%d%d",&n,&m);
for(int i=1;i<=n;i++) {
scanf("%d",&input);
while(!q[0].empty()&&q[0].back().data<=input) q[0].pop_back();
q[0].push_back((node){input,i});
while(!q[1].empty()&&q[1].back().data>=input) q[1].pop_back();
q[1].push_back((node){input,i});
if(i>m)
{
++turn;
while(q[0].front().rank<=turn) q[0].pop_front();
ans[turn][0]=q[0].front().data;
while(q[1].front().rank<=turn) q[1].pop_front();
ans[turn][1]=q[1].front().data;
}
else if(i==m) {
ans[0][0]=q[0].front().data;
ans[0][1]=q[1].front().data;
}
}
for(int i=0;i<=turn;i++) printf("%d ",ans[i][1]);
puts("");
for(int i=0;i<=turn;i++) printf("%d ",ans[i][0]);
puts("");
return 0;
}
II. 最大连续和
思路分析
设
不难看出状态转移时只需要对于权值
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+1,INF=1e18;
int s[MAXN],q[MAXN];
signed main() {
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;++i) scanf("%lld",&s[i]),s[i]+=s[i-1];
int head=1,tail=1,res=-INF;
for(int i=1;i<=n;++i) {
while(head<=tail&&i-q[head]>m) ++head;
if(head<=tail) res=max(res,s[i]-s[q[head]]);
while(head<=tail&&s[q[tail]]>s[i]) --tail;
q[++tail]=i;
}
printf("%lld\n",res);
return 0;
}
III. 修剪草坪
思路分析
设
对于
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=1e5+1;
int s[MAXN],q[MAXN],dp[MAXN][2];
signed main() {
int n,k;
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;++i) scanf("%lld",&s[i]),s[i]+=s[i-1];
int head=0,tail=0;
for(int i=1;i<=n;++i) {
dp[i][0]=max(dp[i-1][1],dp[i-1][0]);
while(head<=tail&&q[head]<i-k) ++head;
dp[i][1]=dp[q[head]][0]+s[i]-s[q[head]];
while(head<=tail&&dp[q[tail]][0]+s[i]-s[q[tail]]<dp[i][0]) --tail;
q[++tail]=i;
}
printf("%lld",max(dp[n][0],dp[n][1]));
return 0;
}
IV. 旅行问题
思路分析
顺时针逆时针相似,这里以顺时针为例,环上问题,首先考虑拆环为链,然后预处理出从
注意解决逆时针时点
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e6+1;
int s[MAXN],q[MAXN],p[MAXN],d[MAXN],n;
bool ok[MAXN];
signed main() {
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld%lld",&p[i],&d[i]);
for(int i=1;i<=n;++i) s[i]=s[i-1]+p[i]-d[i];
for(int i=1;i<=n;++i) s[i+n]=s[i+n-1]+p[i]-d[i];
int head=1,tail=0;
for(int i=1;i<=n;++i) {
while(head<=tail&&s[q[tail]]>s[i]) --tail;
q[++tail]=i;
}
for(int i=1;i<=n;++i) {
while(head<=tail&&q[head]<i) ++head;
if(s[q[head]]>=s[i-1]) ok[i]=true;
while(head<=tail&&s[q[tail]]>s[i+n]) --tail;
q[++tail]=i+n;
}
int tmp=d[n];
for(int i=n;i>1;--i) d[i]=d[i-1];
d[1]=tmp;
reverse(p+1,p+n+1);reverse(d+1,d+n+1);
for(int i=1;i<=n;++i) s[i]=s[i-1]+p[i]-d[i];
for(int i=1;i<=n;++i) s[i+n]=s[i+n-1]+p[i]-d[i];
head=1,tail=0;
for(int i=1;i<=n;++i) {
while(head<=tail&&s[q[tail]]>s[i]) --tail;
q[++tail]=i;
}
for(int i=1;i<=n;++i) {
while(head<=tail&&q[head]<i) ++head;
if(s[q[head]]>=s[i-1]) ok[n-i+1]=true;
while(head<=tail&&s[q[tail]]>s[i+n]) --tail;
q[++tail]=i+n;
}
for(int i=1;i<=n;++i) {
if(ok[i]) puts("TAK");
else puts("NIE");
}
return 0;
}
V. Banknotes
思路分析
简单的多重背包模板,单调队列或二进制优化维护都可以,这里选择的是二进制优化
将一件价值为
设
通过滚动数组优化掉第一维即可
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e3+1,MAXK=2e4+1;
int w[MAXN],v[MAXN],dp[MAXK];
int b[MAXN],c[MAXN];
signed main() {
memset(dp,0x3f,sizeof(dp));
int n,cnt=0,k;
scanf("%lld",&n);
for(int i=1;i<=n;++i) scanf("%lld",&b[i]);
for(int i=1;i<=n;++i) scanf("%lld",&c[i]);
scanf("%lld",&k);
for(int i=1;i<=n;++i) {
for(int t=1;t<=c[i];t=(t<<1)) {
w[++cnt]=b[i]*t;
v[cnt]=t;
c[i]-=t;
}
if(c[i]) {
w[++cnt]=b[i]*c[i];
v[cnt]=c[i];
}
}
dp[0]=0;
for(int i=1;i<=cnt;++i) {
for(int j=k;j>=w[i];--j) {
dp[j]=min(dp[j],dp[j-w[i]]+v[i]);
}
}
printf("%lld\n",dp[k]);
return 0;
}
VI. 烽火传递
思路分析
类似第三题,设
状态转移时以
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=2e5+1;
int a[MAXN],dp[MAXN][2],q[MAXN];
signed main() {
memset(dp,0x3f,sizeof(dp));
int n,m;
scanf("%lld%lld",&n,&m);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
dp[0][0]=dp[0][1]=0;
int head=1,tail=1;
for(int i=1;i<=n;++i) {
dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];
while(head<=tail&&q[head]<=i-m) ++head;
dp[i][0]=dp[q[head]][1];
while(head<=tail&&dp[q[tail]][1]>dp[i][1]) --tail;
q[++tail]=i;
}
printf("%lld\n",min(dp[n][0],dp[n][1]));
return 0;
}
VII. 绿色通道
思路分析
简单题,再上一题的基础 dp 上用二分枚举最长空串长度即可,用耗时检验是否可行
时间复杂度
代码呈现
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int MAXN=5e4+1;
int a[MAXN],dp[MAXN][2],q[MAXN],n,t;
inline bool check(int v) {
memset(dp,0x3f,sizeof(dp));
dp[0][0]=dp[0][1]=0;
int head=1,tail=1;
for(int i=1;i<=n;++i) {
dp[i][1]=min(dp[i-1][0],dp[i-1][1])+a[i];
while(head<=tail&&q[head]<i-v) ++head;
dp[i][0]=dp[q[head]][1];
while(head<=tail&&dp[q[tail]][1]>dp[i][1]) --tail;
q[++tail]=i;
}
return min(dp[n][0],dp[n][1])<=t;
}
signed main() {
scanf("%lld%lld",&n,&t);
for(int i=1;i<=n;++i) scanf("%lld",&a[i]);
int l=0,r=n,res=-1;
while(l<=r) {
int mid=(l+r)>>1;
if(check(mid)) res=mid,r=mid-1;
else l=mid+1;
}
printf("%lld\n",res);
return 0;
}
VII. 理想的正方形
思路分析
对于每一行维护一个长度为
然后枚举正方形单位右下角点,查询向上
时间复杂度
代码呈现
#include<bits/stdc++.h>
const int MAXN=1001,INF=1e9;
int min[MAXN][MAXN],max[MAXN][MAXN];
int a[MAXN][MAXN],q[MAXN];
signed main() {
int n,m,k,head,tail;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i) {
for(int j=1;j<=m;++j) {
scanf("%d",&a[i][j]);
}
}
for(int i=1;i<=n;++i) {
head=1,tail=0;
for(int j=1;j<=m;++j) {
while(head<=tail&&q[head]<=j-k) ++head;
min[i][j]=a[i][j];
if(head<=tail) min[i][j]=std::min(min[i][j],a[i][q[head]]);
while(head<=tail&&a[i][q[tail]]>a[i][j]) --tail;
q[++tail]=j;
}
}
for(int i=1;i<=n;++i) {
head=1,tail=0;
for(int j=1;j<=m;++j) {
while(head<=tail&&q[head]<=j-k) ++head;
max[i][j]=a[i][j];
if(head<=tail) max[i][j]=std::max(max[i][j],a[i][q[head]]);
while(head<=tail&&a[i][q[tail]]<a[i][j]) --tail;
q[++tail]=j;
}
}
int res=INT_MAX;
for(int i=k;i<=n;++i) {
for(int j=k;j<=m;++j) {
int maxv=-INF,minv=INF;
for(int t=i-k+1;t<=i;++t) {
maxv=std::max(maxv,max[t][j]);
minv=std::min(minv,min[t][j]);
}
res=std::min(res,maxv-minv);
}
}
printf("%d\n",res);
return 0;
}
IX. 股票交易
思路分析
类似背包(大概?)的一道 dp 题,思路较简单,细节处理和代码实现有一定难度
设
边界条件:
考虑按不同操作进行分类讨论状态转移
- 第
天什么都不做
此时的状态转移方程最简单,直接从前一天转移过来即可:
- 第
天首次买入股票
此时状态转移直接从
- 第
天买股票
由于两次买股票有时间限制,所以从第
不难发现状态转移时只需要对于权值
- 第
天卖股票
同上,可得状态转移方程:
同样维护一个长度为
代码呈现
#include<bits/stdc++.h>
using namespace std;
const int MAXN=2001;
int dp[MAXN][MAXN],q[MAXN];
signed main() {
memset(dp,-0x3f,sizeof(dp));
int n,m,k,head,tail;
scanf("%d%d%d",&n,&m,&k);
for(int i=1;i<=n;++i) {
int ap,bp,as,bs;
scanf("%d%d%d%d",&ap,&bp,&as,&bs);
for(int j=0;j<=as;++j) dp[i][j]=max(dp[i][j],-j*ap);
for(int j=0;j<=m;++j) dp[i][j]=max(dp[i][j],dp[i-1][j]);
if(i<=k) continue;
head=1,tail=0;
for(int j=0;j<=m;++j) {
while(head<=tail&&q[head]<j-as) ++head;
while(head<=tail&&dp[i-k-1][q[tail]]+q[tail]*ap<dp[i-k-1][j]+j*ap) --tail;
q[++tail]=j;
if(head<=tail) dp[i][j]=max(dp[i][j],dp[i-k-1][q[head]]+q[head]*ap-j*ap);
}
head=1,tail=0;
for(int j=m;j>=0;--j) {
while(head<=tail&&q[head]>j+bs) ++head;
while(head<=tail&&dp[i-k-1][q[tail]]+q[tail]*bp<dp[i-k-1][j]+j*bp) --tail;
q[++tail]=j;
if(head<=tail) dp[i][j]=max(dp[i][j],dp[i-k-1][q[head]]+q[head]*bp-j*bp);
}
}
int res=INT_MIN;
for(int j=0;j<=m;++j) res=max(res,dp[n][j]);
printf("%d\n",res);
return 0;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!