[关闭]
@exut 2024-11-01T02:46:36.000000Z 字数 16895 阅读 202

AtCoder DP Contest

atcoder DP 刷题


题单链接

Frog 1

表示到 石头的最小费用

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int dp[N];
  5. int n;
  6. int h[N];
  7. int main(){
  8. cin>>n;
  9. for(int i=1;i<=n;i++) cin>>h[i];
  10. dp[1]=0,dp[2]=abs(h[2]-h[1]);
  11. for(int i=3;i<=n;i++){
  12. dp[i]=min(dp[i-1]+abs(h[i]-h[i-1]),dp[i-2]+abs(h[i-2]-h[i]));
  13. }
  14. cout<<dp[n];
  15. }

Frog 2

状态同上题

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e5+5;
  4. int dp[N];
  5. int n,k;
  6. int h[N];
  7. int main(){
  8. cin>>n>>k;
  9. for(int i=1;i<=n;i++) cin>>h[i],dp[i]=INT_MAX;
  10. dp[1]=0;
  11. for(int i=2;i<=n;i++){
  12. for(int j=max(1,i-k);j<=i;j++)
  13. dp[i]=min(dp[j]+abs(h[i]-h[j]),dp[i]);
  14. }
  15. cout<<dp[n];
  16. }

Knapsack 1

即01背包

还是从最最普通的背包开始,不优化

表示选到第 个物品,占了 的空间的最大价值

然后注意到这个 他皮用没有可以考虑压一压

考虑到第一维一直递增,第二维只跟比他小的有关,于是我们从大到小更新


枚举时从 枚举

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e5+5;
  5. int dp[N];
  6. int n,W;
  7. int w[N],v[N];
  8. signed main(){
  9. cin>>n>>W;
  10. for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
  11. for(int i=1;i<=n;i++){
  12. for(int j=W;j>=w[i];j--)
  13. dp[j]=max(dp[j],dp[j-w[i]]+v[i]);
  14. }
  15. cout<<dp[W];
  16. }

Grid 1

表示从 走到 的方案数

容易写出转移

如果当前点是#那就跳过去不转移

初始状态为设 或者 ,只设一个

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int mod=1e9+7;
  5. const int N=1e4+5;
  6. int dp[N][N];
  7. int n,m;
  8. char s[N][N];
  9. signed main(){
  10. cin>>n>>m;
  11. for(int i=1;i<=n;i++){
  12. for(int j=1;j<=m;j++){
  13. cin>>s[i][j];
  14. }
  15. }
  16. dp[0][1]=1;
  17. for(int i=1;i<=n;i++){
  18. for(int j=1;j<=m;j++){
  19. if(s[i][j]=='#') continue;
  20. dp[i][j]=(dp[i-1][j]+dp[i][j-1])%mod;
  21. }
  22. }
  23. cout<<dp[n][m];
  24. }

Vacation

翻译有误,是两天不是两天以上

表示 天进行 活动

方程易写出,懒得放了,见代码

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int mod=1e9+7;
  5. const int N=1e5+5;
  6. int dp[N][3];
  7. int n;
  8. int a[N],b[N],c[N];
  9. signed main(){
  10. cin>>n;
  11. for(int i=1;i<=n;i++){
  12. cin>>a[i]>>b[i]>>c[i];
  13. }
  14. for(int i=1;i<=n;i++){
  15. dp[i][0]=max(dp[i-1][1],dp[i-1][2])+a[i];
  16. dp[i][1]=max(dp[i-1][0],dp[i-1][2])+b[i];
  17. dp[i][2]=max(dp[i-1][0],dp[i-1][1])+c[i];
  18. }
  19. cout<<max(dp[n][0],max(dp[n][1],dp[n][2]));
  20. }

Knapsack 2

啊我们发现这个题意是个板子的01背包,但是我们发现复杂度不太合理

这个特别小的代价范围给了我们一个想法

表示到第 个物品为止,获得 的收益的最小重量

转移类似

依旧可以压成一维

,从大往小扫

结束后从大到小找出第一个重量合法的

