这周是做了点区间dp和数位dp的相关题目,主要是数位dp,数位dp的题目大多都是利用深搜来进行,而dp数组的作用更像是保存状态,最终结果是由深搜得到,而且数位dp感觉每道题目的解决方式都像是在深搜上改动,代码整体思路还是差不多,但最近卡到了一个点上——前导0的判断,这点的判断方式我现在还没看懂,然而这一点又是这类题目中很重要的一点orz,看看以后能不能给他磨明白。。。
luogu p1220
dp[i][j][k]代表区间(i,j)灯全部关闭后的最小花费,k=0时代表老张是从左端点开始走的,k=1时代表是从右开始走的,对于dp[i][j][0],可以由i+1往左走一步得到,也可以是j往左走j-i步得到,对于dp[i][j][1],可以由j-1往右走一步得到,也可以是i往右走j-i步得到,能耗就是从i走到j,或从j走到i所花的秒数乘以区间以外没有关闭的灯的能耗,于是有方程
dp[i][j][0]=min(dp[i+1][j][0]+(p[i+1]-p[i])*(sum[n]-sum[j]+sum[i]),dp[i+1][j][1]+(p[j]-p[i])*(sum[n]-sum[j]+sum[i]));
dp[i][j][1]=min(dp[i][j-1][1]+(p[j]-p[j-1])*(sum[n]-sum[j-1]+sum[i-1]),dp[i][j-1][0]+(p[j]-p[i])*(sum[n]-sum[j-1]+sum[i-1]));
(28条消息) P1220 关路灯_冰柠橙的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,c,dp[55][55][2],sum[55],p[55];
int main(){
// freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
int w;
for(int i=1;i<=n;i++){
scanf("%d%d",&p[i],&w);
sum[i]=sum[i-1]+w;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j][0]=dp[i][j][1]=inf;
dp[m][m][0]=dp[m][m][1]=0;
for(int len=1;len<n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
dp[i][j][0]=min(dp[i+1][j][0]+(p[i+1]-p[i])*(sum[n]-sum[j]+sum[i]),dp[i+1][j][1]+(p[j]-p[i])*(sum[n]-sum[j]+sum[i]));
dp[i][j][1]=min(dp[i][j-1][1]+(p[j]-p[j-1])*(sum[n]-sum[j-1]+sum[i-1]),dp[i][j-1][0]+(p[j]-p[i])*(sum[n]-sum[j-1]+sum[i-1]));
//cout<<min(dp[i][j][0],dp[i][j][1])<<endl;
}
}
printf("%d\n",min(dp[1][n][0],dp[1][n][1]));
return 0;
}
luogu p4302
感觉这几个题做的都像是一个思路,都是开三维数组dp[i][j][k],k记录状态,k=1,表示i到j这个区间最后添加的人是j,k=0时,表示最后添加的人是i,然后分情况讨论
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int mod=19650827;
int dp[1005][1005][2],a[1005],n;
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++)
dp[i][i][0]=1;
for(int len=1;len<n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
if(a[i]<a[i+1]) dp[i][j][0]+=dp[i+1][j][0];
if(a[i]<a[j]) dp[i][j][0]+=dp[i+1][j][1];
if(a[j]>a[j-1]) dp[i][j][1]+=dp[i][j-1][1];
if(a[j]>a[i]) dp[i][j][1]+=dp[i][j-1][0];
dp[i][j][0]%=mod;
dp[i][j][1]%=mod;
}
}
printf("%d\n",(dp[1][n][0]+dp[1][n][1])%mod);
return 0;
}
luogu p4302
分割区间,判断dp[i][j]是否能合并成dp[i][k],如果能判断是dp[i][j]小还是合并后的带数字和括号的dp[i][k]小,即
if(pd(i,k,i,j))
dp[i][j]=min(dp[i][j],2+getlen((j-i+1)/(k-i+1))+dp[i][k]);
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
char s[110];
int dp[110][110];
bool pd(int l,int r,int L,int R){//判断是否能够合并
int len=r-l+1;
if((R-L+1)%len!=0) return false;
for(int i=L+len;i<=R;i++)
if(s[i]!=s[l+(i-l)%len])
return false;
return true;
}
int getlen(int n){
int a=0;
while(n>0){
a++;
n/=10;
}
return a;
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%s",s+1);
int n=strlen(s+1);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
dp[i][j]=inf;
for(int i=1;i<=n;i++) dp[i][i]=1;
for(int len=1;len<n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
dp[i][j]=j-i+1;//区间i到j原来的长度
for(int k=i;k<j;k++){
dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
if(pd(i,k,i,j))
dp[i][j]=min(dp[i][j],2+getlen((j-i+1)/(k-i+1))+dp[i][k]);//getlen是获取一共有多少个dp[i][k]合并
//cout<<dp[i][j]<<endl;
}
}
}
printf("%d\n",dp[1][n]);
return 0;
}
luogu p4342
区间dp,是个环,最经典的做法还是换成一个n+n的链,说实话,没想到区间dp可以把这个问题做的那么简单,做出来的人思维也太活跃了吧,不仅要判断最大,还要判断最小(负数的情况)
(30条消息) 洛谷 P4342 [IOI1998]Polygon 区间DP+断链成环_wineandchord-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,a[110],dp[111][111],f[111][111];
char c[110];
int main(){
//freopen("in.txt","r",stdin);
scanf("%d",&n);
for(int i=1;i<=n;i++){
cin>>c[i]>>a[i];
a[i+n]=a[i];
c[i+n]=c[i];
}
memset(dp,-inf,sizeof dp);
memset(f,inf,sizeof f);
for(int i=1;i<=n+n;i++) dp[i][i]=f[i][i]=a[i];
for(int len=1;len<n;len++){
for(int i=1;i<=n+n-len;i++){
int j=i+len;
for(int k=i;k<j;k++){
if(c[k+1]=='x'){
int a=dp[i][k]*dp[k+1][j],b=dp[i][k]*f[k+1][j];
int c=f[i][k]*dp[k+1][j],d=f[i][k]*f[k+1][j];
dp[i][j]=max(dp[i][j],max(a,max(b,max(c,d))));
f[i][j]=min(f[i][j],min(a,min(b,min(c,d))));
}
else if(c[k+1]=='t'){
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]);
}
}
}
}
int ans=0;
for(int i=1;i<=n;i++) ans=max(ans,dp[i][i+n-1]);//这样取值正好是一个环
printf("%d\n",ans);
for(int i=1;i<=n;i++)
if(dp[i][i+n-1]==ans)
printf("%d ",i);
return 0;
}
luogu p5851
dp[i][j]表示能吃到派的牛的最大体重和,如果说一个区间里只剩最后一个派k,那么自然是要体重最大的牛来吃,p[k][i][j]表示第k个派在区间[i,j]里被满足条件的最大体重的牛吃到
(30条消息) P5851 [USACO19DEC]Greedy Pie Eaters P(区间DP好题)_jziwjxjd的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
int n,m,dp[310][310],a,b,c,p[310][310][310];
int main(){
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
memset(dp,0,sizeof dp);
for(int i=1;i<=m;i++){
scanf("%d%d%d",&a,&b,&c);
for(int j=b;j<=c;j++)
p[j][b][c]=a;
}
for(int k=1;k<=n;k++)
for(int len=0;len<n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
int q=p[k][i+1][j],w=p[k][i][j-1];
p[k][i][j]=max(p[k][i][j],max(q,w));
}
}
for(int len=0;len<n;len++){
for(int i=1;i<=n-len;i++){
int j=i+len;
for(int k=i;k<j;k++)
dp[i][j]=max(dp[i][j],dp[i][k]+dp[k+1][j]);
for(int k=i;k<=j;k++){
int q=dp[i][k-1],w=dp[k+1][j];
dp[i][j]=max(dp[i][j],q+w+p[k][i][j]);
}
}
}
printf("%d\n",dp[1][n]);
return 0;
}
数位dp,luogu4999
看完区间来看数位dp,数位dp用来解决数据范围很大的一些关于处理数字的问题,这些题要是暴力做的话往往会超时,这时候就该把数字拆开一位一位的看,从而降低计算的复杂度,而且一些大数问题也是适合把数拆开一位一位的看,而数位dp就是解决这一类问题
luogu4999,题解可以看成是数位dp的模板了,让计算区间内的数字和,数据是10的18次方,记忆化搜索保存已经计算过的结果,从原数一直递归到0再回溯回来,之后再让两个数的结果相减取余
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll t,dp[20][200],n,m,digit[20];
const int mod=1e9+7;
ll dfs(int len,int sum,int ok){
if(!len) return sum;//长度为0,返回sum
if(!ok&&dp[len][sum]!=-1) return dp[len][sum];//已经计算过的结果直接拿来用即可
int maxx=ok?digit[len]:9;//判断最高位要取何值
ll res=0;
for(int i=0;i<=maxx;i++)//进行递归
res=(res+dfs(len-1,i+sum,ok&&(i==maxx)))%mod;
if(!ok) dp[len][sum]=res;//更新结果
return res;
}
ll f(ll n){//将数字拆开
int len=0;
while(n){
digit[++len]=n%10;
n/=10;
}
return dfs(len,0,1);
}
int main(){
//freopen("in.txt","r",stdin);
cin>>t;
memset(dp,-1,sizeof(dp));
while(t--){
scanf("%lld%lld",&n,&m);
printf("%lld\n",(f(m)-f(n-1)+mod)%mod);
}
return 0;
}
hdu2089
排除特定数的模板
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
ll m,n,dp[25][2],digit[25];
ll dfs(ll len,ll cnt,bool ok){
if(!len) return 1;
if(!ok&&dp[len][cnt]!=-1) return dp[len][cnt];
ll maxx=ok?digit[len]:9,ans=0;
for(int i=0;i<=maxx;i++){
if(i==4||cnt&&i==2)
continue;
ans+=dfs(len-1,i==6,ok&&i==maxx);
}
if(!ok) dp[len][cnt]=ans;
return ans;
}
ll f(ll n){
ll len=0;
while(n){
digit[++len]=n%10;
n/=10;
}
return dfs(len,0,1);
}
int main(){
memset(dp,-1,sizeof(dp));
while(scanf("%lld%lld",&n,&m)){
if(n==0&&m==0) break;
printf("%lld\n",f(m)-f(n-1));
}
return 0;
}
luogu2602
输出a到b区间内0-9每个数出现的次数,想到的就是进行十次dp,分别输出,但在递归上不会写了orz,看的题解,上面的递归函数多了两个参数,一个是记录次数,另一个是判断前导0的情况(这我是真没想到),判断前导0的方法虽然大体意思懂了,但还是有点勉强,做多了题之后再回来理解理解
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll a,b,dp[20][20],digit[100];
ll dfs(ll len,ll sum,bool ok,bool lead,ll num){
if(!len) return sum;
if(!ok&&!lead&&dp[len][sum]!=-1) return dp[len][sum];
ll maxx=ok?digit[len]:9,ans=0;
for(ll i=0;i<=maxx;i++)
ans+=dfs(len-1,sum+((!lead||i)&&(i==num)),ok&&(i==digit[len]),lead&&(i==0),num);
if(!ok&&!lead) dp[len][sum]=ans;
return ans;
}
ll f(ll n,ll num){
ll len=0;
while(n){
digit[++len]=n%10;
n/=10;
}memset(dp,-1,sizeof(dp));
return dfs(len,0,1,1,num);
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%lld%lld",&a,&b);
for(ll i=0;i<10;i++){
printf("%lld ",f(b,i)-f(a-1,i));
}
cout<<endl;
return 0;
}
hdu 3555
这道题与hdu2089非常像,只不过一个是排除特定数另一个是计算特定数,所以思路就是总的个数减去排除特定数后的个数,相减之后就是特定数的个数;另一个思路是直接算出特定数的个数
HDU 3555 Bomb_forezxl的博客-CSDN博客
下面是直接算的代码
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
using namespace std;
const int inf=0x3f3f3f3f;
ll t,n,dp[25][2],digit[25],ten[20];
ll dfs(ll len,ll cnt,bool ok){
if(!len) return 0;
if(!ok&&dp[len][cnt]!=-1) return dp[len][cnt];
ll maxx=ok?digit[len]:9,ans=0;
for(ll i=0;i<=maxx;i++){
if(cnt&&i==9){
if(ok) ans+=n%ten[len-1]+1;//例如3496,ok=1,3496%10=6,但满足条件的有
else ans+=ten[len-1];//3490,3491,3492,3493,3494,3495,3496七个数,所以要加1
}
else ans+=dfs(len-1,i==4,ok&&i==maxx);
}
if(!ok) dp[len][cnt]=ans;
return ans;
}
ll f(ll n){
ll len=0;
while(n){
digit[++len]=n%10;
n/=10;
}
return dfs(len,0,1);
}
int main(){
scanf("%lld",&t);
ten[0]=1;
for(int i=1;i<=18;i++) ten[i]=ten[i-1]*10;
while(t--){
memset(dp,-1,sizeof(dp));
scanf("%lld",&n);
printf("%lld\n",f(n));
}
return 0;
}
hdu3652
找含有特定数且能被特定数整除的方法
(38条消息) hdu3652_WA_automation的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
ll a,dp[20][2][2][20],digit[100];
ll dfs(ll len,bool is1,bool have13,int mod,int ok){
if(!len) return (!mod&&have13)?1:0;
if(!ok&&dp[len][is1][have13][mod]!=-1) return dp[len][is1][have13][mod];
int maxx=ok?digit[len]:9;
ll ans=0;
for(int i=0;i<=maxx;i++)
ans+=dfs(len-1,i==1,have13||(is1&&i==3),(mod*10+i)%13,ok&&i==maxx);
if(!ok) dp[len][is1][have13][mod]=ans;
return ans;
}
ll f(ll n){
ll len=0;
while(n){
digit[++len]=n%10;
n/=10;
}memset(dp,-1,sizeof(dp));
return dfs(len,0,0,0,1);
}
int main(){
//freopen("in.txt","r",stdin);
memset(dp,-1,sizeof(dp));
while(scanf("%lld",&a)!=EOF){
printf("%lld\n",f(a)-f(1));
}
return 0;
}
luogu p2567
dfs有四个参数,len:长度,pre:前一个数的数值,lead:判断前导0,ok:判断这个位上的数字是否能取0-9.思路就是判断abs(i-pre)>=2和判断前导0,前导0的判断一直没搞明白,最后用手推了一下才稍微有点明白,需要注意的是0-9竟然都是windy数。。。
P2657 [SCOI2009]windy数_Mystletainn的博客-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
ll a,dp[20][20],digit[100];
ll dfs(ll len,int pre,int lead,int ok){
if(!len) return 1;
if(!ok&&!lead&&dp[len][pre]!=-1) return dp[len][pre];
int maxx=ok?digit[len]:9,ans=0;
for(int i=0;i<=maxx;i++){
if(abs(i-pre)<2&&!lead) continue;
ans+=dfs(len-1,i,lead&&(i==0),ok&&(i==maxx));
}//lead是判断前导0的,注意0-9都是符合条件的数
if(!ok&&!lead) dp[len][pre]=ans;
return ans;
}
ll f(ll n){
ll len=0;
while(n){
digit[++len]=n%10;
n/=10;
}
return dfs(len,0,1,1);
}
int main(){
//freopen("in.txt","r",stdin);
memset(dp,-1,sizeof(dp));
ll n,m;
scanf("%lld%lld",&n,&m);
printf("%lld\n",f(m)-f(n-1));
return 0;
}
luogu p6218
这道题是要处理二进制,看了题解之后还是搞不太懂,而且代码整体也与以前的数位不同,
dp[i]j]代表填到第i位时,有j个零的情况的个数,最后将符合条件的加起来就行,但还是好懵逼orz
【YbtOJ数位DP-2】【luogu P6218】Round Numbers S_blog-CSDN博客
#include<iostream>
#include<cstring>
#include<algorithm>
#include<string>
#include<cstdio>
#include<iomanip>
#include<map>
#include<cmath>
#include<vector>
#include<queue>
#include<deque>
#include<stack>
#include<set>
#include<ctime>
#define ll long long
#define CHECK(x,y) (x>0&&x<=n&&y>0&&y<=m)
//#include<bits/stdc++.h>
ll n,m,dp[33][33],digit[100],d0,d1,tot;
int f(int n){
int ans=0;
d1=d0=0;
tot=0;
memset(digit,0,sizeof(digit));
if(n==0) return 0;
while(n){
digit[++tot]=n&1;
n>>=1;
}
for(int i=tot;i>=1;i--){
if(digit[i]&&i!=tot){
d0++;
for(int j=0;j<i;j++)
if(j+d0>=tot-j-d0)
ans+=dp[i-1][j];
d0--;
}
if(i!=tot){
for(int j=0;j<i;j++)
if(j>=i-j)
ans+=dp[i-1][j];
}
d0+=!digit[i];
d1+=digit[i];
}
if(d0>=d1) ans++;
return ans;
}
int main(){
//freopen("in.txt","r",stdin);
scanf("%d%d",&n,&m);
dp[0][0]=1;
for(int i=1;i<=31;i++){
dp[i-1][0]=1;
for(int j=1;j<=i;j++)
dp[i][j]=dp[i-1][j-1]+dp[i-1][j];
}
printf("%d\n",f(m)-f(n-1));
return 0;
}