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://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!
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; }