复杂度 是收益和的上界

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e5+5;
  5. int dp[N];
  6. int n,W;
  7. int w[N],v[N];
  8. signed main(){
  9. cin>>n>>W;
  10. for(int i=1;i<=n;i++) cin>>w[i]>>v[i];
  11. for(int j=1;j<=100000;j++) dp[j]=INT_MAX;
  12. dp[0]=0;
  13. for(int i=1;i<=n;i++){
  14. for(int j=n*1000;j>=v[i];j--)
  15. dp[j]=min(dp[j],dp[j-v[i]]+w[i]);
  16. }
  17. for(int i=100000;i>=0;i--){
  18. if(dp[i]<=W){
  19. cout<<i;
  20. return 0;
  21. }
  22. }
  23. }

LCS

讨厌的字符串

首先不去考虑路径,考虑求出长度

表示到 为止的 LCS 长度

转移:


至于序列怎么求,记录每个位置是从哪里转移来的,从 反着跑回去就行辣

复杂度 ,分别为两个字符串的长度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e5+5;
  5. char s[3005],t[3005];
  6. int n,m;
  7. int dp[3005][3005];
  8. signed main(){
  9. cin>>(s+1);
  10. cin>>(t+1);
  11. n=strlen(s+1);
  12. m=strlen(t+1);
  13. for(int i=1;i<=n;i++){
  14. for(int j=1;j<=m;j++){
  15. if(s[i]==t[j])dp[i][j]=dp[i-1][j-1]+1;
  16. else dp[i][j]=max(dp[i][j-1],dp[i-1][j]);
  17. }
  18. }
  19. char ans[3005];
  20. int tt=0;
  21. for(int i=n,j=m;dp[i][j]>0;){
  22. if(s[i]==t[j]) ans[dp[i][j]]=s[i],i--,j--;
  23. else if(dp[i][j-1]>dp[i-1][j]) j--;
  24. else i--;
  25. }
  26. for(int i=1;i<=dp[n][m];i++)
  27. cout<<ans[i];
  28. }

Longest Path

实在是太愚蠢了,记忆化搜索即可

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e5+5;
  5. int dp[N];
  6. vector<int> to[N];
  7. int n,m;
  8. int ans;
  9. int dfs(int now){
  10. if(dp[now]!=-1){
  11. return dp[now];
  12. }
  13. for(int v:to[now]){
  14. dp[now]=max(dfs(v),dp[now]);
  15. }
  16. dp[now]++;
  17. return dp[now];
  18. }
  19. signed main(){
  20. cin>>n>>m;
  21. for(int i=1;i<=n;i++) dp[i]=-1;
  22. for(int i=1;i<=m;i++){
  23. int u,v;
  24. cin>>u>>v;
  25. to[u].push_back(v);
  26. }
  27. for(int i=1;i<=n;i++){
  28. ans=max(ans,dfs(i));
  29. }
  30. cout<<ans;
  31. }

Stones

一类博弈问题的基本思路

表示 状态是先手必胜 还是先手必败

是初必败态

而所有能指向必败态的都是必胜态

所以从小到大枚举,刷表法更新后面的是必胜还是必败

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=1e5+5;
  5. int dp[N];
  6. int n;
  7. int A[N];
  8. int k;
  9. signed main(){
  10. cin>>n>>k;
  11. for(int i=1;i<=n;i++){
  12. cin>>A[i];
  13. }
  14. dp[0]=0;
  15. for(int i=0;i<=k;i++){
  16. if(!dp[i]){
  17. for(int j=1;j<=n;j++){
  18. if(i+A[j]<=k)dp[i+A[j]]=1;
  19. }
  20. }
  21. }
  22. if(dp[k]){
  23. cout<<"First";
  24. }
  25. else cout<<"Second";
  26. }

Deque

草了,写线性dp写魔怔了半天没想出来这题

注意到任意时刻剩下的数一定是若干个连续的数,即原数组中的一个区间

尝试依据这个来设计状态

表示当前区间 ,能造成的最大差

这边需要注意的是,什么情况对这个差造成的是正贡献,什么时候是负贡献

那就必须要关注是谁正在操作

当前区间长度为 ,二总长度为 ,一次操作会换人并且让长度

于是当前的人是谁就可以用 表达,当这个数是偶数的时候是先手者操作,奇数就是后手者

区间dp的常见格式是先枚举长度再枚举起点,保持相同的写法习惯有利于调题目,更有利于养成习惯

