题意:给出若干个操作和一个序列,初始序列全为 0,每个操作给定三个数 ( l , r , d ),表示给序列区间 [l,r] 内的所有值加上 d ;需要对于[1,n]内的每个整数 i,分别判断是否存在操作的子集,使得最后区间最大值为 i 。
解法1:考虑序列中单个位置多次操作的所有子集出现过的最大值如何统计,根据数据规模 n,q<=4000,可以用 bitset 对于每一次操作进行位移标记
bitset<4000>x;
x |= (x<<d);
对于整个序列中两个不同位置 x1,x2 ,所有 同时覆盖 和 同时不覆盖 两个位置的操作对于这两个位置的值影响是同步的,只有不同时覆盖这两个位置的操作才会使得它们开始产生分歧,显然,在产生分歧前两个位置(实际上是以两个位置为边界的区间)都可以用同一个 bitset 数组表示。
所以可以用线段树实现,先保存所有操作,用 bitset 记录当前状态,产生分歧分别向左右区间走就好了,复杂度为 O(nlogn*n/32)
#include<vector>
#include<bitset>
#include<cstdio>
#include<iostream>
using namespace std;
const int N = 1e4+10;
vector<int>add[N<<2]; //线段树一般开四倍
bitset<N>num;
void update(int d,int L,int R,int l,int r,int rt)
{
if(L<=l&&r<=R)
{
add[rt].push_back(d);
// 等价于平常线段树的Lazy数组, 因为不能直接累加,所以开个vector
return;
}
int mid=(l+r)>>1;
if(L<=mid) update(d,L,R,l,mid,rt<<1);
if(R>mid) update(d,L,R,mid+1,r,rt<<1|1);
}
void solve(bitset<N>s,int l,int r,int rt)
{
for(int i=0;i<add[rt].size();i++)
{
s|=(s<<add[rt][i]);
//这个式子的意义是:s所有位加上 d 又与原来的s整合起来
}
if(l==r)
{
num|=s; //l==r就是子集枚举完毕了,累加答案
return;
}
int mid=(l+r)>>1;
solve(s,l,mid,rt<<1);
solve(s,mid+1,r,rt<<1|1);
}
int main()
{
int n,q;
scanf("%d%d",&n,&q);
for(int i=1;i<=q;i++)
{
int l,r,d;
scanf("%d%d%d",&l,&r,&d);
update(d,l,r,1,n,1); //更新每次操作
}
bitset<N>s;
s[0]=1;
solve(s,1,n,1);
vector<int>ans;
for(int i=1;i<=n;i++)
{
if(num[i]) ans.push_back(i); //记录答案
}
printf("%d\n",(int)ans.size());
for(int i=0;i<ans.size();i++)
{
printf("%d",ans[i]);
if(i==ans.size()-1) puts("");
else putchar(' ');
}
}
解法2:把所有操作按右边界从小到大排序,对于排完序后的第 i 个操作的三元组 (L,R,D) ,思考这个操作能贡献 最大值 M 的条件:
- M>=D , 且 前 i-1 个操作可以贡献出值 M-D ,且值 M-D 出现的位置至少有一个必须处于区间 [L,R] 中
总结,即前 i-1 个操作贡献出值 M-D 出现的最右边的位置必须处于区间 [L,R] 中,显然这个位置不会超过 R (排序的作用) ,所以只要判断 位置 是否 >= L 即可,
我们设 dp[i][M] 表示前 i 个操作贡献值 M 出现的最右边的位置,显然,转移如下:
if(dp[i-1][M-d]>=L)
dp[i][M] = max(dp[i][M],dp[i-1][M-d]);
#include<bits/stdc++.h>
#define rep(i,a,b) for(int i=a;i<=b;i++)
#define rrep(i,a,b) for(int i=a;i>=b;i--)
using namespace std;
const int N = 1E4+10;
struct node {
int L,R,D;
}e[N];
int n,q,dp[N];
bool cmp(node a,node b) {
return a.R<b.R;
}
int main()
{
ios::sync_with_stdio(false);
cin>>n>>q;
rep(i,1,q) cin>>e[i].L>>e[i].R>>e[i].D;
sort(e+1,e+q+1,cmp);
dp[0] = n;
rep(i,1,q) {
int L = e[i].L , R = e[i].R , D = e[i].D;
rrep(M,n,D) {
if(dp[M-D]>=L)
dp[M] = max(dp[M],dp[M-D]);
}
dp[D] = R;
}
vector<int>ans;
rep(i,1,n) if(dp[i]) ans.push_back(i);
cout<<ans.size()<<endl;
for(auto v:ans) cout<<v<<" ";
}