CodeForces - 981E(Addition on Segments)

4 篇文章 0 订阅
4 篇文章 0 订阅

题意:给出若干个操作和一个序列,初始序列全为 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<<" ";
	
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值