XJ训练2021.4.15

真谛,都在skのass里面啊...


T1:期望
首先对于题目不难得到这样的一个式子:
\( \begin{aligned} ans&=\sum^{n}_{i=1}E(siz_i(n-siz_i)))\\ &=\sum^{n}_{i=1}nE(siz_i)-E(siz_i^2)\\ \end{aligned} \)
可以发现难点在于后面的 \(E(siz_i^2)\)
转换成组合意义,\(E(siz_i^2)\) 等价于在以 \(i\) 为根的子树内选两个点的方案数。
那么考虑点对 \((u,v)\) ,它们造成的贡献的点必然是两点之间的 \(lca(u,v)\) 以及它的祖先。
那么\( \begin{aligned} E(siz_i^2)&=\sum_{i=1}^{n}\sum_{u}^{}\sum_{v}^{}[i是lca(u,v)的祖先] \end{aligned} \)
这样子不好统计,考虑到把所有的贡献都存储到 \(lca(u,v)\) 上。
\( \begin{aligned} 原式&=dep_{i}\sum^{n}_{i=1}[i=lca(u,v)] \end{aligned} \)
这样子看起来似乎阳间一些了还是很阴间
前面的 \(dep_{i}\) 跟后面的式子可以分开来算,难点在有后面的sigma,这里设它为 \(f\)
假设现在有一颗以 \(i\) 为根的树,现在往内加入一个 \(u\) 点,它会产生多少贡献?

这样看下来似乎是 \(f_{i}+=siz_{i}\times siz_{u}\)

但是有一些别的情况:

这种情况不应该被统计,却被额外计算了。额外计算的部分是什么?恰好是 \(f_{lca(u,v)}\)
于是,可以做一个简单容斥,有 \(f_{u}=siz_u^2-\sum^{}_{i\in u}ass_{i}^2f_{i}\) ,其中 \(ass_{i}\) 表示 \(i\) 出现在以 \(u\) 为根的树中的概率skのass
此处的 \(ass_{i}\) 需要平方的原因是因为此处的 \(f_{i}\) 实质上是一个平方的期望,因此 \(ass_{i}\) 也需要平方(或者也可以理解为u在去掉贡献的时候和v在去掉贡献的时候都要乘上\(ass_{i}\))。 需要两个skのass
\(ass_{i}\) 比较好求,直接枚举 \(i\)\(u\) 内的父亲 \(j\) ,不难得到 \(ass_{i}=\frac{\sum^{}_{}ass_{j}}{\sigma(j)}\),其中 \(\sigma(j)\)表示 \(j\) 的因数个数。 想必到这里你已经弄透了skのass
由于 \(u|i,u|j\) ,所以在代码中可以直接让 \(i,j\) 除一个 \(u\) ,方便枚举。
同时,还可以发现有了 \(ass\) 数组之后,计算一棵树的期望 \(siz\) 也能顺便解决了skのass的恩惠
最后做一个统计,注意一些细节就可以了。
最后,一起赞美skのass罢

访问skの尻


#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
ll n,mod;
il ll ksm(ll d,ll t){
    ll res=1;
    for(;t;t>>=1,d=d*d%mod)
        if(t&1) res=res*d%mod;
    return res;
}
const int MAXN=3e5+7;
ll f[MAXN],dep[MAXN],inv[MAXN];
/*  
尻のパワー
ass no Powa
*/
vector<int> vec[MAXN];//因子
il ll add(ll x,ll y){return (x+=y)<mod?x:x-mod;}
ll ass[MAXN],ans;
int main(){
    n=read(),mod=read();
    for(ri i=1;i<=n;++i) inv[i]=ksm(i,mod-2);
    for(ri i=1;i<=n;++i){
        for(ri j=2;i*j<=n;++j){
            vec[i*j].push_back(i);
        }
    }
    dep[1]=0;
    for(ri i=2;i<=n;++i){
        ll w=inv[vec[i].size()];
        for(ri j=0;j<vec[i].size();++j){
            int v=vec[i][j];
            dep[i]=add(dep[i],dep[v]+1);
        }
        dep[i]=dep[i]*w%mod;
    }//计算深度期望
    for(ri i=n;i>=2;--i){
        ass[1]=1;
        ll tot=1,num=n/i;//此处的j实际上对应的点是i*j
        for(ri j=2;j<=num;++j){
            ll res=0;
            for(ri k=0;k<vec[j].size();++k){//诶举可能的父亲
                int v=vec[j][k];
                res=add(res,ass[v]);
            }
            ass[j]=res*inv[vec[i*j].size()]%mod;
            tot=add(tot,1*ass[j]);
        }
        f[i]=tot*tot%mod;
        for(ri j=2;j<=num;++j) 
            f[i]=add(f[i],mod-ass[j]*ass[j]%mod*f[i*j]%mod);
    }
    for(ri i=1;i<=n;++i){
        ans=add(ans,((dep[i]*n-dep[i]*f[i])%mod+mod)%mod);
    }
    print(add(ans,ans));//题目中每条路径要算两次
    return 0;
}