转移方程式给出:

  1. if((n-len)&1) dp[l][r]=min(dp[l][r-1]-a[r],dp[l+1][r]-a[l]);
  2. else dp[l][r]=max(dp[l][r-1]+a[r],dp[l+1][r]+a[l]);

完整代码,复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=3e3+5;
  5. int dp[N][N];
  6. int n;
  7. int a[N];
  8. signed main(){
  9. cin>>n;
  10. for(int i=1;i<=n;i++){
  11. cin>>a[i];
  12. }
  13. for(int len=1;len<=n;len++){
  14. for(int l=1;n-l+1>=len;l++){
  15. int r=len+l-1;
  16. if((n-len)&1)
  17. dp[l][r]=min(dp[l][r-1]-a[r],dp[l+1][r]-a[l]);
  18. else dp[l][r]=max(dp[l][r-1]+a[r],dp[l+1][r]+a[l]);
  19. }
  20. }
  21. cout<<dp[1][n];
  22. }

Slimes

第一反应这不是贪心!合并果子

仔细秒一眼发现只能相邻

考虑区间

表示把 全部合并的最小代价

但是这道题跟上一题有一个不是很一样的地方,这题明显不能只枚举开头结尾,我们还需要枚举一个断点,考虑把断点左右两边合并起来

方程见代码吧,用前缀和优化一下

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=400+5;
  5. int dp[N][N];
  6. int n;
  7. int a[N];
  8. int sum[N];
  9. signed main(){
  10. cin>>n;
  11. for(int i=1;i<=n;i++){
  12. cin>>a[i];
  13. for(int j=i;j<=n;j++)dp[i][j]=LONG_LONG_MAX;
  14. sum[i]=sum[i-1]+a[i];
  15. }
  16. for(int i=1;i<=n;i++)dp[i][i]=0;
  17. for(int len=1;len<=n;len++){
  18. for(int l=1;n-l+1>=len;l++){
  19. int r=len+l-1;
  20. for(int k=l;k<=r;k++){
  21. dp[l][r]=min(dp[l][r],dp[l][k]+dp[k+1][r]+sum[r]-sum[l-1]);
  22. }
  23. }
  24. }
  25. cout<<dp[1][n];
  26. }

Coins

概率dp,第一眼感觉没有绿,顶多中位黄题

表示前 个数有 个正面的概率

注意精度

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=3000+5;
  5. double dp[N][N];
  6. double p[N];
  7. int n;
  8. signed main(){
  9. cin>>n;
  10. for(int i=1;i<=n;i++){
  11. cin>>p[i];
  12. }
  13. dp[0][0]=1;
  14. for(int i=1;i<=n;i++){
  15. dp[i][0]=dp[i-1][0]*(1-p[i]);
  16. for(int k=1;k<=i;k++){
  17. dp[i][k]=dp[i-1][k-1]*p[i]+dp[i-1][k]*(1-p[i]);
  18. }
  19. }
  20. double ans=0;
  21. for(int i=n/2+1;i<=n;i++){
  22. ans+=dp[n][i];
  23. }
  24. printf("%.10lf",ans);
  25. }

Sushi

表示剩 盘子, 盘子, 盘子的期望

考虑单步期望:

  1. 个寿司,期望为

  2. 个,期望为

  3. 个,期望为

  4. 个,期望为

综上可得转移为

移项合并

然后注意一下拓扑序,发现要先枚举 然后 然后

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. typedef double db;
  4. const int N=305;
  5. int n;
  6. db f[N][N][N];
  7. int main(){
  8. ios::sync_with_stdio(0);
  9. cin.tie(0),cout.tie(0);
  10. cin>>n;
  11. int c1=0,c2=0,c3=0,a;
  12. for(int i=1;i<=n;++i){
  13. cin>>a;
  14. if(a==1) ++c1;
  15. if(a==2) ++c2;
  16. if(a==3) ++c3;
  17. }
  18. for(int k=0;k<=n;++k){
  19. for(int j=0;j<=n;++j){
  20. for(int i=0;i<=n;++i){
  21. if(!i&&!j&&!k) continue;
  22. if(i) f[i][j][k]+=i*f[i-1][j][k];
  23. if(j) f[i][j][k]+=j*f[i+1][j-1][k];
  24. if(k) f[i][j][k]+=k*f[i][j+1][k-1];
  25. f[i][j][k]=(f[i][j][k]+n)/(db)(i+j+k);
  26. }
  27. }
  28. }
  29. printf("%.10lf",f[c1][c2][c3]);
  30. return 0;
  31. }

