AtCoder DP Contest vp 记录
题单。
A
从 \(i-1\) 与 \(i-2\) 转移即可。
#include<bits/stdc++.h> using namespace std; int n; int h[100031]; int dp[100031]; int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>h[i]; memset(dp,0x3f,sizeof(dp)); dp[1]=0; for(int i=1;i<=n;i++){ if(i-1>=1) dp[i]=min(dp[i],dp[i-1]+abs(h[i]-h[i-1])); if(i-2>=1) dp[i]=min(dp[i],dp[i-2]+abs(h[i]-h[i-2])); } cout<<dp[n]; return 0; }
B
枚举转移点 \(i-k \sim i-1\) 转移即可。
#include<bits/stdc++.h> using namespace std; int n,k; int h[100031]; int dp[100031]; int main(){ cin>>n>>k; for(int i=1;i<=n;i++) cin>>h[i]; memset(dp,0x3f,sizeof(dp)); dp[1]=0; for(int i=1;i<=n;i++) for(int j=1;j<=k;j++) if(i-j>=1) dp[i]=min(dp[i],dp[i-j]+abs(h[i]-h[i-j])); cout<<dp[n]; return 0; }
C
用 \(dp_{1 \sim n,0 \sim 3}\) 记录每天三种活动的幸福值即可。
转移:
#include<bits/stdc++.h> using namespace std; int n,k; int a[100031][3]; int dp[100031][3]; int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i][0]>>a[i][1]>>a[i][2]; memset(dp,-0x3f,sizeof(dp)),dp[0][0]=dp[0][1]=dp[0][2]=0; for(int i=1;i<=n;i++){ dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i][0]; dp[i][1]=max(dp[i-1][0],dp[i-1][2])+a[i][1]; dp[i][2]=max(dp[i-1][0],dp[i-1][1])+a[i][2]; } cout<<max(dp[n][0],max(dp[n][1],dp[n][2])); return 0; }
D
01 背包板子。
#include<bits/stdc++.h> #define int long long using namespace std; int N,W; int w[131],v[131]; int dp[100031]; signed main(){ cin>>N>>W; for(int i=1;i<=N;i++) cin>>w[i]>>v[i]; for(int i=1;i<=N;i++) for(int j=W;j>=w[i];j--) dp[j]=max(dp[j],dp[j-w[i]]+v[i]); cout<<dp[W]; return 0; }
E
01 背包变种。
考虑通过 01 背包求出对于所有 \(1 \le i \le \text{MAX}\) 的 \(i\),
在满足 \(\sum v_i \le \text{MAX}\) 的条件下最大化的 \(\sum w_i\),
其中 \(\text{MAX}=n \times 10^3\)(因为 \(1 \le v_i \le 10^3\)),记为 \(dp_i\)。
然后倒序枚举所有的 \(dp_i\)(为了保证找到的第一个就是最大的),
若当前的 \(dp_i \le W\),输出即可。
#include<bits/stdc++.h> #define int long long using namespace std; int N,W,MAX; int w[131],v[131]; int dp[100031]; signed main(){ cin>>N>>W; MAX=N*1000; for(int i=1;i<=N;i++) cin>>w[i]>>v[i]; memset(dp,0x3f,sizeof(dp)); dp[0]=0; for(int i=1;i<=N;i++) for(int j=MAX;j>=v[i];j--) dp[j]=min(dp[j],dp[j-v[i]]+w[i]); for(int i=MAX;i;i--){ if(dp[i]<=W){ cout<<i; break; } } return 0; }
F
LCS 板子。
dp 时可以记录转移节点,然后输出时按转移节点递归输出即可。
#include<bits/stdc++.h> using namespace std; string a,b; int n,m; int dp[3031][3031]; int x[3031][3031],y[3031][3031]; void print(int nn,int mm){ if(nn==0||mm==0) return; print(nn-x[nn][mm],mm-y[nn][mm]); if(a[nn]==b[mm]) cout<<a[nn]; } int main(){ cin>>a>>b; a="#"+a,b="#"+b; n=a.size()-1,m=b.size()-1; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(a[i]==b[j]) dp[i][j]=dp[i-1][j-1]+1,x[i][j]=y[i][j]=1; else if(dp[i-1][j]>dp[i][j-1]) dp[i][j]=dp[i-1][j],x[i][j]=1; else dp[i][j]=dp[i][j-1],y[i][j]=1; } } print(n,m); return 0; }
G
令 \(dp_x\) 表示从节点 \(x\) 出发的最长路径长度。
转移时,递归地向儿子寻找路径,进行收集型 dp 即可,别忘加上与儿子的连边。顺便对答案取 \(\max\)。
#include<bits/stdc++.h> using namespace std; int n,m,ans; int dp[100031]; vector<int> G[100031]; void dfs(int x,int fa){ if(dp[x]) return; dp[x]=-1; for(auto i:G[x]){ if(i==fa) continue; dfs(i,x); dp[x]=max(dp[x],dp[i]); } dp[x]++; ans=max(ans,dp[x]); } int main(){ cin>>n>>m; for(int i=1,u,v;i<=m;i++) cin>>u>>v,G[u].push_back(v); for(int i=1;i<=n;i++) dfs(i,0); cout<<ans; return 0; }
H
从左、上的 .
转移即可。
#include<bits/stdc++.h> #define int unsigned long long #define MOD ((int)(1e9+7)) using namespace std; int n,m,ans; char a[1031][1031]; int dp[1031][1031]; signed main(){ cin>>n>>m; dp[1][1]=1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) cin>>a[i][j]; for(int i=1;i<=n;i++){ for(int j=1;j<=m;j++){ if(i==1&&j==1) continue; if(a[i-1][j]!='#') dp[i][j]=(dp[i][j]+dp[i-1][j])%MOD; if(a[i][j-1]!='#') dp[i][j]=(dp[i][j]+dp[i][j-1])%MOD; } } cout<<dp[n][m]%MOD; return 0; }
I
期望 dp 板子。
令 \(dp_{i,j}\) 表示前 \(i\) 个银币有 \(j\) 个朝上的概率。
初始:
转移:
注意至少要保留 \(10\) 位小数。
#include<bits/stdc++.h> using namespace std; int n; long double ans; long double p[3031]; long double dp[3031][3031]; int main(){ cin>>n; for(int i=1;i<=n;i++) cin>>p[i]; dp[0][0]=1; for(int i=1;i<=n;i++){ dp[i][0]=dp[i-1][0]*(1-p[i]); for(int j=1;j<=i;j++) dp[i][j]=dp[i-1][j-1]*p[i]+dp[i-1][j]*(1-p[i]); } for(int i=0;i<=n;i++) if(i>n-i) ans+=dp[n][i]; cout<<setprecision(10)<<fixed<<ans; return 0; }
J
不好想的期望 dp。
令 \(dp_{i,j,k}\) 表示有 \(i\) 个 \(1\) 个寿司的盘子、\(j\) 个 \(2\) 个寿司的盘子、\(k\) 个 \(3\) 个寿司的盘子的操作次数的数学期望值(概率与其答案之积的总和)。
转移:
-
对于空盘子,直接加上每次操作的平均概率 \(\dfrac{n}{i+j+k}\) 即可。
-
对于 \(1\) 个寿司的盘子,直接加上概率 \(\dfrac{i}{i+j+k} \ \times\) 答案 \(dp_{i-1,j,k}\) 即可。
-
对于 \(2\) 个寿司的盘子,直接加上概率 \(\dfrac{j}{i+j+k} \ \times\) 答案 \(dp_{i+1,j-1,k}\)(因为 \(2\) 个寿司吃一个就变成了 \(1\) 个) 即可。
-
对于 \(3\) 个寿司的盘子,直接加上概率 \(\dfrac{k}{i+j+k} \ \times\) 答案 \(dp_{i,j+1,k-1}\) 即可。
答案为 \(dp_{cnt_1,cnt_2,cnt_3}\)(\(cnt_i\) 为 \(i\) 个寿司的盘子的初始数量)。
仍然需要至少保留十位小数。
并且为保证无后效性,需要按照 \(k,j,i\) 的顺序枚举状态。
#include<bits/stdc++.h> using namespace std; int n; int cnt[31]; double dp[331][331][331]; int main(){ cin>>n; for(int i=1,a;i<=n;i++) cin>>a,cnt[a]++; for(int k=0;k<=n;k++){ for(int j=0;j<=n;j++){ for(int i=0;i<=n;i++){ if(!i&&!j&&!k) continue; dp[i][j][k]=(double)n; if(i) dp[i][j][k]+=dp[i-1][j][k]*(double)i; if(j) dp[i][j][k]+=dp[i+1][j-1][k]*(double)j; if(k) dp[i][j][k]+=dp[i][j+1][k-1]*(double)k; dp[i][j][k]/=(double)(i+j+k); } } } cout<<setprecision(10)<<fixed<<dp[cnt[1]][cnt[2]][cnt[3]]; return 0; }
K
博弈论 dp 板子。
令 \(dp_i\) 表示还剩下 \(i\) 个石子时是先手胜(\(1\))还是后手胜(\(0\))。
显然 \(dp_i\) 可以转移到 \(dp_{i,a_j}\)。
很容易想到当一个先手必胜状态可以转移到另一个必败状态时,
当前状态是必败的。
于是我们枚举 \(i,j\) 表示所有状态
并检查其是否会转移到先手必败状态去即可。
#include<bits/stdc++.h> using namespace std; int k,n; int a[131],dp[100031]; int main(){ cin>>n>>k; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1;i<=k;i++) for(int j=1;j<=n;j++) dp[i]+=(i-a[j]>=0&&!dp[i-a[j]]); cout<<(dp[k]?"First":"Second"); return 0; }
L
有意思的题。似乎既可贪心又可 dp。
很容易想到令 \(dp_{i,j}\) 表示两端数下标为 \(i,j\) 时的 \(X-Y\) 值。
分情况转移:
-
若已经被取走的数为偶数个,则此时是先手操作,令 \(dp_{i,j}=\max(dp_{i+1,j}+a_i,dp_{i,j-1}+a_j)\)。
-
若已经被取走的数为奇数个,则此时是后手操作,令 \(dp_{i,j}=\min(dp_{i+1,j}-a_i,dp_{i,j-1}-a_j)\)。
答案为 \(dp_{1,n}\)。
贪心思路有心情再更。
//dp code #include<bits/stdc++.h> #define int long long using namespace std; int n,a[3031]; int dp[3031][3031]; signed main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i]; for(int l=1;l<=n;l++){ for(int i=1;i+l-1<=n;i++){ int j=i+l-1; if((n-l)%2) dp[i][j]=min(dp[i+1][j]-a[i],dp[i][j-1]-a[j]); else dp[i][j]=max(dp[i+1][j]+a[i],dp[i][j-1]+a[j]); } } cout<<dp[1][n]; return 0; } //greedy code //......
M
前缀和优化板子。
首先令 \(dp_{i,j}\) 表示前 \(i\) 个人分到 \(j\) 块糖果的方案数。
易得朴素 dp 方程:
直接转移时间复杂度 \(O(nk^2)\),无法通过。
考虑到每个状态都是由其上面同列所有行的状态转移而来,
于是令 \(sum_i\) 表示 \(\sum^{i}_{j=0} dp_{i,j}\),转移:
然后 dp 转移方程改为:
即可直接转移。时间复杂度 \(O(nk)\)。
#include<bits/stdc++.h> #define int long long using namespace std; const int MOD=1e9+7; int n,k,a[131]; int sum[100031],dp[131][100031]; int get_sum(int l,int r){ if(!l) return sum[r]%MOD; return (sum[r]+MOD-sum[l-1])%MOD; } signed main(){ cin>>n>>k; for(int i=0;i<n;i++) cin>>a[i]; dp[0][0]=1ll; for(int i=1;i<=n;i++){ sum[0]=dp[i-1][0]; for(int j=1;j<=k;j++) sum[j]=(sum[j-1]+dp[i-1][j])%MOD; for(int j=0;j<=k;j++) dp[i][j]=get_sum(max(0ll,j-a[i-1]),j); } cout<<dp[n][k]%MOD; return 0; }
N
原:P1775。
区间 dp 板子。
令 \(dp_{i,j}\) 表示区间 \([i,j]\) 合并成一个数的最小代价。
考虑枚举中转点 \(k\),显然有转移:
其中 \(\sum^{j}_{p=i} a_p\) 用前缀和维护即可。
时间复杂度 \(O(n^3)\)。
注意枚举 \(i\) 时为确保无后效性需要倒序枚举,枚举 \(k\) 时不能到 \(j\)。
#include<bits/stdc++.h> #define int long long using namespace std; int n; int a[431],sum[431]; int dp[431][431]; signed main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i],sum[i]=sum[i-1]+a[i]; memset(dp,0x3f,sizeof(dp)); for(int i=1;i<=n;i++) dp[i][i]=0; for(int i=n;i>=1;i--) for(int j=i+1;j<=n;j++) for(int k=i;k<j;k++) dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+(sum[j]-sum[i-1])); cout<<dp[1][n]; return 0; }
O
状压 dp 板子。
考虑对于枚举集合 \(S\),然后枚举 \(i\),让第 \(cur\) 个男人匹配第 \(i\) 个女人。
易得转移方程:
考虑用 \(1 \sim 2^n-1\) 的二进制数表示 \(S\),则转移方程改为:
此时 \(cur\) 即为 \(S\) 二进制下有多少个 \(1\)(因为男女人数相等)。
直接转移即可,答案为 \(dp_{1,2^n-1}\),时间复杂度 \(O(n2^n)\)。
#include<bits/stdc++.h> using namespace std; const int MOD=1e9+7; int n; int a[31][31]; int dp[(1<<21)]; int main(){ cin>>n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>a[i][j]; dp[0]=1; for(int S=1;S<(1<<n);S++){ int cur=0,S2=S; while(S2>0) cur+=(S2&1),S2>>=1; for(int i=1;i<=n;i++) if(a[cur][i]&&((S>>(i-1))&1)) dp[S]=(dp[S]+dp[S-(1<<(i-1))])%MOD; } cout<<dp[(1<<n)-1]%MOD; return 0; }
P
树形 dp 板子。
令 \(dp_{i,0/1}\) 表示将节点 \(i\) 及其子树染成黑(\(1\))或白(\(0\))的方案数。
显然有转移:
(其中 \(j \in i\) 的子树)
直接 dfs 进行转移即可。时间复杂度 \(O(n)\)。
#include<bits/stdc++.h> #define int long long using namespace std; int n; vector<int> G[200031]; int dp[100031][2]; const int MOD=1e9+7; void dfs(int x,int f){ dp[x][0]=dp[x][1]=1; for(auto i:G[x]){ if(i==f) continue; dfs(i,x); dp[x][0]=(dp[x][0]*(dp[i][0]+dp[i][1]))%MOD; dp[x][1]=(dp[x][1]*dp[i][0])%MOD; } } signed main(){ cin>>n; for(int i=1,u,v;i<n;i++) cin>>u>>v, G[u].push_back(v), G[v].push_back(u); dfs(1,0); cout<<(dp[1][0]+dp[1][1])%MOD; return 0; }
Q
题目要求出最大带权上升子序列(Maximum Weighted Increasing Subsequence,我们姑且称其为 MWIS)。
令 \(dp_i\) 表示以 \(i\) 结尾的 MWIS 的长度。显然有转移:
套个树状数组优化取 \(\max\) 再进行转移即可,时间复杂度 \(O(n \log n)\)。
#include<bits/stdc++.h> #define int long long using namespace std; int n,ans,a[200031],h[200031],t[200031]; void add(int x,int y){ for(int i=x;i<=n;i+=i&(-i)) t[i]=max(t[i],y); } int qry(int x){ int q=0; for(int i=x;i;i-=i&(-i)) q=max(q,t[i]); return q; } signed main(){ cin>>n; for(int i=1;i<=n;i++) cin>>h[i]; for(int i=1;i<=n;i++) cin>>a[i]; for(int i=1,dp;i<=n;i++) dp=qry(h[i]-1)+a[i],add(h[i],dp),ans=max(ans,dp); cout<<ans; return 0; }
R
矩阵加速 dp 板子。
首先令 \(dp_{i,j,k}\) 表示 \(i \to j\) 路径中长度为 \(k\) 的路径条数。
考虑像 floyd 那样枚举中转点 \(t\) 进行转移。
于是易得转移方程:
直接转移时间复杂度 \(O(n^3k)\),T 飞了。
然后我们发现这个转移方程就是矩阵快速幂。
于是套个矩阵快速幂即可。时间复杂度 \(O(n^3 \log k)\)。
#include<bits/stdc++.h> #define int long long using namespace std; int n,k,ans; const int MOD=1e9+7; struct matrix{ int siz,v[53][53]; matrix(){ memset(v,0,sizeof(v)); } }a; matrix operator* (const matrix &a,const matrix &b){ matrix ret; ret.siz=a.siz; for(int i=1;i<=ret.siz;i++) for(int j=1;j<=ret.siz;j++) for(int t=1;t<=ret.siz;t++) ret.v[i][j]+=a.v[i][t]*b.v[t][j]%MOD,ret.v[i][j]%=MOD; return ret; } matrix qpow(matrix a,int b){ matrix ret; ret.siz=a.siz; for(int i=1;i<=ret.siz;i++) ret.v[i][i]=1; while(b){ if(b&1) ret=ret*a; a=a*a,b>>=1; } return ret; } signed main(){ cin>>n>>k,a.siz=n; for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) cin>>a.v[i][j]; matrix res=qpow(a,k); for(int i=1;i<=n;i++) for(int j=1;j<=n;j++) ans=(ans+res.v[i][j])%MOD; cout<<ans; return 0; }
S
数位 dp 板子。
令 \(dp_{i,j,k}\) 表示 \(1 \sim K\) 中,目前处于前缀 \(i\) 位,模 \(d\) 余数为 \(j\),且与 \(K\) 相同(\(k=1\)) / 不同(\(k=0\))的数的个数。
显然有转移:
(其中 \(m\) 为当前这一位能填的最大数值,\(t\) 枚举的是所有能填的数,枚举顺序是从高位到低位)
直接转移即可。需要注意一些细节(在代码中)。
#include<bits/stdc++.h> #define int long long using namespace std; string k; int d,num[10031]; int dp[10031][131][2]; const int MOD=1e9+7; int dfs(int pos,int res,int sta){ if(!pos) return !res; if(dp[pos][res][sta]!=(int)-1) return dp[pos][res][sta]%MOD; int ret=0,maxx=9; if(sta) maxx=num[pos]; //保证构造出的数 <=k for(int i=0;i<=maxx;i++) ret=(ret+dfs(pos-1,(res+i)%d,sta&&(i==maxx)))%MOD; return dp[pos][res][sta]=ret; } signed main(){ cin>>k>>d; memset(dp,-1,sizeof(dp)); for(int i=0;i<k.size();i++) num[i+1]=k[k.size()-i-1]-'0'; cout<<((dfs(k.size(),0,1)-1)%MOD+MOD)%MOD; //要 -1,因为 0 也被算入了 return 0; }
T
简单前缀和优化。
令 \(dp_{i,j}\) 表示第 \(i\) 个数填 \(j\) 时的方案数。
答案即为 \(\sum_{k=1}^{n} dp_{i,k}\)。
易得朴素转移方程:
套个前缀和优化掉 \(\sum\) 即可。时间复杂度 \(O(n^2)\)。
#include<bits/stdc++.h> #define int long long using namespace std; int n; string s; int dp[3031][3031]; int sum[3031][3031]; const int MOD=1e9+7; signed main(){ cin>>n>>s,s="#"+s; dp[1][1]=1; for(int i=1;i<=n;i++) sum[1][i]=1; for(int i=2;i<=n;i++){ for(int j=1;j<=i;j++){ if(s[i-1]=='<') dp[i][j]=sum[i-1][j-1]%MOD; else dp[i][j]=(sum[i-1][i-1]-sum[i-1][j-1]+MOD)%MOD; sum[i][j]=(sum[i][j-1]+dp[i][j])%MOD; } } cout<<sum[n][n]%MOD; return 0; }
U
看到 \(n \le 16\) 直接状压 dp。
令 \(dp_i\) 表示集合 \(i\) 能产生的最大得分。
易得转移方程:
(其中 \(k\) 为 \(j\) 的补集)
初始状态预处理出每个集合 \(i\) 分成一组的最大得分即可。
#include<bits/stdc++.h> #define int long long using namespace std; int n; int a[31][31]; int val[(1<<17)],dp[(1<<17)]; signed main(){ cin>>n; for(int i=0;i<n;i++) for(int j=0;j<n;j++) cin>>a[i][j]; for(int S=1;S<(1<<n);S++) for(int i=0;i<n;i++) for(int j=0;j<i;j++) if(((1<<i)&S)&&((1<<j)&S)) val[S]+=a[i][j]; for(int S=1;S<(1<<n);S++){ dp[S]=val[S]; for(int i=S&(S-1);i;i=S&(i-1)) dp[S]=max(dp[S],dp[i]+dp[S^i]); } cout<<dp[(1<<n)-1]; return 0; }
V
换根 dp 板子。
令 \(dp1_u\) 表示在 \(u\) 的子树内,黑节点与 \(u\) 组成连通块的方案数;\(dp2_u\) 则是在子树外。
对于 \(dp1_u\),显然有转移方程:
对于 \(dp2_u\),我们考虑将问题转化为求父节点 \(f\) 子树内的方案数,易得转移方程:
对于 \(\prod dp_v(v \in \text{brother}\ u)+1\) 预处理前 / 后缀积即可直接转移。
时间复杂度 \(O(n)\)。
#include<bits/stdc++.h> using namespace std; int n,MOD; vector<int> G[100031]; int dp1[100031],dp2[100031]; int pre[100031],lst[100031]; void dfs1(int x,int fa){ vector<int> son; dp1[x]=1; for(auto i:G[x]){ if(i==fa) continue; dfs1(i,x); dp1[x]=1ll*dp1[x]*(dp1[i]+1)%MOD; son.push_back(i); } int npre=1,nlst=1; for(int i=0;i<son.size();i++) pre[son[i]]=npre,npre=1ll*npre*(dp1[son[i]]+1)%MOD; for(int i=son.size()-1;i>=0;i--) lst[son[i]]=nlst,nlst=1ll*nlst*(dp1[son[i]]+1)%MOD; } void dfs2(int x,int fa){ if(fa==-1) dp2[x]=1; else dp2[x]=(1ll*dp2[fa]*(1ll*pre[x]*lst[x]%MOD)%MOD+1)%MOD; for(auto i:G[x]) if(i!=fa) dfs2(i,x); } signed main(){ cin>>n>>MOD; for(int i=1,u,v;i<n;i++) cin>>u>>v,G[u].push_back(v),G[v].push_back(u); dfs1(1,-1),dfs2(1,-1); for(int i=1;i<=n;i++) cout<<1ll*dp1[i]*dp2[i]%MOD<<'\n'; return 0; }
W
令 \(dp_{i,j}\) 表示前 \(i\) 个数中最后一个 \(1\) 在位置 \(j\) 时能获得的最大分数。
易得转移:
可以发现在转移过程中可能有同一个规则被重复使用,
于是我们考虑转移时对于每条规则将其相关联的转移一并操作。
因为规则影响的是连续区间,所以可以使用线段树维护,
具体地,我们每次令 \(dp_i\) 初始化为前 \(i-1\) 的最大 \(dp\) 值,
然后运用双指针思想枚举每个满足 \(r_j=i\) 的规则,转移即可。
每次转移均摊 \(O(1)\),总时间复杂度 \(O(n \log n)\)。
#include<bits/stdc++.h> #define int long long #define ll(p) (p<<1) #define rr(p) (p<<1|1) using namespace std; int n,m; struct interval{ int l,r,v; }a[200031]; struct Segment_tree{ int tag,val; }t[800031]; bool cmp(interval x,interval y){ return x.r<y.r; } void spd(int rt){ t[ll(rt)].val+=t[rt].tag,t[rr(rt)].val+=t[rt].tag; t[ll(rt)].tag+=t[rt].tag,t[rr(rt)].tag+=t[rt].tag,t[rt].tag=0; } void upd(int rt,int l,int r,int s,int e,int v){ if(l>=s&&r<=e){ t[rt].val+=v,t[rt].tag+=v; return; } spd(rt); int mid=(l+r)>>1; if(s<=mid) upd(ll(rt),l,mid,s,e,v); if(e>mid) upd(rr(rt),mid+1,r,s,e,v); t[rt].val=max(t[ll(rt)].val,t[rr(rt)].val); } signed main(){ cin>>n>>m; for(int i=1;i<=m;i++) cin>>a[i].l>>a[i].r>>a[i].v; sort(a+1,a+m+1,cmp); for(int i=1,j=1;i<=n;i++){ upd(1,1,n,i,i,max(0ll,t[1].val)); while(a[j].r==i&&j<=m) upd(1,1,n,a[j].l,a[j].r,a[j].v),j++; } cout<<max(0ll,t[1].val); return 0; }
X
一眼 01 背包。但是直接做会 T。
于是根据国王游戏那题想到对于每个箱子按照 \(s_i+w_i\) 从小到大排序,
然后就过了。
#include<bits/stdc++.h> #define int long long using namespace std; int n,ans=-1e9; int dp[20031]; struct node{ int w,s,v; }a[1031]; bool cmp(node x,node y){ return x.s+x.w<y.s+y.w; } signed main(){ cin>>n; for(int i=1;i<=n;i++) cin>>a[i].w>>a[i].s>>a[i].v; sort(a+1,a+n+1,cmp); memset(dp,-1,sizeof(dp)),dp[0]=0; for(int i=1;i<=n;i++) for(int j=a[i].s;j>=0;j--) dp[j+a[i].w]=max(dp[j+a[i].w],dp[j]+a[i].v); for(int i=0;i<=20000;i++) ans=max(ans,dp[i]); cout<<ans; return 0; }
Y
Z
耗时 2 个月,终于更完了 qwq。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】