那么接下来,探讨的是skの尻。
为和会出现这么奇妙的情况?
skの尻恰好是这题最美妙、最简介却有最有力的证明。
如果没有了skの尻,我想必还在为究竟该如何理解这些东西的含义而苦恼。
skの尻,真理之门!

说人话就是因为设了个叫skの尻的数组结果最后把上面这一段式子的含义解释通了,而且还恰好能联系上前面别的信息,非常神奇。这一切,都是skの尻的指引!
一起信仰skの尻罢。


T2:贪心
这题XJ上贴的题解讲的很清楚了,所以这里就不讲了。

Code


#include<bits/stdc++.h>
using namespace std;
#define il inline
#define ri register int
#define ll long long
#define ui unsigned int
il ll read(){
    bool f=true;ll x=0;
    register char ch=getchar();
    while(ch<'0'||ch>'9') {if(ch=='-') f=false;ch=getchar();}
    while(ch>='0'&&ch<='9') x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
    if(f) return x;
    return ~(--x);
}
il void write(const ll &x){if(x>9) write(x/10);putchar(x%10+'0');}
il void print(const ll &x) {x<0?putchar('-'),write(~(x-1)):write(x);putchar('\n');}
il ll max(const ll &a,const ll &b){return a>b?a:b;}
il ll min(const ll &a,const ll &b){return a<b?a:b;}
vector<int> T;
struct line
{
    int l,r;
    bool operator<(const line &p)const{
        return r>p.r;
    }
};
bool cmp(line x,line y){
    return x.l<y.l;
}
ll n;
const int MAXN=5e5+7;
int h[MAXN],d[MAXN],sta[MAXN],top,cnt,siz;
priority_queue<line> q;
vector<line> vec;
int main(){
    n=read();
    for(ri i=1;i<=n;++i) h[i]=read(),d[i]=read();
    for(ri i=2;i<=n;++i){
        if(h[i]!=h[i-1]) ++cnt;
        if(d[i]!=d[i-1]) ++cnt;
        if(h[i]!=h[i-1]&&d[i]!=d[i-1]) T.push_back(i),++siz;
    }
    sta[++top]=2;
    for(ri i=2;i<=n;++i){
        if(d[i]==d[i-1]){
            sta[top]=i+1;
            continue;
        }
        while(top&&d[i]<d[sta[top]-1])  --top;
        if(top&&d[i]==d[sta[top]-1]){
            vec.push_back((line){sta[top],i});
            ++siz;
            top--;
        }
        sta[++top]=i+1;
    }
    top=0;
    sta[++top]=2;
    for(ri i=2;i<=n;++i){
        if(h[i]==h[i-1]){
            sta[top]=i+1;
            continue;
        }
        while(top&&h[i]<h[sta[top]-1])  --top;
        if(top&&h[i]==h[sta[top]-1]){
             vec.push_back((line){sta[top],i});
            ++siz;
            top--;
        }
        sta[++top]=i+1;
    }
    top=0;
    sort(vec.begin(),vec.end(),cmp);
    for(ri i=0,j=0;i<T.size();++i){
        while(j<vec.size()&&vec[j].l<=T[i]) q.push(vec[j++]);
        while(!q.empty()&&q.top().r<T[i]) q.pop();
        if(!q.empty()){
            q.pop();
            --siz;
        }
    }
    print(cnt-siz);
    return 0;
}

posted @ 2021-04-15 18:26  krimson  阅读(134)  评论(4编辑  收藏  举报