【dp】dp题积累1


时隔多日的又一道dp(状压dp)

Southern and Volga Russia Qualifier 2019-2020C. Marbles

pof说:
那个啊,因为他最后肯定都是一团一团的嘛,
然后a[i]最大只有20,
所以直接状压dp[s]
表示已经把二进制下为1的块按最优顺序放到数列最前面的最少花费

/////pofnb

#include<bits/stdc++.h>
#include<tr1/unordered_map>
#define debug printf("!");
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int inf=0x3f3f3f3f;
const int maxn=4e5+5;

/*
pof: 
那个啊,因为他最后肯定都是一团一团的嘛,
然后a[i]最大只有20,
所以直接状压dp[s]
表示已经把二进制下为1的块按最优顺序放到数列最前面的最少花费
*/
ll dp[2000004];
ll num[22]={0},fnum[22][22]={0},up,resta;
int ha[22]={0};
//num是记录当前的数的个数,方便计算dp1
//fnum[u][v]是记录如果 v这一团在u的前面 则把v这一团移到u前所用的步数 
//ha是把离散序列化为连续序列 

int main()
{
    int n,i,j,k,tot,a;
    memset(dp,inf,sizeof(dp));
    memset(ha,-1,sizeof(ha));
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%d",&a);
        if(ha[a]!=-1)a=ha[a];
        else 
        {
            ha[a]=up++;
            a=ha[a];
        }
        resta|=(1<<a);
        if(!num[a])
        {
            dp[1<<a]=0;
        }
        dp[1<<a]+=i-num[a]-1;
        num[a]++;
        for(j=0;j<20;j++)fnum[a][j]+=num[j];
    }
    dp[0]=0;
    int now[22];
    ll sum;
    for(i=1;i<(1<<up);i++)
    {
        for(j=0,tot=0;j<up;j++)
        {
            if(!(i&(1<<j)))continue;
            now[tot++]=j;
        }
        for(j=0;j<tot;j++)
        {
            sum=0;
            for(k=0;k<tot;k++)
            {
                if(k==j)continue;
                sum+=fnum[now[j]][now[k]];
            }
            dp[i]=min(dp[i],dp[i^(1<<now[j])]+dp[1<<now[j]]-sum);
        }
    }
    
    printf("%lld
",dp[(1<<up)-1]);
}

2019-09-25 19:23:00


每日一dp 又是dp

Southern and Volga Russia Qualifier 2019-2020H. Berland Prospect

求最长的等差数列

以为距离是longlong不能用dp但是忘记有map

#include<cstdio>
#include<algorithm>
#include<tr1/unordered_map>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=3e3+50;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;