Candies

表示到 个人,分掉了 个糖的方案数

好唐的题

显然

于是我们获得了一个 的优秀做法(并非优秀)

然后我们容易发现右边那依托东西看起来就可以前缀和搞一下,然后我们发现突然做完了

,顺手优化了一下空间

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. const int mod=1e9+7;
  5. int dp[100005];
  6. int n,K;
  7. int a[105];
  8. int s[100005];
  9. signed main(){
  10. cin>>n>>K;
  11. for(int i=1;i<=n;i++){
  12. cin>>a[i];
  13. }
  14. dp[0]=1;
  15. s[0]=dp[0];
  16. for(int j=1;j<=K;j++) s[j]=s[j-1]+dp[j],s[j]%=mod;
  17. for(int i=1;i<=n;i++){
  18. for(int j=0;j<=K;j++){
  19. int t=max(0ll,j-a[i]);
  20. dp[j]=s[j];
  21. if(t!=0) dp[j]=(dp[j]-s[t-1]+mod)%mod;
  22. }
  23. s[0]=dp[0];
  24. for(int j=1;j<=K;j++) s[j]=s[j-1]+dp[j],s[j]%=mod;
  25. }
  26. cout<<dp[K];
  27. }

Independent Set

考虑设状态 其中

表示 , 自己是不是黑点的方案数

  1. #include<bits/stdc++.h>
  2. #pragma GCC optimize(2)
  3. using namespace std;
  4. #define int long long
  5. const int mod=1e9+7;
  6. int n;
  7. vector<int> to[200005];
  8. int D[200005][2];
  9. int dp(int now,bool is_black,int fa){
  10. int ans=1;
  11. if(D[now][is_black]) return D[now][is_black];
  12. if(is_black){
  13. for(int v:to[now]){
  14. if(v!=fa){
  15. ans=ans*dp(v,0,now);
  16. ans%=mod;
  17. }
  18. }
  19. }
  20. else{
  21. for(int v:to[now]){
  22. if(v!=fa){
  23. ans=ans*((dp(v,1,now)+dp(v,0,now))%mod);
  24. ans%=mod;
  25. }
  26. }
  27. }
  28. return (D[now][is_black]=(ans%mod));
  29. }
  30. signed main(){
  31. cin>>n;
  32. for(int i=1;i<n;i++){
  33. int u,v;
  34. cin>>u>>v;
  35. to[u].push_back(v);
  36. to[v].push_back(u);
  37. }
  38. cout<<(dp(1,0,0)+dp(1,1,0))%mod;
  39. }

Flowers

发现是一个加权的LIS问题,考虑dp

表示以 结尾的最大答案(强制 必须选)

我们以 为下标开一个树状数组就可以了,实质上是维护了一个二维偏序

  1. #include<bits/stdc++.h>
  2. using namespace std;
  3. #define int long long
  4. const int mod=1e9+7;
  5. int dp[200005];
  6. int n,K;
  7. int a[200005];
  8. int h[200005];
  9. int tr[200005];
  10. void add(int i,int x){
  11. for(;i<=200000;i+=i&(-i)){
  12. tr[i]=max(tr[i],x);
  13. }
  14. }
  15. int Max(int i,int res=0){
  16. for(;i;i-=i&(-i)){
  17. res=max(res,tr[i]);
  18. }
  19. return res;
  20. }
  21. signed main(){
  22. cin>>n;
  23. for(int i=1;i<=n;i++){
  24. cin>>h[i];
  25. }
  26. for(int i=1;i<=n;i++){
  27. cin>>a[i];
  28. }
  29. int ans=0;
  30. for(int i=1;i<=n;i++){
  31. dp[i]=Max(h[i]-1)+a[i];
  32. add(h[i],dp[i]);
  33. ans=max(ans,dp[i]);
  34. }
  35. cout<<ans;
  36. }

Walk

表示 的长度为 的路径条数

