zoukankan      html  css  js  c++  java
  • dp题

    https://vjudge.net/contest/262670#problem/J

    dp思想,也可以用尺取做

    题意:
    给出一串数字,每个数字和他相邻的数相差不超过1,求最长子串长度,子串的最大值和最小值差值不超过1.

    解题思路:
    因为每个数他相邻的都不超过1,所以对于当前的数x来说.
    只要讨论x-1,x-2,x+1,x+2出现的最后位置即可.

    对于x+1 > x-1(x-2出现在x-1后面),那么就看x-1和x+2最后出现的位置(x和x-2不符合,x-1和x+1不符合)

    同理,对于x+1 < x-1,就看x-1的位置和x+2的位置.

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 1e5+5;
    int p[maxn];
    int main()
    {
        ios::sync_with_stdio(false);
        int n;
        cin>>n;
        int ans = 2;
        for(int i = 1;i <= n;i++)
        {
            int x;
            cin>>x;
            if(p[x-1] > p[x+1]) ans = max(ans,i-max(p[x+1],p[x-2]));
            else                ans = max(ans,i-max(p[x+2],p[x-1]));
            p[x] = i;
        }
        cout<<ans;
        return 0;
    }
     //rmq处理每个区间的最小值与最大值
    #include <stdio.h>
    #include <string.h>
    #include <iostream>
    #include <algorithm>
    #include <vector>
    #include <queue>
    #include <stack>
    #include <set>
    #include <map>
    #include <string>
    #include <math.h>
    #include <stdlib.h>
    using namespace std;
     
    #define rep(i,a,b) for (int i=(a),_ed=(b);i<=_ed;i++)
    #define per(i,a,b) for (int i=(b),_ed=(a);i>=_ed;i--)
    #define pb push_back
    #define mp make_pair
    const int inf_int = 2e9;
    const long long inf_ll = 2e18;
    #define inf_add 0x3f3f3f3f
    #define mod 1000000007
    #define LL long long
    #define ULL unsigned long long
    #define MS0(X) memset((X), 0, sizeof((X)))
    #define SelfType int
    SelfType Gcd(SelfType p,SelfType q){return q==0?p:Gcd(q,p%q);}
    SelfType Pow(SelfType p,SelfType q){SelfType ans=1;while(q){if(q&1)ans=ans*p;p=p*p;q>>=1;}return ans;}
    #define Sd(X) int (X); scanf("%d", &X)
    #define Sdd(X, Y) int X, Y; scanf("%d%d", &X, &Y)
    #define Sddd(X, Y, Z) int X, Y, Z; scanf("%d%d%d", &X, &Y, &Z)
    #define reunique(v) v.resize(std::unique(v.begin(), v.end()) - v.begin())
    #define all(a) a.begin(), a.end()
    typedef pair<int, int> pii;
    typedef pair<long long, long long> pll;
    typedef vector<int> vi;
    typedef vector<long long> vll;
    inline int read(){int ra,fh;char rx;rx=getchar(),ra=0,fh=1;while((rx<'0'||rx>'9')&&rx!='-')rx=getchar();if(rx=='-')fh=-1,rx=getchar();while(rx>='0'&&rx<='9')ra*=10,ra+=rx-48,rx=getchar();return ra*fh;}
    //#pragma comment(linker, "/STACK:102400000,102400000")
     
     
    int a[100005];
    int mx[100005][20],mi[100005][20];
     
    void RMQ(int n)
    {
        for(int i=1;i<=n;i++)
            mx[i][0] = mi[i][0] = a[i];
        for(int j=1;(1<<j)<=n;j++)
        {
            for(int i=1;i+(1<<j)-1<=n;i++)
            {
                mx[i][j] = max(mx[i][j-1],mx[i+(1<<(j-1))][j-1]);
                mi[i][j] = min(mi[i][j-1],mi[i+(1<<(j-1))][j-1]);
            }
        }
    }
     
    int query(int l,int r)
    {
        int k = 0;
        while((1<<(k+1))<(r-l+1))k++;
        int ans1 = max(mx[l][k],mx[r-(1<<k)+1][k]);
        int ans2 = min(mi[l][k],mi[r-(1<<k)+1][k]);
        return ans1-ans2;
    }
     
     
    int main()
    {
        //freopen("in.txt","r",stdin);
        //freopen("out.txt","w",stdout);
        ios::sync_with_stdio(0);
        cin.tie(0);
        int n;
        n = read();
        for(int i=1;i<=n;i++)a[i] = read();
        RMQ(n);
        int p = 1;
        int ans = 0;
        for(int i=1;i<=n;i++)
        {
            while(p<=i && query(p,i)>1)
            {
                p++;
            }
            ans = max(ans,i-p+1);
        }
        printf("%d
    ",ans);
     
        return 0;
    }

    https://blog.csdn.net/lxt_Lucia/article/details/81206439

    最长上升子序列

    https://www.cnblogs.com/Rosebud/p/9845935.html

    树状数组求,此处树状数组是求得最大值。

    https://vjudge.net/contest/204059#problem/G

    刷围墙,有n个人,只可以选k个人,保证能刷的最长。

    先更新每一个点最多能延伸到哪一点,因为这个可以保证每次选择最优解。

    然后用dp来更新最大值,代码里有注释。

    第一种:

    #include<bits/stdc++.h>
    using namespace std;
    const int maxn = 2000+50;
    int dp[maxn][maxn];
    int top[maxn];
    int main(){
        int t;scanf("%d",&t);
        for(int cas=1;cas<=t;cas++){
            memset(dp,0,sizeof(dp));
            memset(top,0,sizeof(top));
            int n,m,k;
            scanf("%d%d%d",&n,&m,&k);
            for(int i=0;i<m;i++){
                int x,y;scanf("%d%d",&x,&y);
                for(int j=x;j<=y;j++)top[j]=max(top[j],y);
            }
            for(int i=1;i<=n;i++) {
                for(int j=1;j<=k;j++){
                    dp[i][j]=max(dp[i][j],dp[i-1][j]);//不要第i个
                    dp[top[i]][j]=max(dp[top[i]][j],(dp[i-1][j-1]+top[i]-i+1));//要第i个,可以扩展到第top[i]个
                }
            }
            printf("Case #%d: %d
    ",cas,dp[n][k]);
        }
        return 0;
    }

    第二种 

    题意:给你m个连续区间,让你选取其中的k个,使其所包含的元素个数最多。

    思路:这道题类似于二维背包,很容易想到O(n^3)的方法。但这道题给的数据范围高达2000,所以需要进行一些优化。

    首先我们可以排除一些“无用”的区间,对于每个以r为右端点的区间,只保留其中左端点最小的,也就是长度最长的区间,以后只用这些区间来更新,其余的一概不用考虑。

    然后,我们可以用一个数组len来记录每个右端点可以连续延伸到的左端点的最远距离。设dp[i]表示前i个区间能取得的最多的元素个数,这样我们可以得到状态转移方程:

    按照01背包的思想,我们可以从尾到头扫一遍,就相当于更新了“多放进一件物品”时的状态。

    但是这样的做法可能会有遗漏,因为区间(i-len[i],i)之间的dp值可能会大于dp[i]。因此每用上面的方程从尾到头扫一遍之后,还需要从头到尾扫一遍,保证后面的dp值比前面的大。更新k次之后,dp[n]就是我们所求的结果。

    #define FRER() freopen("i.txt","r",stdin)
    #include<bits/stdc++.h>
    using namespace std;
    const int N=2000+10;
    int n,m,k;
    int len[N],d[N],kase=0;
     
    int main()
    {
        int T;
        scanf("%d",&T);
        while(T--)
        {
            scanf("%d%d%d",&n,&m,&k);
            memset(len,0,sizeof len);
            memset(d,0,sizeof d);
            for(int i=1; i<=m; ++i)
            {
                int l,r;
                scanf("%d%d",&l,&r);
                for(int j=l; j<=r; ++j)len[j]=max(len[j],j-l+1);
            }
            while(k--)
            {
                for(int i=n; i>=1; --i)
                    d[i]=max(d[i],d[i-len[i]]+len[i]);
                for(int i=1; i<=n; ++i)
                    d[i]=max(d[i],d[i-1]);
            }
            printf("Case #%d: %d
    ",++kase,d[n]);
        }
        return 0;
    }

    Codeforces 1132C - Painting the Fence - [前缀和优化]

     

    题目链接:https://codeforces.com/contest/1132/problem/C

    题意:

    这个和上面一题的区别是是固定选k-2个人,所以可以直接用前缀和方法来做。

    https://www.cnblogs.com/dilthey/p/10489056.html

    先开始记录每个点被刷的次数,然后枚举先删掉第一条边,减去对应区间被刷的次数(-1),然后如果某个点的次数变为1,说明只有一个人可以刷到,就记录为1,其他的都记为0,记录前缀和,

    然后在建另一个区间就编程减去那个区间只有他可以刷 的点的个数。

    #include<bits/stdc++.h>
    using namespace std;
    typedef pair<int,int> P;
    #define mk(x,y) make_pair(x,y)
    #define fi first
    #define se second
    const int maxn=5e3+10;
    int n,q;
    P p[maxn];
    int cnt[maxn],sum[maxn];
    int main()
    {
        cin>>n>>q;
        memset(cnt,0,sizeof(cnt));
        for(int i=1;i<=q;i++)
        {
            cin>>p[i].fi>>p[i].se;
            for(int k=p[i].fi;k<=p[i].se;k++) cnt[k]++;
        }
    
        int ans=0;
        for(int x=1;x<=q;x++)
        {
            for(int k=p[x].fi;k<=p[x].se;k++) cnt[k]--;
    
            int tot=0;
            for(int k=1;k<=n;k++)
            {
                tot+=(cnt[k]>0);
                if(cnt[k]==1) sum[k]=1;
                else sum[k]=0;
                sum[k]+=sum[k-1];
            }
    
            int Max=0;
            for(int y=1;y<=q;y++)
            {
                if(x==y) continue;
                Max=max(Max,tot-(sum[p[y].se]-sum[p[y].fi-1]));
            }
            ans=max(ans,Max);
    
            for(int k=p[x].fi;k<=p[x].se;k++) cnt[k]++;
        }
    
        cout<<ans<<endl;
    }

     Team them up!

     UVA - 1627 

    https://vjudge.net/contest/284474#problem/E

    有很多人把他们分组,要保证每边两两要认识,而且必须是a认识b,b认识a才算认识,所以在二分图匹配是不认识的人就必须颜色不同,然后进行dp,dp的话,有点像枚举,就是把每种

    情况都枚举出来,然后从最小的开始循环,有就输出,然后因为差值在-n到n之间,所以都加个n更方便数组。

    题目思路:一开始是挺没有思路的,如果仔细分析的话可以分析出,对于,每一个不相互认识的人可以看成二分图,因为只能分成两种人,一种相互认识,一种不相互认识,

    所以的话,我们把不相互认识的建图,有边存在的一定不能放在同一个集合中,这样的话构建出来的图是一个一个联通块,对于每一个联通块来说,如果不是二分图,肯定是不合法的,判定是否合法之后,我们的问题就是怎么找到差值最小的安排方式,经过前面的分析,我们可以发现每一个联通块分为两个绑定的团体,不同联通块则没有关系,这样的话,我们可以想到用dp去解决,设状态dp[i][j],为前i个联通块,第一个比第二个多j个是否存在,为什么呢,一般我们都是设差的绝对值,这里因为要打印路径,我们设为这样,为了方便打印路径,或者设为dp[i][j][k],为前i个联通块中第一个为j个,第二个为k个,剩下的问题就是打印路径了,因为每一个联通块的两种人只能选择是去第一个或则者去第二个,这样的话,给我们打印路径提供了很大的方便,从最后的答案逆推回去,根据这两种选择看上一个上一个状态是否存在,然后记录下来就行

    #include<cstdio>
    #include<vector>
    #include<cstring>
    #include<algorithm>
    using namespace std;
    
    const int Maxn=100;
    
    int N;
    bool G[Maxn+5][Maxn+5];
    int ccnt,col[Maxn+5];
    vector<int> team[Maxn+5][2];
    int diff[Maxn+5];
    bool f[Maxn+5][2*Maxn+5];
    
    bool DFS(int u,int c) {
        col[u]=c;
        team[ccnt][c-1].push_back(u);
        for(int v=0;v<N;v++)
            if(u!=v&&!(G[u][v]&&G[v][u])) {
                if(col[v]>0&&col[u]==col[v])
                    return false;
                if(!col[v]&&!DFS(v,3-c))
                    return false;
            }
        return true;
    }//二分图判定
    bool BuildGraph() {
        ccnt=0;
        memset(col,0,sizeof col);
    
        for(int i=0;i<N;i++)
            if(!col[i]) {
                team[ccnt][0].clear();
                team[ccnt][1].clear();
                if(!DFS(i,1))return false;
                diff[ccnt]=team[ccnt][0].size()-team[ccnt][1].size();
                ccnt++;
            }
        return true;
    }//对每个连通分量进行判定
    
    void Print(int ans) {
        vector<int> team1,team2;
        for(int i=ccnt-1;i>=0;i--) {
            int t;
            if(f[i][ans-diff[i]+N]) {
                t=0;ans-=diff[i];
            } else {
                t=1;ans+=diff[i];
            }
            for(int j=0;j<team[i][t].size();j++)
                team1.push_back(team[i][t][j]);
            for(int j=0;j<team[i][t^1].size();j++)
                team2.push_back(team[i][t^1][j]);
        }//找出答案
        printf("%d",team1.size());
        for(int i=0;i<team1.size();i++)
            printf(" %d",team1[i]+1);
        puts("");
        printf("%d",team2.size());
        for(int i=0;i<team2.size();i++)
            printf(" %d",team2[i]+1);
        puts("");
    }
    void Solve() {
        memset(f,false,sizeof f);
        f[0][0+N]=true;
    
        for(int i=0;i<ccnt;i++)
            for(int j=-N;j<=N;j++)
                if(f[i][j+N]) {
                    f[i+1][j+N+diff[i]]=true;
                    f[i+1][j+N-diff[i]]=true;
                }
        for(int i=0;i<=N;i++) {
            if(f[ccnt][i+N]) {
                Print(i);
                return;
            }
            if(f[ccnt][N-i]) {
                Print(-i);
                return;
            }
        }
    }
    
    int main() {
        #ifdef LOACL
        freopen("in.txt","r",stdin);
        freopen("out.txt","w",stdout);
        #endif
        int T;
        scanf("%d",&T);
        while(T--) {
            scanf("%d",&N);
            for(int i=0;i<N;i++) {
                int x;
                while(scanf("%d",&x)!=EOF&&x)
                    G[i][x-1]=true;//图是用邻接矩阵存的。
            }
    
            if(N==1||!BuildGraph())
                puts("No solution");
            //注意特判N=1的情况
            else Solve();
            if(T)puts("");
            memset(G,0,sizeof G);
        }
        return 0;
    }

     https://vjudge.net/contest/218179#problem/G 一道字符串相关的基础dp题。

    https://blog.csdn.net/zwj1452267376/article/details/54935275

    给一串字符串,只包含26个字母,可以把这串字符串分成若干个子串,但是限定每个字母只能出现在长度Ax的子串里,问最多有多少种分割方案,方案数对1e9+7取膜,以及分割子串最大长度,和最少分割子串数量。

    解题思路:

    设dp[i]为从0到i这段字符串的分割方案数,为了满足字符a[i]的限定条件,我们只能在i-Ai+1到i之间划分,设len=i-A[i]+1, 但是i-A[i]+1并不就是可以划分的长度,因为在i-Ai+1到i有些字母的限定子串长度会小于i-A[i]+1,所以我们可以设一个指针j从i这个点开始往下枚举,让len不断更新,当i-j+1>len的时候跳出,所以指针j在跳出之前,都是可以划分的点,假如我们在j这个点划分的话,这就是一种划分的方案,同时我们需要加上j这个点之前的划分方案数,也就是dp[j-1],所以每次枚举都要更新:dp[i]=(dp[i]+dp[j-1])%mod。这样就能求出最大方案数了。

    #include<bits/stdc++.h>
    
    using namespace std;
    const int maxm = 1e3 + 5;
    const int inf = 1e9 + 7;
    int n;
    int cnt[30];
    int dp_a[maxm], dp_b[maxm], dp_c[maxm];
    char ch[maxm];
    int main() {
        scanf("%d", &n);
        scanf("%s", ch);
        for(int i = 0; i< 26; i++) scanf("%d", &cnt[i]);
        int ant;
        dp_a[0] = 1;
        dp_b[0] = dp_c[0] = 0;
        for(int i = 1; i <= n; i++) {
            ant = inf;
            dp_a[i] = 0;
            dp_b[i] = -inf;
            dp_c[i] = inf;
            for(int j = i - 1; j >= 0; j--) {
                ant = min(ant, cnt[ch[j] - 'a' ]);
                if(ant < i - j) break;
                dp_a[i] = (dp_a[i] + dp_a[j]) % inf;
                dp_b[i] = max(i - j, max(dp_b[j], dp_b[i]));
                dp_c[i] = min(dp_c[j] + 1, dp_c[i]);
            }
        }
        printf("%d
    %d
    %d
    ", dp_a[n], dp_b[n], dp_c[n]);
        return 0;
    }

    https://vjudge.net/contest/216992#status/xiayuyang/D/0/ 求最长上升子序列的个数

    第一问,求最长公子序列,模板题。。。

    第二问,求最长公共子序列的个数,这个就比较有意思了。

    设len[i][j]表示表示第一个串到i位置,第二个串到j位置时的长度。

    设lcs[i][j]表示第一个串到i位置,第二个串到j位置时,lcs的个数。

    当a[i] == b[j]:

    那么最长公共子序列肯定是可以从i-1,j-1的位置继承来的,所以lcs[i][j] += lcs[i-1][j-1];

    当len[i][j-1] = len[i][j],说明lcs也可以从i,j-1的位置继承来,那么lcs[i][j] += lcs[i][j-1];

    当len[i-1][j] = len[i][j],同理。

    当a[i] != b[j]时:

    要么从i – 1,j的位置继承而来,要么从i,j-1的位置继承而来。

    但是当len[i][j-1] = len[i-1][j]时,说明两个lcs均是从i-1,j-1的位置继承而来,那么以i-1,j-1结尾的lcs的个数就被加了两次,所以要减去一次。

    然后这题限制空间,所以得用滚动数组优化。

    #include <stdio.h>
    #include <string.h>
    #include <algorithm>
    using namespace std;
    
    const int N = 5005;
    const int mod = 1e8;
    
    char x[N],y[N];
    
    int a[2][N],b[2][N];
    
    int main()
    {
        while (scanf("%s%s",x+1,y+1) != EOF)
        {
            int n = strlen(x+1) - 1;
            int m = strlen(y+1) - 1;
            
            for (int i = 0;i <= m;i++) b[0][i] = 1;
            b[1][0] = 1;
            
            for (int i = 1;i <= n;i++)
            {
                for (int j = 1;j <= m;j++)
                {
                    int c = i % 2,p = 1 - c;
                    
                    if (x[i] == y[j])
                    {
                        a[c][j] = a[p][j-1] + 1;
                        
                        int tmp = b[p][j-1] % mod;
                        
                        if (a[c][j] == a[c][j-1]) tmp = (tmp + b[c][j-1]) % mod;
                        if (a[c][j] == a[p][j]) tmp = (tmp + b[p][j]) % mod;
                        
                        b[c][j] = tmp;
                    }
                    else
                    {
                        a[c][j] = max(a[p][j],a[c][j-1]);
                        
                        int tmp = 0;
                        
                        if (a[c][j] == a[p][j]) tmp = (tmp + b[p][j]) % mod;
                        if (a[c][j] == a[c][j-1]) tmp = (tmp + b[c][j-1]) % mod;
                        if (a[c][j] == a[p][j-1]) tmp = (tmp - b[p][j-1]) % mod;
                        
                        b[c][j] = tmp;
                    }
                }
            }
            
            printf("%d
    %d
    ",a[n%2][m],(b[n%2][m]+mod) % mod);
        }
        
        return 0;
    }

  • 相关阅读:
    客户端session与服务端session
    对session和cookie的一些理解
    Servlet生命周期与工作原理
    Linux命令行编辑快捷键
    含有GROUP BY子句的查询中如何显示COUNT()为0的成果(分享)
    设计模式学习——准备(UML类图)
    find()方法
    js中的动态效果
    动态添加,移除,查找css属性的方法
    VUE中的require ( )
  • 原文地址:https://www.cnblogs.com/downrainsun/p/10021861.html
Copyright © 2011-2022 走看看