tr1::unordered_map<int,ll>dp[maxn];
int main()
{
    int n,i,j,k,ans=1,num;
    ll a[maxn],dis;
    scanf("%d",&n);
    for(i=1;i<=n;i++)scanf("%lld",&a[i]);
    for(i=1;i<=n;i++)
    {
        for(j=i-1;j>0;j--)
        {
            dis=a[i]-a[j];
            k=dp[i][dis]=dp[j][dis]+1;
            ans=max(ans,k);
        }
    }
    printf("%d
",ans+1);
}

还差得远,继续练QAQ

2019-09-19 19:30:11


shanghai icpc F. Rhyme scheme

T个询问 输出长度为n的第k个Rhyme scheme;贝尔数

还是不太懂dp是什么

题解说这种做法是dp,那就是dp吧

/*

题解说:

• 如右图,Rhyme scheme可以形成一个字典树。(图为n=3)

• 可以用DP求出,dp[n][i][j]表示长度为n的Rhyme scheme, 在第i层, 前面出现的字母最大是j有多少个。

• 如果询问n,k的时候,只要在字典树上从上往下走即可。
• B(26)会超出long long, 可以用两个long long或者__int128搞一搞

*/

然后我看懂了dp[n][i][j]。然后题解代码的做法看不懂。然后我结合dp思路用了自己的做法:

首先:

设dp[u][i][j]表示:起始字母为u的树,在第i层的Rhythm scheme(下面简称Rs),最大字母为j 的数 有多少个

按题意,其始字母一定是A,设长度为n;

如果看子树,把子树看成一棵独立的树,以它为根,有新的Rs,长度为n-k,k为根的层数-1,而起始字母就不一定是A了。所以有了[u]这个单元,作用如下:

此时设sum[u][i],表示起始字母为u的数,在第i层(长度为i)的Rs的个数,则sum[u][i]=∑dp[u][i][j](1<=j<=i)。

dp的作用是为了求出sum,用sum数组来找到答案。

dp[u][][]对下一个u无影响。可以略去,便是dp[i][j]。

那么,题目的答案输出就可以是:

判断输出字母在当前层数之后的Rs的数目,如果大于k,就减去这颗树的大小,直到小于等于k,输出这个字母。然后再在下一层遍历一遍。

然而,这种做法会出错。

因为以u节点为根子树,并一定不等于以u开始建的Rs数。因为:

以u节点为根的子树,上面可能有比u节点大的字母,设为其上最大字母为up,此时,u节点的拓展不是[1,u+1],而是[1,up+1],

所以,对新建的子树做修改,拓展出up单元,表示整棵树的【最小*最大字母*】是up,然后在up的基础上,以建立u节点开头的树,就其到达每层的Rs的个数。

sum[up][u][i]。

结论:答案的结果是记录当前以输出的最大字母up,从1开始遍历字母c,判断k和sum[up][c][i]的关系,大于则减去,小于等于则跳出循环,输出。更新最大字母。

#include<bits/stdc++.h>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=1e5+50;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;

void read(__int128 &x)
{
    char ch;
    while(!isdigit(ch=getchar()));
    x=ch-'0';
    while(isdigit(ch=getchar()))
        x=x*10+ch-'0';
}


/*
dp[i][j] 第i层 前面出现的字母 最大是j 的 Rhyme scheme 有多少个?

dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1];

*/

__int128 dp[27][27],sum[27][27][27];
void init()
{
    int i,u;
    __int128 j,up,t;
    for(up=1;up<=26;up++)
    {
        memset(dp,0,sizeof(dp));
        for(u=1;u<=26;u++)
        {
            dp[1][up]=sum[up][u][1]=1;
            for(i=2;i<=26-u+1;i++)
            {
                sum[up][u][i]=0;
                for(j=up;j<=up+i-1&&j<=26;j++)
                {    
                    dp[i][j]=dp[i-1][j]*j+dp[i-1][j-1];
                    sum[up][u][i]=sum[up][u][i]+dp[i][j];
                }
            }
        }
    }
}

int main()
{
    init();
    int T,u;
    scanf("%d",&T);
    for(u=1;u<=T;u++)
    {
        int n,i,j,c,up;
        __int128 k;
        scanf("%d",&n);
        read(k);
        printf("Case #%d: ",u);
        up=1;
        for(i=1;i<=n;i++)
        {
            for(c=1;c<up&&k>sum[up-1][c][n-i+1];c++)k-=sum[up-1][c][n-i+1];
            putchar('A'+c-1);
            up=max(up,c+1);
        }
        putchar(10);
    }
}

2019-09-18 12:56:05


每日dp

洛谷P2034 选择数字

题意:给定一行n个非负整数a[1]..a[n]。现在你可以选择其中若干个数,但不能有超过k个连续的数字被选择。使得选出的数字的和最大。

原本做法是O(nk) 也就是O(n^2) 90分 超时了

后来写一个简单的线段树维护区间最小值 过了

然后题解更优的做法是单调队列维护区间最小值。  忘记简单维护区间的最小值单调队列更方便了。

#include<bits/stdc++.h>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=1e5+50;
const int mod=1e9+7;
const int inf=0x3f3f3f3f;

/*
不能超过k个数连续
[5 6 7  4] 9 [3  2  11 9] 2 [4 3 6
5 [6 7  4 9] 3  [2  11 9] 2 [4 3 6
[5 6 7] 4 [9  3] 2 [11 9] 2 [4 3 6 
[5 6 7 4]  9 [11 12 13 14] 4 [3 2 1 
*/

/*
dp[i]表示删除了第i个数

dp[i]=min(dp[i-j]+a[i],dp[i])//i-k-1<=j<i
O(n^2)
*/ 


const ll INF=LLONG_MAX>>1;



int main()
{
    int n,k,i,j;
    ll a[maxn],dp[maxn],sum=0,ans=0,que[maxn],fir,tail;
    scanf("%d%d",&n,&k);
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        sum+=a[i];
    }
    a[0]=0;fir=0,tail=-1;
    for(i=0;i<=n;i++)
    {
        if(i-k-1<=0)
        {
            dp[i]=a[i];
            while(fir<=tail&&dp[que[tail]]>=dp[i])tail--;
            que[++tail]=i;
        }
        else
        {
            while(fir<=tail&&que[fir]<i-k-1)fir++;
            dp[i]=dp[que[fir]]+a[i];
            while(fir<=tail&&dp[que[tail]]>=dp[i])tail--;
            que[++tail]=i;
        }
        if(i>=n-k)ans=max(ans,sum-dp[i]);
    }
    printf("%lld
",ans);
}

也是今天遇到的题比较简单,也也许是我进步一点点了。

2019-09-17 21:42:47


shanghai icpc J.Stone game 

退背包。

按题解说:

• 对所有石子从大到小排序,进行dp。
• 我们考虑取出的那一堆石子,f[i][j] 表示该堆石子里最小的石子为 i,总价值 为 j 的方案数,这个通过dp来算。
• 对于所有最小石子为 i 的方案,可以求出其左右边界,那么对应答案加上改 区间内的 f[i][l] ~ f[i][r]。其中 i 这一维可以略去。

所以当前的*最小的石子*为必取,比当前的*最小的石子*小的石子必不取

所以先退最小的石子,把此时退的石子当做必取,统计除去这个石子外满足价值总和为m的方案数:退背包。最初的更新数组就是背包数组。

退完上一颗石子就退当前石子,设当前石子为必取石子,比当前小的石子已退。所以用上一次的dp数组作为这一次的更新数组。

然后每一次(退完一颗石子==必取一颗石子),就按照当前石子的左右边界累加答案。

因为是从小到大退石子,所以需要排序。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int manx=1e5+50;
//f[i][j]=max(f[i][j],f[i][j-v]+1)
int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int n,i,j,a[330],sum=0;
        ll dp[150005]={0},ans=0;
        scanf("%d",&n);
        for(i=1;i<=n;i++)
        {
            scanf("%d",&a[i]);
            sum+=a[i];
        }
        sort(a+1,a+1+n);
        dp[0]=1;
        for(i=1;i<=n;i++)
            for(j=sum;j>=a[i];j--)
                dp[j]=(dp[j]+dp[j-a[i]])%mod;
        for(i=1;i<=n;i++)
        {
            for(j=a[i];j<=sum;j++)
                dp[j]=(dp[j]-dp[j-a[i]]+mod)%mod;
            for(j=max((sum+1)/2-a[i],0);j<=sum-j-a[i];j++)
                ans=(ans+dp[j])%mod;
        }
        printf("%lld
",ans);
    }
}