容易发现 就是初始图

显然有

就是,你集中一下注意力,你发现这玩意跟矩阵乘法形式一样

于是上一个矩阵快速幂即可

Matching

搞一下左半点枚举

然后不可能再跑右边点了,那就 已经死掉了

考虑状压, 表示前 个左点已经和 集合中的点完全匹配的方案数

容易发现 , 指去掉 ,就是枚举新的这个左点跟谁匹配

复杂度 ,换一下循环顺序卡卡常就可以

  1. #include<bits/stdc++.h>
  2. #pragma GCC optimize(2)
  3. using namespace std;
  4. #define rint register int
  5. #define int long long
  6. const int mod=1e9+7;
  7. int n;
  8. int dp[23][2097154];
  9. int a[23][23];
  10. int to[23][23];
  11. int len[23];
  12. signed main(){
  13. ios::sync_with_stdio(0);
  14. cin.tie(0),cout.tie(0);
  15. cin>>n;
  16. for(rint i=1;i<=n;i++){
  17. for(int j=1;j<=n;j++){
  18. cin>>a[i][j];
  19. if(a[i][j])to[i][++len[i]]=j;
  20. }
  21. }
  22. dp[0][0]=1;
  23. for(rint t=1;t<=n;t++){
  24. for(rint k=1;k<=len[t];k++){
  25. for(rint j=0;j<(1<<n);j++){
  26. int w=to[t][k];
  27. if(j>>(w-1)){
  28. dp[t][j]=(dp[t][j]+dp[t-1][j-(1<<(w-1))])%mod;
  29. }
  30. }
  31. }
  32. }
  33. cout<<dp[n][(1<<n)-1];
  34. }

Digit Sum

简单数位dp, 表示到哪一位,总和(),是否贴着上限

记忆化即可

复杂度不会算

  1. #include<bits/stdc++.h>
  2. #pragma GCC optimize(2)
  3. using namespace std;
  4. #define rint register int
  5. #define int long long
  6. const int mod=1e9+7;
  7. int D;
  8. int f[105][105][2];
  9. int word[100005],cnt;
  10. int dfs(int now,int sum,bool flg){
  11. if(now<=0){
  12. return sum==0;
  13. }
  14. if(f[now][sum][flg]!=-1) return f[now][sum][flg];
  15. int up=flg?word[now]:9;
  16. int wc=0;
  17. for(int i=0;i<=up;i++){
  18. wc+=dfs(now-1,(sum+i)%D,(i==up) and flg);
  19. wc%=mod;
  20. }
  21. f[now][sum][flg]=wc;
  22. return wc;
  23. }
  24. string s;
  25. signed main(){
  26. ios::sync_with_stdio(0);
  27. cin.tie(0),cout.tie(0);
  28. cin>>s;
  29. for(int i=0;i<s.length();i++) {
  30. word[++cnt]=s[s.length()-i-1]-'0';
  31. }
  32. cin>>D;
  33. memset(f,-1,sizeof f);
  34. cout<<dfs(cnt,0,1)-1;
  35. }

Permutation

表示到 位,上一位放 的方案数

显然这样信息少了,不能这样

表示在前 位放 排列, 位置填 的方案数

那么加入加入一个新的 只要把原来比 大的全部 做一个映射就变成一个 的排列了

如果前面是 ,

如果前面是 ,

然后我们可以得到一个 的dp

前缀和优化一下就可以了

复杂度 ,空间可以压到 但是我比较懒

  1. #include<bits/stdc++.h>
  2. #pragma GCC optimize(2)
  3. using namespace std;
  4. #define rint register int
  5. #define int long long
  6. const int mod=1e9+7;
  7. int dp[3005][3005];
  8. int n;
  9. string s;
  10. int S[3005];
  11. signed main(){
  12. cin>>n>>s;
  13. dp[1][1]=1;
  14. for(int i=2;i<=n;i++){
  15. for(int j=1;j<=n;j++) S[j]=S[j-1]+dp[i-1][j],S[j]%=mod;
  16. for(int j=1;j<=i;j++){
  17. if(s[i-2]=='<'){
  18. dp[i][j]=S[j-1];
  19. }
  20. else{
  21. dp[i][j]=(S[i-1]-S[j-1]+mod)%mod;
  22. }
  23. }
  24. }
  25. int ans=0;
  26. for(int i=1;i<=n;i++){
  27. ans+=dp[n][i];
  28. ans%=mod;
  29. }
  30. cout<<ans;
  31. }

