T2-好题

T2

题目描述

无量仙翁的法阵可以看作一个二维平面,平面上有 \(n\) 根法杖,第 \(i\) 根法杖可以看作从 \((i, 0)\) 到 \((i, h_i)\) 上的线段,保证 \(h\) 是一个 \(1 \sim n\) 的排列。

每根法杖都有一个朝向:

  • 如果第 \(i\) 根法杖朝左,会向满足 \(0 \le x \le i, 0 \le y \le h_i\) 的实数坐标 \((x, y)\) 注入法力;
  • 否则,会向满足 \(i \le x \le n + 1, 0 \le y \le h_i\) 的实数坐标 \((x, y)\) 注入法力;

令 \(S\) 表示被注入法力的点的面积。请你求出,所有可能的朝向方案对应 \(2^S\) 的和。

答案对 \(998244353\) 取模。

思路:

最简单的思考:

注意到 \(h\) 是个排列,而且又是覆盖相关问题。所以不难想到按照 \(h\) 的顺序从高到低进行 \(dp\) 。

假设比当前柱子高的主子的贡献都已经统计完毕,那么对一个高度的柱子来说,其能产生贡献的区间是唯一的(从左边第一个比它高的柱子到右边第一个比他高的柱子)。

所以可以枚举当前柱子是向左还是向右覆盖。

但是这样发现一个重要问题是枚举一个柱子是向某一边覆盖时,这一边并不是完全没有贡献了,这边可能会在更低的高度产生贡献。这样思路就卡死了,完全无法继续。

重要思路转变:

刚刚的思路实质上是想:

枚举到高度 \(i\) 的柱子,就试图把这根柱子向左或向右覆盖所产生的全部贡献一次性算完。

但问题在于,这根柱子所处的区间可能还会被更低的柱子继续分割。于是你会发现:当前看起来已经向某一侧覆盖了,但这一侧并不是没有贡献,而是贡献要等到更低的高度才能确定。

这样一来,你就不得不考虑未来哪些低柱子会进入这个区域、它们又会怎样继续分割区域。结构自然就越来越复杂。

而这道题真正关键的转变是:

不要按“每根柱子最终产生多少贡献”来统计,而要按“每个高度这一层产生多少贡献”来统计。

接上刚刚的思考,我们不难发现:

对于高度 \(i\) 而言:没有产生贡献的位置一定是一个区间,而且这个区间只可能存在于相邻两个高度大于等于 \(i\) 柱子之间。

于是乎可以写出一个转移:

设 \(f_{i,l,r}\) 表示考虑完高度大于等于 \(i\) 的区间,高度 \(i\) 中区间 \((l,r)\) 没有被覆盖到。

设 \(x\) 为高度为 \(i\) 柱子的位置。

考虑用 \(f_{i+1}\) 的值来得到 \(f_i\) 的值,对于一个区间 \(f_{i+1,l,r}\) :

  • 若 \(x\in(l,r)\) :可以转移到 \(f_{i,l,x}\) 和 \(f_{i,x,r}\) 。

  • 否则有两种情况:

    • 柱子 \(i\) 没有覆盖到 \((l,r)\) : 那么可以转移到 \(f_{i,l,r}\) 。

    • 否则全部覆盖,后面的 \(i-1\) 个柱子怎么选都可以,可以直接把 \(2^{i-1}\times 2^{i\times(n+1)}\times f_{i+1,l,r}\) 累加到答案。

然后对于高度为 \(i\) 的区间都要乘上这一个高度的贡献:\(2^{n-r+l+1}\) 。

由于每一层只会有 \(n\) 个区间,所以时间复杂度 \(O(n^2)\) 。

优化:

接下来就是一点比较简单的优化了。

注意到每一个高度除了分裂的那一个区间,其他区间只是自身值会乘上一个有规律的数。

并且答案会累加上一个也有规律的数。

具体来说,我们可以快速算出一个区间经过 \(y\) 轮后,向答案累加了多少贡献。而不用每一局都拿出来统计一下。

具体地,我们可以快速算出一个区间 \((l,r)\) 从 \(s\) 到 \(t\) 轮的贡献:

因为第 \(i\) 轮的贡献是:

\[2^{(n+2)\times i-1}\times f_{i,l,r} \]

而又由于 \(f_{i,l,r}=f_{s,l,r}\times (2^{n-r+l+1})^{i-s+1}\) 。

所以这是一个等比数列的形式,公比是:

\[2^{n+2}\times2^{n-r+l+1}=2^{2n-r+l+3} \]

然后就可以快速算了。

用一个 \(set\) 维护当前层的区间,每个区间需要维护:

  • l : 左端点

  • r : 右端点

  • last : 产生的位置

  • val : \(DP\) 的值

然后每一轮在 \(set\) 可以轻松找到需要被分裂的区间。分裂并删除它,然后根据 \(last\) 统计答案。

结尾一样统计即可。

CODE:

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=1e6+10;
const int mod=998244353;
const ll inv2=499122177; 
int n;
int h[N]; 
struct A{
	int l,r;
	int last;
	ll val; 
	bool operator < (const A &rhs) const{
		return l<rhs.l;
	}
};
set <A> dp;
int pos[N];
ll pow2[N];
ll kuaisumi(ll a,ll b){
	ll res=1;
	while(b){
		if (b&1) res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	}
	return res;
}
int main(){
	cin >> n;
	pow2[0]=1;
	for (int i=1;i<=n;i++){
		cin >> h[i];
		pos[h[i]]=i;
		
	}
	for (int i=1;i<=n+1;i++) pow2[i]=pow2[i-1]*2%mod;
	dp.insert({0,n+1,n+1,1});
	ll ans=0;
	ll mul=kuaisumi(2,n+2);
	for (int i=n;i>=1;i--){
		int x=pos[i];
		auto it=dp.upper_bound((A){x,0,0,0});
		if (it==dp.begin()) continue;
		it=prev(it);
		auto now=*it;
		if (now.l<x&&x<now.r){
    		int last=now.last;
    		ll C=pow2[now.l+n-now.r+1];
    		ll q=kuaisumi(inv2,now.r-now.l+1);
    		int m=last-i-1;
		
    		if (m){
    		    ll T=now.val*kuaisumi(mul,last-1)%mod*inv2%mod;
    		    ll sum=T*(1-kuaisumi(q,m)+mod)%mod;
    		    sum=sum*kuaisumi((1-q+mod)%mod,mod-2)%mod;
    		    (ans+=sum)%=mod;
    		}
    		ll V=now.val*kuaisumi(C,m)%mod;
    		dp.erase(it);
    		dp.insert({now.l,x,i,V*pow2[now.l+n-x+1]%mod});
    		dp.insert({x,now.r,i,V*pow2[x+n-now.r+1]%mod});
		}
	}
	for (auto now:dp){
    	ll C=pow2[now.l+n-now.r+1];
    	ll q=kuaisumi(inv2,now.r-now.l+1);
    	int m=now.last-1;
	
    	if (m){
    	    ll T=now.val*kuaisumi(mul,now.last-1)%mod*inv2%mod;
    	    ll sum=T*(1-kuaisumi(q,m)+mod)%mod;
    	    sum=sum*kuaisumi((1-q+mod)%mod,mod-2)%mod;
    	    (ans+=sum)%=mod;
    	}
	
    	(ans+=now.val*kuaisumi(C,m))%=mod;
	}
	cout <<  ans;
	


	return 0;
}



posted @ 2026-11-04 16:40  wlhao  阅读(3)  评论(0)    收藏  举报