多校A层冲刺NOIP2024模拟赛09

多校A层冲刺NOIP2024模拟赛09

\(T1\) A. 排列最小生成树 (pmst) \(50pts\)

  • 部分分

    • 子任务 \(1\) :输出 \(n-1\)
    • 子任务 \(2\) :暴力建边跑最小生成树。
  • 正解

    • 当连接所有的 \((i,i+1)\) 时,一定有最终生成树所有边的边权都 \(<n\) ;而且若最终生成树里存在一条边的边权 \(\ge n\) ,那它一定不优。
      • 前者比较显然就不写证明了。
      • 后者若所有的边的边权都 \(\ge n\) 一定不优,暂且不考虑这种情况。
      • 设这条边为 \((u,j,|p_{u}-p_{v}| \times |u-v| \ge n)\) ,若断掉这条边一定可以用另外的一条形如 \((i,i+1,|p_{i}-p_{i+1}|<n)\) 的边进行替代,故一定不优。
    • \(|p_{u}-p_{v}| \times |u-v|<n\) 当且仅当 \(|p_{u}-p_{v}|,|u-v|\) 中有一个满足 \(<\sqrt{n}\) ,而这是很容易人为钦定的。
    • 这样的话,每个点只需要考虑 \(O(\sqrt{n})\) 条边,总边数就是 \(O(n\sqrt{n})\) 。排序用桶排序代替或对每种边权开一个 vector 记录端点使得天然有序;并查集(均摊后)单次 \(O(\log n)\) 还是 \(O(\alpha(n))\) 因很难卡满,所以关系不是很大。
      • 在学校 \(OJ\)sort/stable_sort 直接过了,但在 accoders NOI 上被卡到了 \(50pts\)
    点击查看代码
    vector<pair<int,int> >e[50010];
    int p[50010],pos[50010],cnt=0;
    void add(int u,int v,int w,int n)
    {
    	if(w<=n)
    	{
    		e[w].push_back(make_pair(u,v));
    	}
    }
    struct DSU
    {
    	int fa[50010];
    	void init(int n)
    	{
    		for(int i=1;i<=n;i++)
    		{
    			fa[i]=i;
    		}
    	}
    	int find(int x)
    	{
    		return fa[x]==x?x:fa[x]=find(fa[x]);
    	}
    }D;
    int main()
    {
    	freopen("pmst.in","r",stdin);
    	freopen("pmst.out","w",stdout);
    	int n,m,x,y,i,j;
    	ll ans=0;
    	cin>>n;
    	m=sqrt(n)+1;
    	for(i=1;i<=n;i++)
    	{
    		cin>>p[i];
    		pos[p[i]]=i;
    	}
    	for(i=1;i<=n;i++)
    	{
    		for(j=1;j<=m&&i+j<=n;j++)
    		{
    			add(i,i+j,abs(p[i]-p[i+j])*j,n);
    			add(pos[i],pos[i+j],abs(pos[i]-pos[i+j])*j,n);
    		}
    	}
    	D.init(n);
    	for(j=1;j<=n;j++)
    	{
    		for(i=0;i<e[j].size();i++)
    		{	
    			x=D.find(e[j][i].first);
    			y=D.find(e[j][i].second);
    			if(x!=y)
    			{
    				D.fa[x]=y;
    				ans+=j;
    			}
    		}
    	}
    	cout<<ans<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T2\) B. 卡牌游戏 (cardgame) \(15pts\)

  • 钦定下标从 \(0\) 开始,且令 \(\forall i \in [0,nm-1], a_{i}=a_{i \bmod n},b_{i}=b_{i \bmod m}\) ,不然有点难处理。

  • 部分分

    • 子任务 \(1\) :模拟;在发现有循环节 \(\operatorname{lcm}(n,m)\) 后可以适当优化。
    点击查看代码
    ll a[100010],b[100010];
    ll gcd(ll a,ll b)
    {
    	return b?gcd(b,a%b):a;
    }
    int main()
    {
    	freopen("cardgame.in","r",stdin);
    	freopen("cardgame.out","w",stdout);
    	ll n,m,cnt,d,y=0,z=0,p=0,i,j,k;
    	scanf("%lld%lld",&n,&m);
    	d=gcd(n,m);
    	for(i=0;i<=n-1;i++)
    	{
    		scanf("%lld",&a[i]);
    	}
    	for(i=0;i<=m-1;i++)
    	{
    		scanf("%lld",&b[i]);
    	}
    	for(i=0,j=0,k=1;k<=n/d*m;i++,j++,k++)
    	{
    		i-=(i==n)*n;
    		j-=(j==m)*m;
    		if(a[i]>b[j])
    		{
    			y++;
    		}
    		if(a[i]==b[j])
    		{
    			p++;
    		}
    		if(a[i]<b[j])
    		{
    			z++;
    		}
    	}
    	printf("%lld\n",y*d);
    	printf("%lld\n",z*d);
    	printf("%lld\n",p*d);
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    
  • 正解

    • 考虑如何计算每轮中 \(\operatorname{lcm}(n,m)\) 个回合的贡献。
    • 观察到 \(a_{i}\) 遇到的数字一定形如 \(b_{(i+kn) \bmod m}(k \in [0,\frac{m}{\gcd(n,m)}-1])\) ,而这些数恰好是在模 \(\gcd(n,m)\) 意义下和 \(i\) 同余的所有的 \(b_{j}(j \in [0,m-1])\) ,且每个数恰好出现了一次。
    • 然后二分/双指针即可。
    点击查看代码
    ll a[100010],b[100010];
    vector<ll>r[100010];
    ll gcd(ll a,ll b)
    {
    	return b?gcd(b,a%b):a;
    }
    int main()
    {
    	freopen("cardgame.in","r",stdin);
    	freopen("cardgame.out","w",stdout);
    	ll n,m,d,pos1,pos2,y=0,z=0,p=0,i;
    	cin>>n>>m;
    	d=gcd(n,m);
    	for(i=0;i<=n-1;i++)
    	{
    		cin>>a[i];
    	}
    	for(i=0;i<=m-1;i++)
    	{
    		cin>>b[i];
    		r[i%d].push_back(b[i]);
    	}
    	for(i=0;i<=d-1;i++)
    	{
    		sort(r[i].begin(),r[i].end());
    	}
    	for(i=0;i<=n-1;i++)
    	{
    		pos1=lower_bound(r[i%d].begin(),r[i%d].end(),a[i])-r[i%d].begin();
    		pos2=upper_bound(r[i%d].begin(),r[i%d].end(),a[i])-r[i%d].begin();
    		y+=pos1;
    		p+=pos2-pos1;
    	}
    	z=n*m/d-y-p;
    	cout<<y*d<<endl;
    	cout<<z*d<<endl;
    	cout<<p*d<<endl;
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T3\) C. 比特跳跃 (jump) \(19pts\)

  • 部分分

    • 子任务 \(1\) :暴力建边求单源最短路。
    • 子任务 \(2\) :从 \(1\) 跳跃到 \(n\) 再从 \(n\) 跳跃到其他节点代价都是 \(0\)
  • 正解

    • 子任务 \(3\) / \(s=1\)
      • 仿照子任务 \(2\) 的做法,考虑找到一个最大的 \(t\) 使得 \(2^{t} \le n\)
      • 对于 \(x\in [2,2^{t}]\) ,都可以从 \(1\) 跳跃到 \(2^{t}\) 再跳跃到其他节点,故代价为 \(0\)
      • 对于 \(x=2^{t}+r(r \in [1,2^{t}-1])\) ,可以先从 \(1\) 跳跃到 \(x \bigoplus (2^{t+1}-1) \in[1,2^{t}]\) 再跳跃到 \(x\) ,故代价为 \(0\)
      • 而当 \(n=2^{t+1}-1\) 时无法仅通过代价为 \(0\) 的跳跃到达,故暴力建边求最短路即可。
    • 子任务 \(4\) / \(s=2\)
      • 考虑进行拆位统计贡献,而跳跃的代价只和二进制表示下不同的位有关。
      • 假设 \(u,v\) 在二进制表示下不同的位分别是 \(2^{x_{1}},2^{x_{2}},2^{x_{3}}, \dots ,2^{x_{k}}\) ,那么从 \(u\)\(u \bigoplus 2^{x_{1}} \bigoplus 2^{x_{2}}\) 之间的连边是不重要的,因为完全可以先到 \(u \bigoplus 2^{x_{1}}\) 再到 \(u \bigoplus 2^{x_{1}} \bigoplus 2^{x_{2}}\)
      • 故可以只保留有一位不相同的进行连边,暴力建边求最短路。
      • 为避免连重边,考虑只枚举为 \(1\) 的二进制位即可。但这样又出现了另一个问题,就是 \(1,2\) 之间无法之间连边,我们增加一个超级源点 \(0\) 作为中转点即可。
    • 子任务 \(5\) / \(s=3\)
      • 跳多次的代价一定超过跳一次的代价,即跳跃到 \(i\) 若不是从 \(i\) 二进制表示下子集过来,那一定不如直接从 \(1\) 跳过来。
      • 先加上从 \(1\) 到其他点的跳跃边求出单源最短路,然后更新加重新迭代即可。
      • 但直接枚举子集的时间复杂度过高,无法接受。考虑新开一个数组 \(f_{i}\) 表示 \(i\) 二进制表示下子集的最小 \(dis\) ,然后就可以通过枚举在这个集合中的元素进行更新了。
    点击查看代码
    vector<pair<ll,ll> >e[100010];
    ll dis[100010],vis[100010],f[100010];
    priority_queue<pair<ll,ll> >q;
    void add(ll u,ll v,ll w)
    {
    	e[u].push_back(make_pair(v,w));
    }
    void init1(ll s)
    {
    	memset(dis,0x3f,sizeof(dis));
    	dis[s]=0;
    	q.push(make_pair(-dis[s],s));
    }
    void init2(ll n)
    {
    	for(ll i=1;i<=n;i++)
    	{
    		q.push(make_pair(-dis[i],i));
    	}
    }
    void dijsktra(ll s)
    {
    	memset(vis,0,sizeof(vis));
    	while(q.empty()==0)
    	{
    		ll x=q.top().second;
    		q.pop();
    		if(vis[x]==0)
    		{
    			vis[x]=1;
    			for(ll i=0;i<e[x].size();i++)
    			{
    				if(dis[e[x][i].first]>dis[x]+e[x][i].second)
    				{
    					dis[e[x][i].first]=dis[x]+e[x][i].second;
    					q.push(make_pair(-dis[e[x][i].first],e[x][i].first));
    				}
    			}
    		}
    	}
    }
    int main()
    {
    	freopen("jump.in","r",stdin);
    	freopen("jump.out","w",stdout);
    	ll n,m,t,s,k,u,v,w,i,j;
    	scanf("%lld%lld%lld%lld",&n,&m,&s,&k);
    	for(i=1;i<=m;i++)
    	{
    		scanf("%lld%lld%lld",&u,&v,&w);
    		add(u,v,w);
    		add(v,u,w);
    	}
    	if(s==1)
    	{
    		for(t=2;t<=n;t*=2);
    		t/=2;
    		memset(dis,0,sizeof(dis));
    		if(2*t-1==n)
    		{
    			dis[n]=k;
    			for(i=0;i<e[n].size();i++)
    			{
    				dis[n]=min(dis[n],e[n][i].second);
    			}
    		}
    	}
    	if(s==2)
    	{
    		for(i=1;i<=n;i++)
    		{
    			for(j=0;j<=31;j++)
    			{
    				if((i>>j)&1)
    				{
    					add(i,i^(1<<j),k*(1<<j));
    					add(i^(1<<j),i,k*(1<<j));
    				}
    			}
    		}
    		init1(1);
    		dijsktra(1);
    	}
    	if(s==3)
    	{
    		for(i=2;i<=n;i++)
    		{
    			add(1,i,k*(1|i));
    			add(i,1,k*(1|i));
    		}
    		init1(1);
    		dijsktra(1);
    		memset(f,0x3f,sizeof(f));
    		f[1]=0;
    		for(i=2;i<=n;i++)
    		{
    			for(j=0;j<=31;j++)
    			{
    				if((i>>j)&1)
    				{
    					dis[i]=min(dis[i],f[i^(1<<j)]+k*i);
    					f[i]=min(dis[i],f[i^(1<<j)]);
    				}
    			}
    		}
    		init2(n);
    		dijsktra(1);
    	}
    	for(i=2;i<=n;i++)
    	{
    		printf("%lld ",dis[i]);
    	}
    	fclose(stdin);
    	fclose(stdout);
    	return 0;
    }
    

\(T4\) D. 区间 (interval) \(35pts\)

  • 部分分
    • 子任务 \(1,2\)
      • 模拟,适当剪枝。时间复杂度为 \(O(qn^{2})\)

        点击查看代码
        ll a[300010],b[300010],r[300010];
        vector<ll>pos[300010];
        stack<ll>s;
        int main()
        {
        	freopen("interval.in","r",stdin);
        	freopen("interval.out","w",stdout);
        	ll n,q,x,y,ans,i,j,k;
        	scanf("%lld",&n);
        	for(i=1;i<=n;i++)
        	{
        		scanf("%lld",&a[i]);
        	}
        	for(i=2;i<=n;i++)
        	{
        		scanf("%lld",&b[i]);
        	}
        	for(i=n;i>=1;i--)
        	{
        		while(s.empty()==0&&a[s.top()]<a[i])
        		{
        			s.pop();
        		}
        		r[i]=(s.empty()==0)?s.top():n;
        		s.push(i);
        	}
        	for(i=1;i<=n;i++)
        	{
        		for(j=i+1;j<=r[i];j++)
        		{
        			if(b[j]>a[i])
        			{
        				pos[i].push_back(j);
        			}
        		}	
        	}
        	scanf("%lld",&q);
        	for(k=1;k<=q;k++)
        	{
        		scanf("%lld%lld",&x,&y);
        		ans=0;
        		for(i=x;i<=y;i++)
        		{
        			for(j=0;j<pos[i].size()&&pos[i][j]<=y;j++)
        			{
        				ans++;
        			}
        		}
        		printf("%lld\n",ans);
        	}
        	fclose(stdin);
        	fclose(stdout);
        	return 0;
        }
        
    • 子任务 \(3\)
      • 因为 \(a_{i},b_{i} \le 3\) 所以每个 \(l\) 对应的 \(r\) 不会很多。

      • 询问本质上是一个静态二维数点问题,主席树/ \(CDQ\) 分治/扫描线的时间复杂度均为 \(O((n+q) \log n)\) ,但主席树比 \(CDQ\) 分治/扫描线空间复杂度多带一个 \(O(\log n)\)

        点击查看主席树代码
        int a[300010],b[300010],r[300010];
        stack<int>s;
        struct PDS_SMT
        {
        	int root[300010],rt_sum;
        	struct SegmentTree
        	{
        		int ls,rs,sum;
        	}tree[5355550];
        	#define lson(rt) (tree[rt].ls)
        	#define rson(rt) (tree[rt].rs)
        	int build_rt()
        	{
        		rt_sum++;
        		lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0;
        		return rt_sum;
        	}
        	void update(int pre,int &rt,int l,int r,int pos)
        	{
        		rt=build_rt();
        		tree[rt]=tree[pre];
        		tree[rt].sum++;
        		if(l==r)
        		{
        			return;
        		}
        		int mid=(l+r)/2;
        		if(pos<=mid)
        		{
        			update(lson(pre),lson(rt),l,mid,pos);
        		}
        		else
        		{
        			update(rson(pre),rson(rt),mid+1,r,pos);
        		}
        	}
        	ll query(int rt1,int rt2,int l,int r,int x,int y)
        	{
        		if(x<=l&&r<=y)
        		{
        			return tree[rt2].sum-tree[rt1].sum;
        		}
        		int mid=(l+r)/2;
        		ll ans=0;
        		if(x<=mid)
        		{
        			ans+=query(lson(rt1),lson(rt2),l,mid,x,y);
        		}
        		if(y>mid)
        		{
        			ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y);
        		}
        		return ans;
        	}
        }T;
        int main()
        {
        	freopen("interval.in","r",stdin);
        	freopen("interval.out","w",stdout);
        	int n,q,x,y,ans,i,j;
        	scanf("%d",&n);
        	for(i=1;i<=n;i++)
        	{
        		scanf("%d",&a[i]);
        	}
        	for(i=2;i<=n;i++)
        	{
        		scanf("%d",&b[i]);
        	}
        	for(i=n;i>=1;i--)
        	{
        		while(s.empty()==0&&a[s.top()]<a[i])
        		{
        			s.pop();
        		}
        		r[i]=(s.empty()==0)?s.top():n;
        		s.push(i);
        	}
        	for(i=1;i<=n;i++)
        	{
        		T.root[i]=T.root[i-1];
        		for(j=i+1;j<=r[i];j++)
        		{
        			if(b[j]>a[i])
        			{
        				T.update(T.root[i],T.root[i],1,n,j);
        			}
        		}	
        	}
        	scanf("%d",&q);
        	for(i=1;i<=q;i++)
        	{
        		scanf("%d%d",&x,&y);
        		printf("%lld\n",T.query(T.root[x-1],T.root[y],1,n,x,y));
        	}
        	fclose(stdin);
        	fclose(stdout);
        	return 0;
        }
        
        点击查看 CDQ 分治代码
        int a[300010],b[300010],r[300010],cnt=0;
        ll ans[300010];
        stack<int>s;
        struct node
        {
        	int x,y,val,id;
        	bool operator < (const node &another) const
        	{
        		return (x==another.x)?(y<another.y):(x<another.x);
        	}
        }q[3500010];
        void add(int x,int y,int val,int id)
        {
        	cnt++;
        	q[cnt].x=x;
        	q[cnt].y=y;
        	q[cnt].val=val;
        	q[cnt].id=id;
        }
        struct BIT
        {
        	ll c[300010];
        	int lowbit(int x)
        	{
        		return (x&(-x));
        	}
        	void update(int n,int x,int val)
        	{
        		if(val==0)
        		{
        			return;
        		}
        		for(int i=x;i<=n;i+=lowbit(i))
        		{
        			c[i]+=val;
        		}
        	}
        	ll getsum(int x)
        	{
        		ll ans=0;
        		for(int i=x;i>=1;i-=lowbit(i))
        		{
        			ans+=c[i];
        		}
        		return ans;
        	}
        }T;
        void cdq(int l,int r,int k)
        {
        	if(l==r)
        	{
        		return;
        	}
        	int mid=(l+r)/2,x,y;
        	cdq(l,mid,k);
        	cdq(mid+1,r,k);
        	for(x=l,y=mid+1;y<=r;y++)
        	{
        		for(;q[x].x<=q[y].x&&x<=mid;x++)
        		{
        			T.update(k,q[x].y,(q[x].id==0)*q[x].val);
        		}
        		ans[q[y].id]+=(q[y].id!=0)*q[y].val*T.getsum(q[y].y);
        	}
        	x--;
        	for(int i=l;i<=x;i++)
        	{
        		T.update(k,q[i].y,-(q[i].id==0)*q[i].val);
        	}
        	inplace_merge(q+l,q+mid+1,q+r+1);
        }
        int main()
        {
        	freopen("interval.in","r",stdin);
        	freopen("interval.out","w",stdout);
        	int n,m,x,y,i,j;
        	scanf("%d",&n);
        	for(i=1;i<=n;i++)
        	{
        		scanf("%d",&a[i]);
        	}
        	for(i=2;i<=n;i++)
        	{
        		scanf("%d",&b[i]);
        	}
        	for(i=n;i>=1;i--)
        	{
        		while(s.empty()==0&&a[s.top()]<a[i])
        		{
        			s.pop();
        		}
        		r[i]=(s.empty()==0)?s.top():n;
        		s.push(i);
        		for(j=i+1;j<=r[i];j++)
        		{
        			if(b[j]>a[i])
        			{
        				add(i+1,j+1,1,0);
        			}
        		}	
        	}
        	scanf("%d",&m);
        	for(i=1;i<=m;i++)
        	{
        		scanf("%d%d",&x,&y);
        		x++;
        		y++;
        		add(x-1,x-1,1,i);
        		add(y,y,1,i);
        		add(y,x-1,-1,i);
        		add(x-1,y,-1,i);
        	}
        	cdq(1,cnt,n+1);
        	for(i=1;i<=m;i++)
        	{
        		printf("%lld\n",ans[i]);
        	}
        	fclose(stdin);
        	fclose(stdout);
        	return 0;
        }
        
        点击查看扫描线代码
        int a[300010],b[300010],r[300010],cnt=0;
        ll ans[300010][5];
        stack<int>s;
        struct node
        {
        	int x,y,id;
        	bool operator < (const node &another) const
        	{
        		return (x==another.x)?(y<another.y):(x<another.x);
        	}
        }q[10000010];
        void add(int x,int y,int id)
        {
        	cnt++;
        	q[cnt].x=x;
        	q[cnt].y=y;
        	q[cnt].id=id;
        }
        struct BIT
        {
        	ll c[300010];
        	int lowbit(int x)
        	{
        		return (x&(-x));
        	}
        	void update(int n,int x,int val)
        	{
        		if(val==0)
        		{
        			return;
        		}
        		for(int i=x;i<=n;i+=lowbit(i))
        		{
        			c[i]+=val;
        		}
        	}
        	ll getsum(int x)
        	{
        		ll ans=0;
        		for(int i=x;i>=1;i-=lowbit(i))
        		{
        			ans+=c[i];
        		}
        		return ans;
        	}
        }T;
        int main()
        {
        	freopen("interval.in","r",stdin);
        	freopen("interval.out","w",stdout);
        	int n,m,x,y,i,j;
        	scanf("%d",&n);
        	for(i=1;i<=n;i++)
        	{
        		scanf("%d",&a[i]);
        	}
        	for(i=2;i<=n;i++)
        	{
        		scanf("%d",&b[i]);
        	}
        	for(i=n;i>=1;i--)
        	{
        		while(s.empty()==0&&a[s.top()]<a[i])
        		{
        			s.pop();
        		}
        		r[i]=(s.empty()==0)?s.top():n;
        		s.push(i);
        		for(j=i+1;j<=r[i];j++)
        		{
        			if(b[j]>a[i])
        			{
        				add(i+1,j+1,0);
        			}
        		}	
        	}
        	scanf("%d",&m);
        	for(i=1;i<=m;i++)
        	{
        		scanf("%d%d",&x,&y);
        		x++;
        		y++;
        		add(x-1,x-1,i);
        		add(y,y,i);
        		add(y,x-1,i);
        		add(x-1,y,i);
        	}
        	sort(q+1,q+1+cnt);
        	for(i=1;i<=cnt;i++)
        	{
        		if(q[i].id==0)
        		{
        			T.update(n+1,q[i].y,1);
        		}
        		else
        		{
        			ans[q[i].id][0]++;
        			ans[q[i].id][ans[q[i].id][0]]=T.getsum(q[i].y);
        		}
        	}
        	for(i=1;i<=m;i++)
        	{
        		printf("%lld\n",ans[i][4]+ans[i][3]-ans[i][2]-ans[i][1]);
        	}
        	fclose(stdin);
        	fclose(stdout);
        	return 0;
        }
        
  • 正解
    • 考虑将询问离线下来,枚举右端点进行扫描线。

    • 维护一个单调递减的栈来满足第一个要求。对于每个 \(r\) 找到最小的左端点 \(l\) 使得 \(\forall i \in [l,r-1],a_{i}<b_{r}\) ,挂个 \(ST\) 表再二分是不必要的,因为只有单调栈内的元素才可能产生贡献,所以直接在单调栈里二分即可。

    • 二分出单调栈的左端点 \(l\)\(s_{l \sim top}\) 这些点均可以作为合法的左端点进行转移,但因为实际下标可能不连续,线段树挨个遍历修改时间复杂度不可接受。

    • 常见的做法是线段树维护历史版本和来做。具体地,将 \(s_{l} \sim r-1\) 都新加一个贡献(仅对在栈内的生效),而通过记录某个位置是否在栈内可以很方便地支持这个操作。

      • 每个左端点都对某些右端点有贡献,贡献彼此之间可能不相同,故考虑每扫到一个右端点就视为新建一个版本,然后查询历史版本和。

        点击查看代码
        ll a[300010],b[300010],ans[300010];
        deque<ll>s;
        vector<pair<ll,ll> >q[300010];
        struct SMT
        {
        	struct SegentTree
        	{
        		ll sum,hsum,lazy,cnt;
        	}tree[1200010];
        	ll lson(ll x)
        	{
        		return x*2;
        	}
        	ll rson(ll x)
        	{
        		return x*2+1;
        	}
        	void pushup(ll rt)
        	{
        		tree[rt].cnt=tree[lson(rt)].cnt+tree[rson(rt)].cnt;
        		tree[rt].hsum=tree[lson(rt)].hsum+tree[rson(rt)].hsum;
        	}
        	void build(ll rt,ll l,ll r)
        	{
        		if(l==r)
        		{
        			return;
        		}
        		ll mid=(l+r)/2;
        		build(lson(rt),l,mid);
        		build(rson(rt),mid+1,r);
        	}
        	void pushlazy(ll rt,ll lazy)
        	{
        		tree[rt].hsum+=lazy*tree[rt].cnt;
        		tree[rt].lazy+=lazy;
        	}
        	void pushdown(ll rt)
        	{
        		pushlazy(lson(rt),tree[rt].lazy);
        		pushlazy(rson(rt),tree[rt].lazy);
        		tree[rt].lazy=0;
        	}
        	void update_val(ll rt,ll l,ll r,ll x,ll y,ll val)
        	{
        		if(x<=l&&r<=y)
        		{
        			tree[rt].hsum+=val*tree[rt].cnt;
        			tree[rt].lazy+=val;
        			return;
        		}
        		pushdown(rt);
        		ll mid=(l+r)/2;
        		if(x<=mid)
        		{
        			update_val(lson(rt),l,mid,x,y,val);
        		}
        		if(y>mid)
        		{
        			update_val(rson(rt),mid+1,r,x,y,val);
        		}
        		pushup(rt);
        	}
        	void update_tim(ll rt,ll l,ll r,ll pos,ll val)
        	{
        		if(l==r)
        		{
        			tree[rt].cnt+=val;
        			return;
        		}
        		pushdown(rt);
        		ll mid=(l+r)/2;
        		if(pos<=mid)
        		{
        			update_tim(lson(rt),l,mid,pos,val);
        		}
        		else
        		{
        			update_tim(rson(rt),mid+1,r,pos,val);
        		}
        		pushup(rt);
        	}
        	ll query(ll rt,ll l,ll r,ll x,ll y)
        	{
        		if(x<=l&&r<=y)
        		{
        			return tree[rt].hsum;
        		}
        		pushdown(rt);
        		ll mid=(l+r)/2,ans=0;
        		if(x<=mid)
        		{
        			ans+=query(lson(rt),l,mid,x,y);
        		}
        		if(y>mid)
        		{
        			ans+=query(rson(rt),mid+1,r,x,y);
        		}
        		return ans;
        	}
        }T;
        ll under(ll x)
        {
        	ll ans=-1;
        	if(s.empty()==0)
        	{
        		ll l=0,r=s.size()-1,mid;
        		while(l<=r)
        		{
        			mid=(l+r)/2;
        			if(a[s[mid]]<x)
        			{
        				ans=mid;
        				r=mid-1;
        			}
        			else
        			{
        				l=mid+1;
        			}
        		}
        	}
        	return ans;
        }
        ll lower(ll x)
        {
        	ll ans=-1;
        	if(s.empty()==0)
        	{
        		ll l=0,r=s.size()-1,mid;
        		while(l<=r)
        		{
        			mid=(l+r)/2;
        			if(s[mid]>=x)
        			{
        				ans=mid;
        				r=mid-1;
        			}
        			else
        			{
        				l=mid+1;
        			}
        		}
        	}
        	return ans;
        }
        int main()
        {
        	freopen("interval.in","r",stdin);
        	freopen("interval.out","w",stdout);
        	ll n,m,l,r,pos,tmp,i,j;
        	scanf("%lld",&n);
        	for(i=1;i<=n;i++)
        	{
        		scanf("%lld",&a[i]);
        	}
        	for(i=2;i<=n;i++)
        	{
        		scanf("%lld",&b[i]);
        	}
        	scanf("%lld",&m);
        	for(i=1;i<=m;i++)
        	{
        		scanf("%lld%lld",&l,&r);
        		q[r].push_back(make_pair(l,i));
        	}
        	T.build(1,1,n);
        	for(i=1;i<=n;i++)
        	{
        		pos=under(b[i]);
        		if(pos!=-1)
        		{
        			T.update_val(1,1,n,s[pos],i-1,1);
        		}
        		for(j=0;j<q[i].size();j++)
        		{
        			pos=lower(q[i][j].first);
        			ans[q[i][j].second]=T.query(1,1,n,q[i][j].first,i-1);
        		}
        		while(s.empty()==0&&a[s.back()]<=a[i])
        		{
        			T.update_tim(1,1,n,s.back(),-1);
        			s.pop_back();
        		}
        		s.push_back(i);
        		T.update_tim(1,1,n,i,1);
        	}
        	for(i=1;i<=m;i++)
        	{
        		printf("%lld\n",ans[i]);
        	}
        	fclose(stdin);
        	fclose(stdout);
        	return 0;
        }
        
    • 另外一个做法是再开一棵副线段树维护单调栈内元素的贡献,在弹出时将贡献算入主线段树,查询时两棵线段树均进行查询。

      点击查看代码
      ll a[300010],b[300010],ans[300010];
      deque<ll>s;
      vector<pair<ll,ll> >q[300010];
      struct SMT
      {
      	struct SegentTree
      	{
      		ll len,sum,lazy;
      	}tree[1200010];
      	ll lson(ll x)
      	{
      		return x*2;
      	}
      	ll rson(ll x)
      	{
      		return x*2+1;
      	}
      	void pushup(ll rt)
      	{
      		tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum;
      	}
      	void build(ll rt,ll l,ll r)
      	{
      		tree[rt].len=r-l+1;
      		if(l==r)
      		{
      			return;
      		}
      		ll mid=(l+r)/2;
      		build(lson(rt),l,mid);
      		build(rson(rt),mid+1,r);
      	}
      	void pushdown(ll rt)
      	{
      		if(tree[rt].lazy!=0)
      		{
      			tree[lson(rt)].sum+=tree[rt].lazy*tree[lson(rt)].len;
      			tree[rson(rt)].sum+=tree[rt].lazy*tree[rson(rt)].len;
      			tree[lson(rt)].lazy+=tree[rt].lazy;
      			tree[rson(rt)].lazy+=tree[rt].lazy;
      			tree[rt].lazy=0;
      		}
      	}
      	void update(ll rt,ll l,ll r,ll x,ll y,ll val)
      	{
      		if(x<=l&&r<=y)
      		{
      			tree[rt].sum+=val*tree[rt].len;
      			tree[rt].lazy+=val;
      			return;
      		}
      		pushdown(rt);
      		ll mid=(l+r)/2;
      		if(x<=mid)
      		{
      			update(lson(rt),l,mid,x,y,val);
      		}
      		if(y>mid)
      		{
      			update(rson(rt),mid+1,r,x,y,val);
      		}
      		pushup(rt);
      	}
      	ll query(ll rt,ll l,ll r,ll x,ll y)
      	{
      		if(x<=l&&r<=y)
      		{
      			return tree[rt].sum;
      		}
      		pushdown(rt);
      		ll mid=(l+r)/2,ans=0;
      		if(x<=mid)
      		{
      			ans+=query(lson(rt),l,mid,x,y);
      		}
      		if(y>mid)
      		{
      			ans+=query(rson(rt),mid+1,r,x,y);
      		}
      		return ans;
      	}
      }T[2];
      ll under(ll x)
      {
      	ll ans=-1;
      	if(s.empty()==0)//栈为空时, accoders 直接 RE 了,但学校 OJ 没爆
      	{
      		ll l=0,r=s.size()-1,mid;
      		while(l<=r)
      		{
      			mid=(l+r)/2;
      			if(a[s[mid]]<x)
      			{
      				ans=mid;
      				r=mid-1;
      			}
      			else
      			{
      				l=mid+1;
      			}
      		}
      	}
      	return ans;
      }
      ll lower(ll x)
      {
      	ll ans=-1;
      	if(s.empty()==0)
      	{
      		ll l=0,r=s.size()-1,mid;
      		while(l<=r)
      		{
      			mid=(l+r)/2;
      			if(s[mid]>=x)
      			{
      				ans=mid;
      				r=mid-1;
      			}
      			else
      			{
      				l=mid+1;
      			}
      		}
      	}
      	return ans;
      }
      int main()
      {
      	freopen("interval.in","r",stdin);
      	freopen("interval.out","w",stdout);
      	ll n,m,l,r,pos,tmp,i,j;
      	scanf("%lld",&n);
      	for(i=1;i<=n;i++)
      	{
      		scanf("%lld",&a[i]);
      	}
      	for(i=2;i<=n;i++)
      	{
      		scanf("%lld",&b[i]);
      	}
      	scanf("%lld",&m);
      	for(i=1;i<=m;i++)
      	{
      		scanf("%lld%lld",&l,&r);
      		q[r].push_back(make_pair(l,i));
      	}
      	T[0].build(1,1,n);
      	T[1].build(1,1,n);
      	for(i=1;i<=n;i++)
      	{
      		pos=under(b[i]);
      		if(pos!=-1)
      		{
      			T[1].update(1,1,n,pos+1,s.size(),1);
      		}
      		for(j=0;j<q[i].size();j++)
      		{
      			pos=lower(q[i][j].first);
      			ans[q[i][j].second]=T[0].query(1,1,n,q[i][j].first,i-1);
      			if(pos!=-1)
      			{
      				ans[q[i][j].second]+=T[1].query(1,1,n,pos+1,s.size());
      			}
      		}
      		while(s.empty()==0&&a[s.back()]<=a[i])
      		{
      			tmp=T[1].query(1,1,n,s.size(),s.size());
      			T[0].update(1,1,n,s.back(),s.back(),tmp);
      			T[1].update(1,1,n,s.size(),s.size(),-tmp);
      			s.pop_back();
      		}
      		s.push_back(i);
      	}
      	for(i=1;i<=m;i++)
      	{
      		printf("%lld\n",ans[i]);
      	}
      	fclose(stdin);
      	fclose(stdout);
      	return 0;
      }
      