Grouping

表示对 集合进行划分的最大答案

据说复杂度

  1. #include<bits/stdc++.h>
  2. #pragma GCC optimize(2)
  3. using namespace std;
  4. #define rint register int
  5. #define int long long
  6. const int mod=1e9+7;
  7. int n;
  8. int a[17][17];
  9. int dp[65537];
  10. int C[65537];
  11. int dfs(int now){
  12. if(!now) return 0;
  13. if(dp[now]!=-1) return dp[now];
  14. for(int i=now;i;i=(i-1)&now){//一种枚举子集的写法
  15. dp[now]=max(dp[now],C[i]+dfs(now-i));
  16. }
  17. return dp[now];
  18. }
  19. signed main(){
  20. cin>>n;
  21. for(int i=1;i<=n;i++){
  22. for(int j=1;j<=n;j++){
  23. cin>>a[i][j];
  24. }
  25. }
  26. for(int s=1;s<(1<<n);s++){
  27. dp[s]=-1;
  28. for(int i=1;i<=n;i++){
  29. if((s>>(i-1))&1){
  30. for(int j=i+1;j<=n;j++){
  31. if((s>>(j-1))&1){
  32. C[s]+=a[i][j];
  33. }
  34. }
  35. }
  36. }
  37. }
  38. cout<<dfs((1<<n)-1);
  39. }

Subtree

一点点考虑

假设只以某一个点为根的答案

表示在 的子树内,强制必须染 后联通的方案数

容易推出

此外我们还需要维护一个 表示 的子树外,强制染 并且联通的方案数,这样答案就是

但我们都知道子树外的信息不是很好维护,于是我们发现如果一个点没有子树外部分就好了

于是考虑换根dp

考虑将根从 换到 的影响

然后

结合上面两个式子我们可以得出

好像做完了,但是有个小小的问题就是

模数不一定是质数

那这个除法就是个大问题了

我们注意到答案的统计其实是乘积形式,那除法就可以理解成直接不加上某一部分

维护前缀后缀积即可

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. int n,mod,cnt;
  5. int dp[100005],pre[400005],sub[400005];
  6. vector<int> e[100005];
  7. void dfs1(int x,int fa){
  8. dp[x]=1;
  9. for(int y:e[x]){
  10. if(y==fa)continue;
  11. dfs1(y,x);
  12. dp[x]=(dp[x]*(dp[y]+1))%mod;
  13. }
  14. }
  15. void dfs(int x,int fa,int las){
  16. dp[x]=dp[x]*(las+1)%mod;
  17. int st=++cnt,en,o=0;
  18. cnt=cnt+e[x].size()+1;
  19. pre[st-1]=1,sub[cnt-1]=1;
  20. en=cnt-2;
  21. for(int i=0;i<e[x].size();i++){
  22. int y=e[x][i];
  23. if(y==fa)pre[st+o]=pre[st+o-1]*(las+1)%mod;
  24. else pre[st+o]=pre[st+o-1]*(dp[y]+1)%mod;
  25. o++;
  26. }
  27. o=0;
  28. for(int i=e[x].size()-1;~i;--i){
  29. int y=e[x][i];
  30. if(y==fa)sub[en-o]=sub[en-o+1]*(las+1)%mod;
  31. else sub[en-o]=sub[en-o+1]*(dp[y]+1)%mod;
  32. o++;
  33. }
  34. for(int i=0;i<e[x].size();i++){
  35. int y=e[x][i];
  36. if(y==fa)continue;
  37. dfs(y,x,(pre[st+i-1]*sub[st+i+1])%mod);
  38. }
  39. }
  40. signed main(){
  41. cin>>n>>mod;
  42. for(int i=1,u,v;i<n;i++){
  43. cin>>u>>v;
  44. e[u].emplace_back(v);
  45. e[v].emplace_back(u);
  46. }
  47. dfs1(1,0);
  48. dfs(1,0,0);
  49. for(int i=1;i<=n;i++){
  50. cout<<dp[i]<<'\n';
  51. }
  52. }

