問題文
すぬけくんは鉄道会社を運営するゲームで遊ぶことにしました。すぬけ鉄道には M+1 個の駅があり、 0 から M までの番号がついています。 すぬけ鉄道の列車は駅 0 から d 駅ごとに停車します。 例えば d=3 のとき駅 0,駅 3,駅 6,駅 9, … に停車します。
すぬけ鉄道が走っている地域には N 種類の名産品があり、種類 i の名産品は 駅 li,駅 li+1,駅 li+2, …, 駅 ri のいずれかに列車が停車したとき購入することが可能です。
列車が停車する間隔 d は 1,2,3,…,M の M 種類が存在しています。 M 種類の列車それぞれについて、その列車に駅 0 で乗車した場合に購入可能な名産品の種類数を求めなさい。 なお、列車から別の列車への乗り換えは許されないものとします。
中文翻译
有m+1个车站0~m,有n种商品,第i种只在[li,ri]出售,编号为i的列车只会在i的倍数的站停车,求对于编号1-m的列车,你能在站台上买到几种商品。
解题思路
暴力枚举很容易想到,但是肯定T。
因为每个商品都可以在一个区间内得到,所以我们可以想到,对于每个区间都加上1,那么每个点的值就是答案。
但是这样有问题,有可能一种商品被选了多次。
对于每个大于等于d的区间,其中一定有一个数\(mod \ d\)答案为0,而我们前面所想到的正是这种问题,这样,我们从小到大枚举i,那么显然区间长度大于等于i的一定能被选到,我们可以直接在答案中加上。小于d的区间,我们可以使用线段树或者树状数组维护,每次计算完i的答案后,将长度为i的区间进行维护。
手贱,写的线段树。
顺便吐槽一句评测的数据有锅 @C_K_Y_
代码
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<vector>
using namespace std;
const int maxn=300050;
int sum[maxn<<3],lazy[maxn<<3],l[maxn],r[maxn];
int n,M;
vector<int>q[maxn];
inline void push_down(int ID,int l,int r){
int m=(l+r)>>1;
sum[ID<<1]+=(m-l+1)*lazy[ID],sum[ID<<1|1]+=(r-m)*lazy[ID];
lazy[ID<<1]+=lazy[ID],lazy[ID<<1|1]+=lazy[ID],lazy[ID]=0;
}
inline void ins(int ID,int l,int r,int L,int R){
if(lazy[ID])push_down(ID,l,r);
if(l>=L&&r<=R){
sum[ID]+=(r-l+1);
lazy[ID]++;
return;
}
int m=(l+r)>>1;
if(m>=L)ins(ID<<1,l,m,L,R);
if(m<R)ins(ID<<1|1,m+1,r,L,R);
sum[ID]=sum[ID<<1]+sum[ID<<1|1];
}
inline int query(int ID,int x,int l,int r){
if(lazy[ID])push_down(ID,l,r);
if(sum[ID]==0)return 0;
if(l==r)return sum[ID];
int m=(l+r)>>1;
if(m>=x)return query(ID<<1,x,l,m);
else return query(ID<<1|1,x,m+1,r);
}
int main(){
scanf("%d%d",&n,&M);
for(register int i=1;i<=n;i++){
scanf("%d%d",&l[i],&r[i]);
q[r[i]-l[i]+1].push_back(i);
}
int remain=n;
for(register int i=1;i<=M;i++){
int ans=remain;
remain-=q[i].size();
if(i!=1)
for(register int j=1;j*i<=M;j++)ans+=query(1,i*j,1,300000);
for(register int j=0;j<q[i].size();j++)ins(1,1,300000,l[q[i][j]],r[q[i][j]]);
printf("%d\n",ans);
}
}