总结

  • 在发现 \(T1\) \(45 \min\) 还没切的前提下,直接去写后面的部分分了。写部分分的时候也是匆匆忙忙,基本没从值域入手。
  • \(T1\)
    • 在最后尝试随机化做法下,发现 \(n \le 2000\) 下前后各选 \(50\) 个点就拍上了,但没意识到最终最小生成树上所有边的边权都 \(<n\) ,导致 \(2 \times 50n\) 的边数都开不下,没再去想只需要考虑 \(O(\sqrt{n})\) 条边的做法。
    • 以为会像 多校A层冲刺NOIP2024模拟赛08 T1 A. 传送 (teleport) 一样通过几何直观性可以很好理解,所以把 \(|p_{i}-p_{j}| \times |i-j|\) 理解成了矩形面积然后在二维平面上进行覆盖。
  • \(T1,T2\) 交换了下位置是没想到的。
  • \(T2\) 想到同余系/按下标分类的方法了,但没时间(还剩 \(40 \min\) ,且还要分配给 \(T1\) 的随机化做法)去想具体实现了,而且同余系也掌握得不是很好。
  • \(T3\)\(s=1\) 的部分分想的时间太短了,没想到还可以从 \(n\) 进行跳跃。
  • \(T4\) 分析单调栈分割区间时见复杂度少乘了个 \(n\) ,导致误认为是 \(T1,T4\) 互换了位置,后面所写的主席树和 \(CDQ\) 分治的时空复杂度都少乘了一个 \(O(n)\) 。但在意识到这个问题后,仍坚持写完了主席树和 \(CDQ\) 分治,没有直接摆烂,挺好。
posted @ 2024-10-20 07:33  hzoi_Shadow  阅读(65)  评论(0编辑  收藏  举报
扩大
缩小