关键点
- dp的参数
- dp的状态转移方程
- 初始化
- 细节部分
P1020 [NOIP1999 提高组] 导弹拦截
解析
这道题分为两问,涉及动态规划及贪心思想
Question 1.
根据题意,第一问求的即为最长不上升子序列。属于非常典型的dp。
做法
参数:设 表示以 结尾的最长不上升子序列,最终答案即为 。
转移方程:
复杂度:。
做法
当 的范围扩大到 时,第一种做法就不够快了,下面给出了一个 的做法。
回顾一下之前的状态:。
但这次,我们不是要按照相同的 处理状态,而是直接判断合法的 。
再看一下之前的转移:,就可以判断某个 是否合法。
初始时 肯定合法。
那么,只需要找到一个 最大的合法的 ,就可以得到最终最长不下降子序列的长度了。
根据此思路,可以进行如下分析:
参数: 为所有的长度为 的不下降子序列的末尾元素的最小值, 为子序列的长度。
做法:当前已知长度为 ,所以可从 到 循环,求出前 个元素的最长长度,循环过程中不断更新维护 数组及 的长度。
如何维护?
考虑每一步的决策,
当 时,显然 可以将其插入序列末尾,。
反之,可以将 序列中第一个大于它的元素更新为 。这一步中可以使用二分解决。
至此,Question 1.解决。
Question 2.
第二问要求拦截所有导弹所需的最小数量。
考虑贪心
从第一问来考虑,一套系统所拦截的即为当前的最长不上升子序列,推广考虑,多套系统拦截即为每次删除最长不上升子序列之后重复操作。所以根据Dilworth定理可得,最长上升子序列的长度就是能构成的不上升序列的个数。
所以第二问实际上即为求解最长上升子序列的长度,算法与**Que 1.**基本相同,稍微改动即可。
Code
// #pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE// #define MULTI_CASES#define endl '\n'const int MaxN = 2e5+100;const int INF = 1e9;const int mod=212370440130137957ll;int T=1, N, M;int a[MaxN];int dp[MaxN],dp2[MaxN];
inline void Solve(){ int x; while(cin>>x){ a[++N]=x; } dp[1]=a[1]; int len=1; for(int i=2;i<=N;i++){ if(a[i]<=dp[len]){
dp[++len]=a[i]; } else{ int cnt=upper_bound(dp+1,dp+len+1,a[i],greater<int>())-dp; dp[cnt]=a[i]; } } cout<<len<<endl; // return; len=1; dp2[1]=a[1]; for(int i=2;i<=N;i++){ if(a[i]>dp2[len]){ len++; dp2[len]=a[i]; } else{ int cnt=lower_bound(dp2+1,dp2+len+1,a[i])-dp2; dp2[cnt]=a[i]; } } cout<<len<<endl;}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
P1026 [NOIP2001 提高组] 统计单词个数
解析
本题求解将一个字符串拆分成 个部分,使得每份中包含的单词个数总数最大。
数据很小,考虑 做法。
首先,预处理 数组,表示从 到 的单词数。从后往前推,可得 如果存在从 开头的单词,则再加一(注意只加一次)。
关于参数,可以从 角度逆向去推,本题所求为分成 份以后,包含单词的最大个数。通过简单推理可得,本题 有三个参数,即为:
- 最大个数
- 分为 份
- 字符串长度 (这个条件没有明显指出,但十分关键,且在dp类做法中较为常见)
之后推理可得, 表示一个长度为 的字符串在拆分为 份时,最多的单词个数。
考虑状态转移方程,设断点 可以得出:
大体方向确定后,考虑初始化,
错误汇总
RE on #3~#10:数组大小开为 ,忽略了实际长度应该为 。
Code
// #pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE// #define MULTI_CASES#define endl '\n'const int MaxN = 800;const int INF = 1e9;const int mod=212370440130137957ll;int T=1, N, M, K;int a[MaxN];string sa[10];string s={},s1;
int sum[MaxN][MaxN];bool check(int l,int r){ string x=s.substr(l,r-l+1); for(int i=1;i<=K;i++){ if(x.find(sa[i])==0){ return true; } } return false;}void init(){ for(int i=N;i>=1;i--){ for(int j=i;j>=1;j--){ sum[j][i]=sum[j+1][i]; if(check(j,i))sum[j][i]++; } }}int dp[MaxN][MaxN];inline void Solve(){ cin>>N>>M; s+=' '; for(int i=1;i<=N;i++){ cin>>s1; s+=s1; } N*=20; cin>>K; for(int i=1;i<=K;i++){ cin>>sa[i]; } init(); // dp[0][0]=sum[0][0]; dp[0][0]=0; for(int i=1;i<=M;i++){ dp[i][i]=dp[i-1][i-1]+sum[i][i]; } for(int i=1;i<=N;i++){ dp[i][1]=sum[1][i]; } for(int i=1;i<=N;i++){ for(int j=1;j<=M&&j<i;j++){ for(int r=j;r<i;r++){ dp[i][j]=max(dp[i][j],dp[r][j-1]+sum[r+1][i]); } } } cout<<dp[N][M];}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
P11187 配对序列
解析
根据题意,此题要求出序列中符合 这种两两相同的最长子序列。
要求子序列最大值,显然我们可以使用 做法。
考虑参数,设 表示以 为结尾的符合条件的子序列最大值。最终的答案即为
考虑预处理,我们可以先预处理出 ,表示序列中上一个与 相同的数的位置。具体实现也十分简单,使用map
即可。
不难得出, 如果不为 ,则 才会有值。又因为每个数字都是成对出现的,所以,只需要找到与这个数相同的上一个数和上一个数的上一个数,求出从上一个数的上一个数的下一位至上一个数的前一位的最大值,这样可以满足 的条件。再加上当前的 就是 的答案。
即
关于求 的操作,我们可以使用数据结构优化,比如线段树或者ST表等。
注意如果 则应该跳过。
Code
// #pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE// #define MULTI_CASES#define endl '\n'const int MaxN = 5e5+100;const int INF = 1e9;const int mod=212370440130137957ll;int T=1, N, M;int a[MaxN];int pre[MaxN];struct Segment{ struct Point{ int l,r,tag; int mex; }; vector<int>a; vector<Point>t; Segment(int N){ a.assign(N+10,0); t.assign(N<<4,{}); } #define ls p<<1 #define rs p<<1|1 int midd(int p){ return (t[p].l+t[p].r)>>1; } void push_up(int p){ t[p].mex=max(t[ls].mex,t[rs].mex); } void change(int p,int k,int q){ int l=t[p].l,r=t[p].r; if(l==r&&l==q){ t[p].mex+=k; return; } int mid=midd(p); if(q<=mid){ change(ls,k,q); } if(mid<q){ change(rs,k,q); } push_up(p); } void build(int p,int nl,int nr){ t[p].l=nl,t[p].r=nr; if(nl==nr){ t[p].mex=a[nl]; return; } int mid=midd(p); build(ls,nl,mid); build(rs,mid+1,nr); push_up(p); } int query(int p,int nl,int nr){ int l=t[p].l,r=t[p].r; if(nl<=l&&r<=nr){ return t[p].mex; } int mid=midd(p); int ans=0; if(nl<=mid){ ans=max(ans,query(ls,nl,nr)); } if(mid<nr){ ans=max(ans,query(rs,nl,nr)); } return ans; }};inline void Solve(){ map<int,int>mp; cin>>N; Segment Tree(N); for(int i=1;i<=N;i++){ cin>>a[i]; // Tree.a[i]=a[i]; if(mp[a[i]]==0){ mp[a[i]]=i; pre[i]=0; } else{ pre[i]=mp[a[i]]; mp[a[i]]=i; } } pre[0]=0; Tree.build(1,1,N); // Tree.change(1,1,1); // cout<<1;return; // vector<int>dp(N+1); for(int i=1;i<=N;i++){ if(pre[i]==0) continue; int maxn=Tree.query(1,pre[pre[i]]+1,pre[i]-1); // cout<<maxn<<" "; Tree.change(1,maxn+2,i); }
cout<<Tree.query(1,1,N);}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
P11188 「KDOI-10」商店砍价
解析
此题中 不难得出如果要花费 的代价删除剩余的数,数位一定不能大于 。
由此可得,设 表示前 位中已经删除 个数时,删除所有数位的最小代价。
不难得出,每保留单独的一位,代价就会增加 。所以我们可以从后往前递推,
可得
显然,我们可以使用滚动数组来优化空间,所以最终的式子为:
Code
// #pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE#define MULTI_CASES#define endl '\n'const int MaxN = 2e5+100;const int INF = 1e18;const int mod=212370440130137957ll;int T=1, N, M;int a[MaxN];int binpow(int a,int b){ if(b==0){ return 1; } int res=binpow(a,b/2); if(b%2){ return res*res*a; } else{ return res*res; }}inline void Solve(){ string s; cin>>s; for(int i=1;i<=9;i++){ cin>>a[i]; } int ans=0; for(int i=0;i<s.size();i++) { ans+=a[s[i]-'0']; } int sum=ans; // cout<<ans<<endl; for(int i=6;i>=1;i--){ vector<int>dp(10); dp.assign(10,INF); dp[i]=sum; for(int j=1;j<=s.size();j++){ for(int ii=1;ii<=i;ii++){ dp[ii]=min(dp[ii],dp[ii+1]+binpow(10,ii-1)*(s[j-1]-'0')-a[s[j-1]-'0']); } } ans=min(ans,dp[1]); // cout<<dp[1]<<' '; } cout<<ans<<endl;}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0); cin>>T;#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
CF2025D Attribute Checks
解析
本题给定了两种属性,在过程中会获得 个属性点,以及多次check,求使得通过的check数最多。
考虑 做法,首先是 的状态,设 表示获得 个属性点,并将 个属性点加在智力,将 个属性点加在力量上时,能够通过的最多检查数。
考虑状态转移,显然可得:
对于 ,若 : 若 : 若 :
考虑优化,可以发现,在cnt
更新之后,只要check的值小于等于cnt
,只需要将值加一即可。因此我们可以优化掉第一维只保留第二维。对于区间修改,可以使用线段树来实现。
Code
TLE
#pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE// #define MULTI_CASES#define endl '\n'const int MaxN = 2e6+100;const int INF = 1e9;const int mod=212370440130137957ll;int T=1, N, M;int a[MaxN];int dp[5000][5000];inline void Solve(){ cin>>N>>M; for(int i=1;i<=N;i++){ cin>>a[i]; } int cnt=0; for(int i=1;i<=N;i++){ if(a[i]==0){ cnt++; for(int j=0;j<cnt;j++){ dp[cnt][j]=dp[cnt-1][j]; } } else{ if(a[i]>0){ for(int j=a[i];j<=cnt;j++){ dp[cnt][j]=max(dp[cnt][j]+1,dp[cnt-1][j-1]+1); } } else{ for(int j=cnt-abs(a[i]);j>=0;j--){ dp[cnt][j]=max(dp[cnt][j]+1,dp[cnt-1][j]+1); } } } } int ans=0; for(int i=0;i<=cnt;i++){ ans=max(ans,dp[cnt][i]); } cout<<ans<<endl;}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
AC
#pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll#define ONLINE_JUDGE// #define MULTI_CASES#define endl '\n'const int MaxN = 2e6+100;const int INF = 1e9;const int mod=212370440130137957ll;int T=1, N, M;int a[MaxN];// int dp[500][5000];struct Segment{ vector<int>a; struct Point{ int l,r,tag; int mex; }; vector<Point>t; Segment(int N){ a.assign(N,0); t.assign(N<<2,{}); } #define ls p<<1 #define rs p<<1|1 int midd(int p){ return (t[p].l+t[p].r)>>1; } void push_up(int p){ t[p].mex=max(t[ls].mex,t[rs].mex); } void pls(int p,int k){ t[p].tag+=k; t[p].mex+=k; } void push_down(int p){ if(t[p].tag){ pls(ls,t[p].tag); pls(rs,t[p].tag); t[p].tag=0; } } void build(int p,int l,int r){ t[p].l=l,t[p].r=r; if(l==r){ t[p].mex=a[l]; return; } build(ls,l,midd(p)); build(rs,midd(p)+1,r); push_up(p); } void dfs(int p,int l,int r){ if(l==r){ a[l]=t[p].mex; return; } int mid=midd(p); push_down(p); dfs(ls,l,mid); dfs(rs,mid+1,r); push_up(p); } void change(int p,int nl,int nr,int k){ int l=t[p].l,r=t[p].r; if(nl<=l&&r<=nr){ pls(p,k); return; } push_down(p); int mid=midd(p); if(nl<=mid){ change(ls,nl,nr,k); } if(mid<nr){ change(rs,nl,nr,k); } push_up(p); } int query_max(int p,int nl,int nr){ int l=t[p].l,r=t[p].r; if(nl<=l&&r<=nr){ return t[p].mex; } int mid=midd(p),ans=0; push_down(p); if(nl<=mid){ ans=max(ans,query_max(ls,nl,nr)); } if(mid<nr){ ans=max(ans,query_max(rs,nl,nr)); } return ans; }};inline void Solve(){ cin>>N>>M; for(int i=1;i<=N;i++){ cin>>a[i]; } int cnt=0; Segment Tree(M+10); Tree.build(1,0,M); for(int i=1;i<=N;i++){ if(a[i]==0){ cnt++; Tree.dfs(1,0,M); for(int j=M;j>=1;j--) Tree.a[j]=max(Tree.a[j],Tree.a[j-1]); Tree.build(1,0,M); } else{ if(a[i]>0){ if(cnt>=a[i]) Tree.change(1,a[i],cnt,1); } else{ if(cnt>=abs(a[i])) Tree.change(1,0,cnt-abs(a[i]),1); } }
} // cerr<<cnt;return; // cout<<1;return; int ans=0;
cout<<Tree.query_max(1,0,cnt)<<endl;}signed main(){#ifndef ONLINE_JUDGE freopen(".in", "r", stdin); freopen(".out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); //fclose(stdin); //fclose(stdout); return 0;}
P4059 找爸爸(daddy)
题目分析
题目要求DNA序列的最大相似程度,考虑 做法,可以得出 表示 a序列长度为 ,b序列长度为 ,末尾有/无空格( 无空格, 表示a序列有空格, 表示b序列末尾有空格)时的最大相似程度。
关于空格,对于每段连续空格,相似程度为 因为 都是正整数,所以其相似程度一定为负数。对于一个空格,会减少 点相似度,如果在后面多添加一个空格,则会进一步减少 点相似度。
状态转移方程如下:
Code
// #pragma GCC optimize(1, 2, 3, "Ofast", "inline")#include <bits/stdc++.h>using namespace std;#define ll long long#define int ll// #define ONLINEJUDGE// #define MULTI_CASESconst int MaxN = 2e3 + 100;const int INF = 1e18;int T = 1, N, M;int a[10][10];int dp[MaxN][MaxN][4];int A, B;
int g(int k){ return -A - B * (k - 1);}inline void Solve(){ string s1, s2; cin >> s1 >> s2; s1 = ' ' + s1; s2 = ' ' + s2; map<char, int> mp; mp['A'] = 1; mp['T'] = 2; mp['G'] = 3; mp['C'] = 4; for (int i = 1; i <= 4; i++) { for (int j = 1; j <= 4; j++) { cin >> a[i][j]; } } cin >> A >> B; for (int i = max(s1.size(), s2.size()); i >= 1; i--) { dp[0][i][0] = dp[i][0][0] = dp[0][i][2] = dp[i][0][1] = dp[0][i][1] = dp[i][0][2] = -INF; dp[0][i][1] = dp[i][0][2] = g(i); } for (int i = 1; i < s1.size(); i++) { for (int j = 1; j < s2.size(); j++) { dp[i][j][0] = max({dp[i - 1][j - 1][0], dp[i - 1][j - 1][1], dp[i - 1][j - 1][2]}) + a[mp[s1[i]]][mp[s2[j]]]; dp[i][j][1] = max({dp[i - 1][j][0] - A, dp[i - 1][j][1] - B, dp[i - 1][j][2] - A}); dp[i][j][2] = max({dp[i][j - 1][0] - A, dp[i][j - 1][1] - A, dp[i][j - 1][2] - B}); } } cout << max({dp[s1.size() - 1][s2.size() - 1][0], dp[s1.size() - 1][s2.size() - 1][1], dp[s1.size() - 1][s2.size() - 1][2]}) << endl;}signed main(){#ifndef ONLINEJUDGE freopen("daddy.in", "r", stdin); freopen("daddy.out", "w", stdout);#endif ios::sync_with_stdio(0); cin.tie(0), cout.tie(0);
#ifdef MULTI_CASES cin >> T; while (T--)#endif Solve(); // fclose(stdin); // fclose(stdout); return 0;}