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\) 轮的贡献是:
而又由于 \(f_{i,l,r}=f_{s,l,r}\times (2^{n-r+l+1})^{i-s+1}\) 。
所以这是一个等比数列的形式,公比是:
然后就可以快速算了。
用一个 \(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;
}

浙公网安备 33010602011771号