专栏文章
两个小时才会,脑子没带来???
CF1621G题解参与者 1已保存评论 0
文章操作
快速查看文章及其快照的属性,并进行相关操作。
- 当前评论
- 0 条
- 当前快照
- 1 份
- 快照标识符
- @mio1t2t2
- 此快照首次捕获于
- 2025/12/02 11:58 3 个月前
- 此快照最后确认于
- 2025/12/02 11:58 3 个月前
看了眼别的题解做法都好妙,感觉妙妙题的妙妙方法学不会了。
先说一个常用技巧。这个题我的做法并没有用上这个,但是因为妙妙做法用了所以记录一下,等会会分别叙述两种做法的。
trick
就是两个 Node,分别表示“min()和取到 min 的方法数()”以及“每种方法对应的权值总和()与方法数()”。它们可以很方便地定义运算,对于 min-node:
对于 sum-node:
这两种东西的定义都秉承着“加法分类,乘法分步”的加乘原理,所以代码会很自然。
回到本题。首先看着这个 烦,先变成排列(如果两个位置值一样,想想就应该知道是位置更后的离散化完值更小),这样就可以按照值找位置了。
考虑拆贡献,什么时候 在上升子序列且有贡献?当且仅当这个子序列没有包含任何 的下标,其中 满足下面两点:
- (对, 是允许的,这时 怎么都没有贡献)
- ,有 。换言之 是满足上一个条件的最大的位置。
这个等价转化应该很明显,那么现在就变成了下面一个问题:给定 个区间 ,对于每个区间有多少个只包含 内下标的上升子序列并且一定得包含 (与原题的关联是这里 )。这个东西我不知道能不能莫队,反正不能 polylog 应该是明显的。
考虑继续转化。容斥。把他变成算“在里面且没有贡献”的个数。也就是说必须包含 和 满足 且 的 !这是什么!这样的 只有一个,就是 ! 好的这是第一个关键的转化:问题变成了给定 个区间 ,求刚好过 和 两个位置的上升子序列的个数。怎么说呢,单纯的这个问题也是没法做的,所以还得用 的性质。
处理这个,可以用两种方法。说之前先假装我们已经处理好了其他的问题,变成了“过 和 且 是第一个, 是最后一个的个数,乘 。”这里 表示以 结尾的上升子序列个数。然后就可以思考这个问题了,先说我的。
继续瞪着 看,有第二个关键性质:对于 ,一定有 ——根据 的定义其实就能推出这个单调性。使用这个我们能发现我的做法最关键的地方:对于 的 ,以他开头的上升子序列,不会对 产生不应该有的贡献。 为什么呢?因为这时一定有 。而 的时候,一定是 才让 跳过 的,因此没问题!而对于 的 只需严格限定他在 BIT 上的贡献只到 也就行了。原因一定有 ,因此不会妨碍 。
综上,直接在 BIT 上给 部分加上自己的目前的“类 LIS 状 dp”的结果就行了。复杂度单只老哥。
code
CPP#include <bits/stdc++.h>
#define rep(i, a, b) for (int i = (a), i##ABRACADABRA = (b); i <= i##ABRACADABRA; i++)
#define drep(i, a, b) for (int i = (a), i##ABRACADABRA = (b); i >= i##ABRACADABRA; i--)
using namespace std;
using ll = long long;
constexpr ll mod=1e9+7;
int n,a[200010],ia[200010],pos[200010];
ll ans=0,dp[200010],f[200010],g[200010],w[200010],c[200010];
void add(int o,ll x){for (;o<=n+1;o+=o&-o)(c[o]+=x)%=mod;}
ll ask(int o){ll x=0;for (;o;o&=o-1)x+=c[o];return x%mod;}
void solve(){
scanf("%d",&n),ans=0;
rep(i,1,n)scanf("%d",&a[i]),ia[i]=i;
// 把 a 变成一个排列:相同的数,下标大的要小,方便
sort(ia+1,ia+n+1,[&](int x,int y){
return a[x]==a[y]?x>y:a[x]<a[y];
});
rep(i,1,n)a[ia[i]]=i;
int j=n;
rep(x,1,n){
// 倒着寻找
while (j&&a[j]<x)--j;
pos[ia[x]]=j;
}
rep(i,0,n+5)c[i]=0;
rep(i,1,n)f[i]=1+ask(a[i]-1),add(a[i],f[i]);
rep(i,0,n+5)c[i]=0;
drep(i,n,1)g[i]=1+ask(n-a[i]),add(n-a[i]+1,g[i]);
rep(i,1,n)ans+=f[i]*g[i]%mod;
rep(i,0,n+5)c[i]=0;
// 正片开始
rep(i,1,n){
w[i]=(f[i]+ask(a[i]-1))%mod; // 计算自己答案
add(a[i],w[i]),add(a[pos[i]],mod-w[i]); // 贡献到 a_{p_i}-1
// cout<<i<<' '<<w[i]<<'\n';
}
rep(i,1,n)if (pos[i]==i)ans+=mod-w[i]; // 每个 p,只减一次,因为多个贡献已经上了
printf("%lld\n",ans%mod);
}
int main() {
int tt;
scanf("%d",&tt);
while (tt--)solve();
return 0;
}
题解区看到了另一种做法(credit: @CXY07),非常妙啊!我来按照我的理解讲一下。就是说有:如果 在 引出的上升子序列上, 一定是 所引出的上升子序列的结尾,而且所有 的 所引出的上升子序列的结尾 一定有 。根据 的定义和那个单调性这是显然的,因此我们到一个 只需要查他到最远的地方的贡献就行了!这样我们就需要开头所说的 max-node 结构了。我们直接把所有的点丢到树状数组上,倒着做,到了 拿出最远的,查看这个节点 max 部分,如果不是 就丢掉,否则一定他的 cnt 就是贡献。
复杂度单只老哥。妙啊。
相关推荐
评论
共 0 条评论,欢迎与作者交流。
正在加载评论...