2021-10-09每周总结

142 篇文章 1 订阅
92 篇文章 0 订阅

        这周是做了点区间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;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

killer_queen4804

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值