每日一dp  退背包

我才知道背包还能退的。

洛谷P4141消失之物

题意是 :有 N 个物品, 体积分别是 W1, W2, …, WN。 第 i 个物品丢失了。 要使用剩下的 N – 1 物品装满容积为 x 的背包,有几种方法呢?

!!!!!

背包:f[i][j] = f[i-1][j-w[i]] + f[i-1][j]

退背包:f[i-1][j] = f[i][j] - f[i-1][j-w[i]] (先求出背包的dp数组!!!!)

然后据说:转移的时候带系数的背包不一定可以退背包

////背包 
for(i=1;i<=n;i++)
    for(j=w[i];j<=m;j++)
        f[i][j]=f[i-1][j-w[i]]+f[i-1][j];
//优化:
for(i=1;i<=n;i++)
    for(j=m;j>=w[i];j--)
        f[j]=f[j]+f[j-w[i]]; 

////退背包
for(i=1;i<=n;i++)
    for(j=m;j>=w[i];j--)
        f[i-1][j]=f[i][j]-f[i-1][j-w[i]]; 
//优化
for(i=1;i<=n;i++)
    for(j=w[i];j<=m;j++)
        f[j]=f[j]-f[j-w[i]]; 
//完全背包退背包 (先dp出完全背包) 
for(i=m;i>=w[x];i--)dp[i]-=dp[i-w[x]]; 