Tower

数据范围有点大,直接dp没有什么前途,拓扑序不确定需要跑 次背包很炸裂就

考虑先贪心一下,是一个很典的exchange arguements类型的贪心,也许拓扑序能够定下来呢

我们考虑相邻两个物品

下面则在 上方能堆 ,反之为 的物品

那么当 时, 应该被放在下面,按这样排序

然后拓扑序就正常了,可以跑一遍01背包就行了

复杂度 ,大概吧

  1. #include <bits/stdc++.h>
  2. using namespace std;
  3. const int N=1e3+5,W=2e4+5;
  4. int n;
  5. long long dp[N][W];
  6. struct node{
  7. int w,s;
  8. long long v;
  9. }a[N];
  10. bool cmp(node x,node y){
  11. return x.s+x.w<y.s+y.w;
  12. }
  13. int main() {
  14. ios::sync_with_stdio(false);
  15. cin.tie(0);
  16. cin>>n;
  17. for(int i=1;i<=n;i++){
  18. cin>>a[i].w>>a[i].s>>a[i].v;
  19. }
  20. sort(a+1,a+n+1,cmp);
  21. memset(dp,-0x3f,sizeof(dp));
  22. dp[0][0]=0;
  23. for(int i=1;i<=n;i++){
  24. for(int j=0;j<W;j++){
  25. dp[i][j]=dp[i-1][j];
  26. if(j>=a[i].w and j-a[i].w<=a[i].s){
  27. dp[i][j]=max(dp[i][j],dp[i-1][j-a[i].w]+a[i].v);
  28. }
  29. }
  30. }
  31. long long ans=0;
  32. for(int i=0;i<W;i++){
  33. ans=max(ans,dp[n][i]);
  34. }
  35. cout<<ans<<'\n';
  36. return 0;
  37. }

Grid 2

都不用想暴力dp,空间爆炸时间爆炸没有什么优化空间

看到 的范围很健康,考虑关于关键点设计dp

表示到第 个关键点,不经过任何障碍 (除了当前点自己)的方案数

然后考虑容斥,

然后我们把 设为 号关键点

复杂度

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int mod=1e9+7;
  5. struct exut{int x,y;}a[3005];
  6. bool cmp(exut x,exut y){
  7. return x.x==y.x?x.y<y.y:x.x<y.x;
  8. }
  9. int H,W,n;
  10. int dp[3005];
  11. int inv[300005];
  12. int jc[300005];
  13. int pinv[300005];
  14. int C(int x,int y){
  15. return (jc[x]*pinv[x-y]%mod)*pinv[y]%mod;
  16. }
  17. signed main(){
  18. cin>>H>>W>>n;
  19. inv[1]=pinv[1]=jc[1]=1;
  20. jc[0]=1,pinv[0]=1;
  21. for(int i=2;i<=H+W;i++){
  22. jc[i]=jc[i-1]*i%mod;
  23. inv[i]=(mod-mod/i)*inv[mod%i]%mod;
  24. pinv[i]=pinv[i-1]*inv[i]%mod;
  25. }
  26. for(int i=1;i<=n;i++){
  27. cin>>a[i].x>>a[i].y;
  28. }
  29. a[++n]={H,W};
  30. sort(a+1,a+n+1,cmp);
  31. for(int i=1;i<=n;i++){
  32. dp[i]=C(a[i].x+a[i].y-2,a[i].x-1);
  33. for(int j=1;j<i;j++){
  34. if(a[i].x>=a[j].x and a[i].y>=a[j].y)
  35. dp[i]=(dp[i]+mod-dp[j]*C(a[i].x-a[j].x+a[i].y-a[j].y,a[i].x-a[j].x)%mod)%mod;
  36. }
  37. }
  38. cout<<dp[n];
  39. }

Intervals

都说是套路....

但是不会一点啊

套路貌似是把区间看成点,在右端点考虑答案,这样子是补充不漏的

考虑状态 表示处理完 位置,上一个 的最大答案

单独考虑一个特殊的情况

其次有

这样做的时空都不是很健康,双 的复杂度不可能过这道题

空间是简单的,只需要滚一下立马就好了

时间上,我们注意一下当 右移一位的时候发生了什么

