两道题都很好
Intervals
给定 \(m\) 条规则形如 \((l_i,r_i,a_i)\),对于一个 01 串,其分数的定义是:对于第 \(i\) 条规则,若该串在 \([l_i,r_i]\) 中至少有一个 1,则该串的分数增加 \(a_i\)
你需要求出长度为 \(n\) 的 01 串中的最大分数
\(1\le n,m\le 2\times 10^5,|a_i|\le 10^9\)
设计 \(f_{i,j}\) 表示考虑到第 \(i\) 个数,上一个 \(1\) 的位置在 \(j\) 得到情况
这里考虑一个经典 trick,为了避免区间重复贡献,钦定每个区间在右端点处取得贡献(由于区间在范围内选取,这必定不会影响最终的贡献值)
因此,考虑一个 \(f_{i,j}(i\neq j)\),当其从 \(f_{i-1,j}\) 转移过来的时候,由于 \(1\) 的状态不变,增加的只是右端点为 \(i\) 的贡献,此外还要要求在该区间内含有 \(1\),最优化地想,也就是最近的一个 \(1\) 包含在区间内,转移方程为 \(f_{i,j}=f_{i-1,j}+\sum\limits_{r_k=i\operatorname{and}l_k\le j}a_k\)
考虑 \(f_{i,i}\),由于我们在 \(i\) 新放了一个 \(1\),因此这个状态可以由任意一个 \(j\lt i\) 的 \(f_{i-1,j}\) 转移得到,额外的区间贡献计算方式不变,转移方程为 \(f_{i,j}=(\max\limits_{j}f_{i-1,j})+\sum\limits_{r_k=i\operatorname{and}l_k\le j}a_k\)
滚动数组优化的暴力
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int f[2][1000001];
struct gz{int l,r,a;
}a[1000001];
int ans=-0x7fffffffffffffff;
signed main(){cin>>n>>m;for(int i=1;i<=m;++i){cin>>a[i].l>>a[i].r>>a[i].a;}for(int i=1;i<=n;++i){for(int j=1;j<=i-1;++j){f[i&1][j]=f[(i-1)&1][j];for(int k=1;k<=m;++k){if(a[k].r==i and a[k].l<=j){f[i&1][j]+=a[k].a;}}}for(int j=1;j<=i-1;++j){f[i&1][i]=max(f[i&1][i],f[(i-1)&1][j]);}for(int j=1;j<=m;++j){if(a[j].r==i and a[j].l<=i){f[i&1][i]+=a[j].a;}}}for(int i=0;i<=n;++i){ans=max(ans,f[n&1][i]);}cout<<ans;
}
事实上也可以优化到一维,优化之后少了从 \(f_{i-1,j}\) 到 \(f_{i,j}\) 的转移,更简洁了
优化到一维的暴力
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m;
int f[1000001];
struct gz{int l,r,a;
}a[1000001];
int ans=0;
signed main(){cin>>n>>m;if(n>=5000) return 0;for(int i=1;i<=m;++i){cin>>a[i].l>>a[i].r>>a[i].a;}memset(f,-0x3f,sizeof f);f[0]=0;for(int i=1;i<=n;++i){for(int j=0;j<=i-1;++j){int t=f[j];for(int k=1;k<=m;++k){if(j<a[k].l and a[k].l<=i and a[k].r>=i){t+=a[k].a;}}f[i]=max(f[i],t);}ans=max(ans,f[i]);}cout<<ans;
}
考虑我们的转移式 \(\sum\limits_{r_k=i\operatorname{and}l_k\le j}a_k\),可以发现这个转移式修改的位置是连续的,满足 \(j\in[l_k,i]\) 的 \(j\) 位置均可被这个 \(k\) 修改更新
由于我们压成一维之后已经把从 \(f_{i-1,j}\) 到 \(f_{i,j}\) 的转移省了,因此我们只需要考虑求那个形如 \(\max\) 的式子
区间修改,区间查最值,可以将 DP 数组放在线段树上处理
值得注意的是这个区间查最值的时候,由于 \(i\) 后面的值尚未更新,实际上约等于全局查最值,所以你其实根本没必要写一个查最值的函数
注意为了避免负贡献成最优解,查询的时候要对 \(0\) 取 \(\max\)
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n,m,ans;
struct qj{int l,r,a;
};
vector<qj>a[400001];
namespace stree{struct tree{int maxn;int lazy;}t[400001*4];#define tol (id*2)#define tor (id*2+1)void pushdown(int id){if(t[id].lazy){t[tol].maxn+=t[id].lazy;t[tol].lazy+=t[id].lazy;t[tor].maxn+=t[id].lazy;t[tor].lazy+=t[id].lazy;t[id].lazy=0;}}void change(int id,int l,int r,int L,int R,int val){if(L<=l and r<=R){t[id].maxn+=val;t[id].lazy+=val;return;}int mid=(l+r)/2;pushdown(id);if(R<=mid) change(tol,l,mid,L,R,val);else if(L>=mid+1) change(tor,mid+1,r,L,R,val);else{change(tol,l,mid,L,mid,val);change(tor,mid+1,r,mid+1,R,val);}t[id].maxn=max(t[tol].maxn,t[tor].maxn);}int ask(){return max(0ll,t[1].maxn);}
}
signed main(){ios::sync_with_stdio(false);cin>>n>>m;for(int i=1;i<=m;i++){int il,ir,ia;cin>>il>>ir>>ia;a[ir].push_back({il,ir,ia});}for(int i=1;i<=n;++i){stree::change(1,1,n,i,i,stree::ask());for(qj j:a[i]){stree::change(1,1,n,j.l,i,j.a);}}cout<<stree::ask();
}
Tower
直接买一赠一了吧
每个物品有 \(w,s,v\),表示重量,最大承重,价值,把物品堆在一起,要求每个物品上面堆着物品的总重量不超过这个物品的最大承重,问最大价值
\(n\le 10^3,w_i,s_i\le 10^4\)
这个题显然比上一个题水
首先看出这似乎是个背包,但是正常做背包显然做不出来(有后效性,先遍历过的物品有可能后选比较优),如果能找到某些性质使得先遍历的物品一定比后遍历的物品先选更优,那么这道题就能转化成背包问题
考虑两个物品 \(i,j\),设 \(i\) 放在 \(j\) 上面,那么 \(j\) 上面还能放的物品总质量为 \(s_j-w_i\),同理,如果 \(j\) 在上面,这个值为 \(s_i-w_j\)
由于 \(i,j\) 如何放,总重量都是一样的,对下面没有影响,因此 \(i\) 放在 \(j\) 上面更优,当且仅当 \(s_j-w_i\gt s_i-w_j\)
移项,\(s_i+w_i\lt s_j+w_j\),以此为比较依据直接背包即可
#include<bits/stdc++.h>
using namespace std;
#define int long long
int n;
struct tower{int w,s,v;bool operator <(const tower&a)const{return w+s<a.w+a.s;}
}a[10001];
int f[20001];
int ans=0;
signed main(){ios::sync_with_stdio(false);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);for(int i=1;i<=n;++i){for(int j=a[i].s;j>=0;--j){f[j+a[i].w]=max(f[j+a[i].w],f[j]+a[i].v);}}cout<<ans;
}