2023NOIP A层联测30-草莓列车

给定一个序列 { a n } \{a_n\} {an},有 m m m 次操作,形如 l r v,表示将 [ l , r ] [l,r] [l,r] 的每个 a i a_i ai 变为 max ⁡ ( a i , v ) \max(a_i,v) max(ai,v)。求最终的序列。

n ≤ 1 0 5 , m ≤ 1 0 7 n\le10^5,m\le10^7 n105,m107


ST表

考虑构建 ST 表的反过程,每次操作就给 st[l][x]st[r-(1< v v v 更新,其中 x = ⌊ log ⁡ 2 ( r − l + 1 ) ⌋ x=\lfloor\log_2(r-l+1)\rfloor x=log2(rl+1)⌋。注意到如果 s t [ i ] [ j ] st[i][j] st[i][j] 有值,说明 [ l , l + 2 j ) [l,l+2^j) [l,l+2j) 被更新过。

然后对于区间 [ i , i + 2 j + 1 ) [i,i+2^{j+1}) [i,i+2j+1),把区间分成两部分 [ i , i + 2 j ) [i,i+2^{j}) [i,i+2j) [ i + 2 j , i + 2 j + 1 ) [i+2^j,i+2^{j+1}) [i+2j,i+2j+1),把 st[i][j+1] 往下更新即可。

时间复杂度 O ( n log ⁡ n + m ) O(n\log n+m) O(nlogn+m)

#include
using namespace std;
const int N=1e5+10;
int n,m,typ;
unsigned int a[N],bj[N],tr[N][18],lg[N];
namespace Maker{
	unsigned int x0,seed;
	void init() {scanf("%u%u",&x0,&seed);}
	inline unsigned int getnum(){
		x0=(x0<<3)^x0;
		x0=((x0>>5)+seed)^x0;
		return x0;
	}
}
void update(int l,int r,unsigned int val)
{  
    int x=lg[r-l+1];
    tr[l][x]=max(tr[l][x],val);
    tr[r-(1<<x)+1][x]=max(tr[r-(1<<x)+1][x],val);
}
int main()
{
    freopen("train.in","r",stdin);
    freopen("train.out","w",stdout);
    scanf("%d%d%d",&n,&m,&typ);
    for(int i=1;i<=n;i++) scanf("%u",&a[i]);
    for(int i=2;i<=n;i++) lg[i]=lg[i>>1]+1;
    Maker::init();
	for(int i=1; i<=m; ++i){
		int l=Maker::getnum()%n+1,r=Maker::getnum()%n+1;
		unsigned int v=Maker::getnum();
		if(l>r) swap(l,r);
		if(typ==1) l=1;
        update(l,r,v);
 	}
    for(int j=16;j>=0;j--){
        for(int i=1;i+(1<<j)<=n;i++){
            tr[i][j]=max(tr[i][j],tr[i][j+1]);
            tr[i+(1<<j)][j]=max(tr[i+(1<<j)][j],tr[i][j+1]);
        }
    }
    for(int i=1;i<=n;i++) printf("%u ",max(a[i],tr[i][0]));
}

分块

考虑用 t i t_i ti 记录所有左端点为 i i i 的操作区间的右端点。从小到大遍历 i i i,每次给 t i t_i ti 的所有右端点所在出打上标记 v v v

一旦标记出现,标记的意义就是更新它左边所有的数,所以只需求 [ i , n ] [i,n] [i,n] 的标记的最大值,用分块可以解决。具体实现上,打标记时可以用 bj1 记录散块的标记,bj2 记录整块的标记,查询时若当前是散块,就用 bj1 更新,当前是整块,就用 bj2 更新。

为什么不用线段树求最大值?因为修改很多,我们想要保证修改 O ( 1 ) O(1) O(1),线段树做不到。

时间复杂度 O ( n n + m ) O(n\sqrt n+m) O(nn +m)

#include
using namespace std;
const int N=1e5+10;
int n,m,typ,block;
unsigned int a[N],bj1[N],bj2[400];
vector<pair<int,unsigned int> > v[N];
namespace Maker{
	unsigned int x0,seed;
	void init() {scanf("%u%u",&x0,&seed);}
	inline unsigned int getnum(){
		x0=(x0<<3)^x0;
		x0=((x0>>5)+seed)^x0;
		return x0;
	}
}
void update(pair<int,unsigned int> a)
{
    bj1[a.first]=max(bj1[a.first],a.second);
    int id=(a.first-1)/block;
    bj2[id]=max(bj2[id],a.second);
}
unsigned int getans(int l,int r)
{
    int minr=min((l-1)/block*block+block,r),id=(l-1)/block;
    unsigned int ans=0;
    while(l<=minr){
        ans=max(ans,bj1[l]);
        l++;
    }
    while(r-l+1>=block){
        ans=max(ans,bj2[(l-1)/block]);
        l+=block;
    }
    while(l<=r){
        ans=max(ans,bj1[l]);
        l++;
    }
    return ans;
}
int main()
{
    freopen("train.in","r",stdin);
    freopen("train.out","w",stdout);
    scanf("%d%d%d",&n,&m,&typ);
    block=sqrt(n);
    for(int i=1;i<=n;i++) scanf("%u",&a[i]);
    Maker::init();
	for(int i=1;i<=m;++i){
		int l=Maker::getnum()%n+1,r=Maker::getnum()%n+1;
		unsigned int val=Maker::getnum();
		if(l>r) swap(l,r);
		if(typ==1) l=1;
        v[l].push_back(make_pair(r,val));
 	}
    for(int i=1;i<=n;i++){
        for(auto j:v[i]) update(j);
        printf("%u ",max(a[i],getans(i,n)));
    }
}

你可能感兴趣的:(算法)