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\n",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\n",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\n",++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\n%d\n%d\n", 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\n%d\n",a[n%2][m],(b[n%2][m]+mod) % mod);
    }
    
    return 0;
}

 

转载于:https://www.cnblogs.com/downrainsun/p/10021861.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值