AtCoder ABC 221 D
题目分析
不难分析出来,我们就是要求出,对每一个位置i而言,其后面的比它大的位置j1,j2.....jn,构成的数学式子求解。
\[ans[i]=2^{j_1-i-1}+2^{j_2-i-1}+...+2^{j_n-i-1}
\]
我们可以对式子进行化简。
\[ans[i]=\frac{2^{j_1-1}+2^{j_2-1}+...2^{j_n-1}}{2_i}
\]
然后,emm,我就卡在这里了,因为这时就要求出i之后所有比它大的位置。
但是,我忽略了一个很重要的思想,正着想不行的时候,我们可以尝试反着想。
如果,我们要求的是一个数前面所有比它小的位置,会有出路嘛?
我们可以很快的发现,当要找一个数前面比他小的位置来求答案时,我们要求的式子就变成了
\[ans[j]=2^{j-1}(\frac{1}{2^{i_1}}+\frac{1}{2^{i_2}}+...+\frac{1}{2^{i_n}})
\]
那接下来要解决的问题就是,括号里面的怎么求?
但这时,就简单了,我们可以用树状数组进行优化。在树状数组中,考虑每个位置时,在w[j]上插入对应的式子。这样正向扫描时,这样sum(w[j])就是前面比它小的位置上就是对应的式子的和(即括号里的内容)了。
在这里,因为我们要插入的是一个分数,因为需要取模,所以要插入的是对应的逆元。
思考
这里,你会发现,其实反向也是能写的,只要每个位置插入2^j次方即可,过程与上同理。
此时,我们会发现关于树状数组的运用的小技巧。
当我们发现,我们要用到的是数组中相对某一个位置i前或后中的某些比该位置i上数字大或小的位置某些特性的和时,我们就要想到树状数组了。
例如:我们要求逆序对
我们要求的就是相对于这个位置i,它前面比它大的位置的数量,那此时,我们就可以给这些位置插入1,这样sum(w[i])时,就能直接知道比它小的数量
再比如,这道题目。
若正向求,我们要求的就是相对于这个位置i,它前面比它小的位置上的下标,同时这个下标我们用时,就是将下标带入对应式子的,所以我们可以直接在对应位置上插入,带入下标后的对应式子的值,这样sum(w[i])就是前面比它小的位置的下标带入的式子的值的和了。
AcCode
#include<iostream>
#include<cmath>
#include<map>
using namespace std;
typedef long long LL;
const LL N = 3e5 + 10,mod=998244353;
LL tr[N];
int w[N];
int n;
int lowbit(int x)
{
return x & (-x);
}
void add(int x,LL c)
{
for(int i=x;i<=n;i+=lowbit(i))
{
tr[i]=(tr[i]+c)%mod;
}
}
LL sum(int x)
{
LL res = 0;
for(int i=x;i;i-=lowbit(i))
{
res=(res+tr[i])%mod;
}
return res;
}
LL ksm(LL a,LL b)
{
LL res = 1;
while(b)
{
if(b&1) res=res*a%mod;
b>>=1;
a=a*a%mod;
}
return res;
}
void init()
{
map<int,int> mp;
for(int i=1;i<=n;i++)
mp[w[i]]=0;
int sz=0;
for(auto &p:mp) p.second=++sz;
for(int i=1;i<=n;i++) w[i]=mp[w[i]];
}
int main()
{
const LL div = ksm(2,mod-2);
cin>>n;
for(int i=1;i<=n;i++) cin>>w[i];
LL ans = 0;
init();
for(int i=1; i<=n; i++){
ans += sum(w[i])*ksm(2,i-1);
ans %= mod;
add(w[i],ksm(div,i));
}
cout<<ans<<endl;
return 0;
}

浙公网安备 33010602011771号