C. Dawn-K's water

求最小花费使得重量达到m或以上

物品可以无限量取。

不会做dp还要现场做。搞得好紧张啊.......

#include<bits/stdc++.h>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=5e4+50;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
struct P{
    int p,w;
}b[maxn];
int main()
{
    int n,m,i,j,k,up=0,v,ans1,ans2;
    while(~scanf("%d%d",&n,&m))
    {
        for(i=1;i<=n;i++)
        {
            scanf("%d%d",&b[i].p,&b[i].w);
            up=max(up,b[i].w);
        }
        int dp[maxn];
        memset(dp,inf,sizeof(dp));
        dp[0]=0;
        for(i=0;i<=m+up;i++)
        {
            for(j=1;j<=n;j++)
            {
                if(i>=b[j].w)dp[i]=min(dp[i],dp[i-b[j].w]+b[j].p);
            }
        }
        ans1=inf,ans2=inf;
        for(i=m;i<=m+up;i++)
        {
            if(dp[i]<=ans1)
            {
                ans1=dp[i];
                ans2=i;
            }
        }
        //    printf("$%d
",m+up);
        printf("%d %d
",ans1,ans2);
    }
}

每日一dp

洛谷P1052

重点在题解的路径压缩。

题解说:

每次走p步或者 p+1p+1 步,p*(p+1)p(p+1) 之后的地方均能够到达。

如果两个石子之间的距离大于 p*(p+1)p(p+1) ,那么就可以直接将他们之间的距离更改为 p*(p+1)p(p+1

#include<bits/stdc++.h>
#define debug printf("!");
using namespace std;
typedef long long ll;
const int maxn=3e5+50;
const int inf=0x3f3f3f3f;
//dp[i+j]=min(dp[i+j],dp[i]+is[i+j])
int main()
{
    int L,s,t,m,i,j,k,ans;
    int is[20200]={0},dp[20200],a[110],dis;
    scanf("%d",&L);
    scanf("%d%d%d",&s,&t,&m);
    if(s==t)
    {
        ans=0;
        for(i=1;i<=m;i++)
        {
            scanf("%d",&k);
            if(k%s==0)ans++;
        }
        printf("%d
",ans);
        return 0;
    }
    for(i=1;i<=m;i++)
        scanf("%d",&a[i]);
    sort(a+1,a+1+m);
    k=0;a[0]=0;
    for(i=1;i<=m;i++)
    {
        dis=min(a[i]-a[i-1],90);
        k+=dis;
        is[k]=1;
    }
    dis=min(L-a[m],100);
    L=k+dis;
    dp[0]=0;
    for(i=s;i<=L+10;i++)
    {
        dp[i]=200;
        for(j=s;j<=t ;j++)
            if(i>=j)dp[i]=min(dp[i],dp[i-j]+is[i]);
    }
    ans=dp[L];
    for(i=L;i<=L+10;i++)ans=min(ans,dp[i]);
    printf("%d
",ans);
}

2018-2019 ACM-ICPC Brazil Subregional Programming Contest

F. Music Festival

我这才知道原来cf 1s 可以跑1e9

也许不止....  试了86400*2047...=1e9+...然后300+ms...

比赛的时候想到了dp 也想到了状压 也想到了一层时间一层状态...

然后 然后我以为要离散 发现处理好麻烦...  然后就去看L了.....

然后 然后不用离散啊...  然后大概不用离散我当时也未必就能做出来QAQ.. dp太弱了

#include<cstdio>
#include<cstring>
#include<vector>
#include<bitset>
#include<algorithm>
#define debug printf("!");
using namespace std;
typedef __int64 ll;//%I64d
struct P{
    int s,t,v,id;
    bool operator <(const P&p)const{return t<p.t;}
    P(int ss,int tt,int vv,int idd){s=ss;t=tt;v=vv;id=idd;}
};
int dp[86440][2100];
int main()
{
    int n,m,i,j,k,num,a,ans=0,s,t,v,tot,id,up=0,siz;
    vector<P> p;
    scanf("%d",&n);
    for(i=0;i<n;i++)
    {
        scanf("%d",&num);
        for(j=1;j<=num;j++)
        {
            scanf("%d%d%d",&s,&t,&v);
            p.push_back(P(s,t,v,i));
            up=max(up,t);
        }
    }
    sort(p.begin(),p.end());
    memset(dp,0,sizeof(dp));
    tot=0;siz=p.size();
    for(i=1;i<=up;i++)
    {
        if(tot==siz)break;
        for(j=1;j<(1<<n);j++)dp[i][j]=dp[i-1][j];
        if(i<p[tot].t)continue;
        while(p[tot].t==i)
        {
            dp[i][1<<p[tot].id]=max(dp[i][1<<p[tot].id],p[tot].v);
            for(j=1;j<(1<<n);j++)
            {
                if(!dp[p[tot].s][j])continue;
                dp[i][j|(1<<p[tot].id)]=max(dp[i][j|(1<<p[tot].id)],dp[p[tot].s][j]+p[tot].v);
            }
            tot++;
            if(tot==siz)break;
        }
    }
    if(!dp[up][(1<<n)-1])printf("-1
");
    else printf("%d
",dp[up][(1<<n)-1]);
}

2019-09-12 23:34:56


洛谷P3205

第一次正式接触区间dp,以前应该遇见过,但没这个意识。

“区间DP,顾名思义是在区间上DP,它的主要思想就是先在小区间进行DP得到最优解,然后再利用小区间的最优解合并求大区间的最优解。”

然后看了区间dp的套路,做出这道题了。

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int mod=1e9+7;
const int maxn=1e3+50;
ll dp[maxn][maxn][2];
//dp[i][j][0]  [i,j]以j为最后一个入队的人的种数 
//dp[i][j][1]  [i,j]以i为最后一个入队的人的种数 
int main()
{
    memset(dp,0,sizeof(dp));
    int n,i,j,len,p=19650827;
    ll a[maxn];
    scanf("%d",&n);
    for(i=1;i<=n;i++)
    {
        scanf("%lld",&a[i]);
        dp[i][i][0]=1;
    }
    for(len=2;len<=n;len++)
    {
        for(i=1;i+len-1<=n;i++)
        {
            j=i+len-1;
            if(len==2)
            {
                if(a[j-1]<a[j])dp[i][j][0]=dp[i][j-1][0];
                if(a[i+1]>a[i])dp[i][j][1]=dp[i+1][j][0];
                continue;
            }
            if(a[j-1]<a[j])dp[i][j][0]=dp[i][j-1][0]%p;
            if(a[i]<a[j])dp[i][j][0]=(dp[i][j][0]+dp[i][j-1][1])%p;
            if(a[j]>a[i])dp[i][j][1]=dp[i+1][j][0]%p;
            if(a[i+1]>a[i])dp[i][j][1]=(dp[i][j][1]+dp[i+1][j][1])%p;
        }
    }
    printf("%lld
",(dp[1][n][0]+dp[1][n][1])%p);
}

2019-09-10 13:29:20


原文地址:https://www.cnblogs.com/kkkek/p/11497005.html