你细品,其实就是一个区间加一个区间最值,直接上线段树维护吧

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. const int N=2e5+5;
  5. const int inf=1e17;
  6. int tr[N*4],tag[N*4];
  7. #define ls(x) (x<<1)
  8. #define rs(x) (x<<1|1)
  9. #define mid (l+r>>1)
  10. #define pushup(x) tr[x]=max(tr[ls(x)],tr[rs(x)])
  11. struct node{
  12. int l,v;
  13. };
  14. vector<node> V[N];
  15. void pushdown(int rt,int l,int r){
  16. if(!tag[rt]) return ;
  17. int k=tag[rt];tag[rt]=0;
  18. tr[ls(rt)]+=k,tr[rs(rt)]+=k;
  19. tag[ls(rt)]+=k,tag[rs(rt)]+=k;
  20. }
  21. void modi(int rt,int l,int r,int L,int R,int k){
  22. if(L<=l and r<=R){tr[rt]+=k,tag[rt]+=k;return;}
  23. if(L>r or l>R) return;
  24. pushdown(rt,l,r);
  25. modi(ls(rt),l,mid,L,R,k);
  26. modi(rs(rt),mid+1,r,L,R,k);
  27. pushup(rt);
  28. }
  29. int query(){
  30. return max(tr[1],0ll);
  31. }
  32. int n,m;
  33. signed main(){
  34. cin>>n>>m;
  35. while(m--){
  36. int l,r,w;
  37. cin>>l>>r>>w;
  38. V[r].push_back({l,w});
  39. }
  40. for(int i=1;i<=n;i++){
  41. modi(1,1,n,i,i,query());
  42. for(node v:V[i]){
  43. modi(1,1,n,v.l,i,v.v);
  44. }
  45. }
  46. cout<<query();
  47. }

Frog 3

状态显然,转移显然

然后先摘掉 ,化一下式子

这已经是一次函数形式了,李超树维护一下就行了

  1. #include<bits/stdc++.h>
  2. #define int long long
  3. using namespace std;
  4. typedef double db;
  5. const int N=2e5+5;
  6. const db eps=1e-9;
  7. db K[N],B[N];
  8. db F(int now,int x){//哪一条线段在x处的值
  9. return K[now]*x+B[now];
  10. }
  11. int tr[1000005*4];//线段树每个结点存的是哪条线段(编号)
  12. #define ls (rt<<1)
  13. #define rs (rt<<1|1)
  14. #define mid (l+r>>1)
  15. void ins(int rt,int l,int r,int p){//插入编号为p
  16. if(F(tr[rt],l)>F(p,l) and F(tr[rt],r)>F(p,r)) {tr[rt]=p;return;}
  17. if(F(tr[rt],l)<=F(p,l) and F(tr[rt],r)<=F(p,r)) {return;}
  18. if(F(tr[rt],mid)<=F(p,mid)){
  19. ins(ls,l,mid,p);
  20. ins(rs,mid+1,r,p);
  21. }
  22. else{
  23. ins(ls,l,mid,tr[rt]);
  24. ins(rs,mid+1,r,tr[rt]);
  25. tr[rt]=p;
  26. }
  27. }
  28. db query(int rt,int l,int r,int d){
  29. if(l==r) return F(tr[rt],d);
  30. if(d<=mid) return min(F(tr[rt],d),query(ls,l,mid,d));
  31. else return min(F(tr[rt],d),query(rs,mid+1,r,d));
  32. }
  33. int dp[N];
  34. int n,C,h[N];
  35. signed main(){
  36. // ios::sync_with_stdio(0);
  37. // cin.tie(0),cout.tie(0);
  38. cin>>n>>C;
  39. for(int i=1;i<=n;i++) cin>>h[i];
  40. dp[1]=0;
  41. K[1]=-2*h[1],B[1]=h[1]*h[1];
  42. ins(1,1,1000000,1);
  43. for(int i=2;i<=n;i++){
  44. dp[i]=query(1,1,1000000,h[i])+h[i]*h[i]+C;
  45. K[i]=-2*h[i],B[i]=dp[i]+h[i]*h[i];
  46. ins(1,1,1000000,i);
  47. }
  48. cout<<dp[n];
  49. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注