Loading

【考后总结】7 月 NOI 模拟赛 2

Page Views Count

7.10 冲刺国赛模拟 33

T1 染色

点分树模板。

另一做法是考虑差分,关键是求 \(\mathrm{LCA}\) 到跟距离的和,可以每次染色后使得到根的路径上 \(+1\),这样查询时也查询到根的路径上权值和。

点击查看代码
int n,m;
struct edge{
    int to,w,nxt;
}e[maxn<<1];
int head[maxn],cnt;
inline void add_edge(int u,int v,int w){
    e[++cnt].to=v,e[cnt].w=w,e[cnt].nxt=head[u],head[u]=cnt;
    e[++cnt].to=u,e[cnt].w=w,e[cnt].nxt=head[v],head[v]=cnt;
}
namespace HLD{
    int fa[maxn],dep[maxn],siz[maxn],son[maxn];
    ll dis[maxn];
    int top[maxn];
    void dfs1(int u,int d){
        dep[u]=d,siz[u]=1;
        int maxson=-1;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to,w=e[i].w;
            if(v==fa[u]) continue;
            dis[v]=dis[u]+w;
            dfs1(v,d+1);
            siz[u]+=siz[v];
            if(siz[v]>maxson) son[u]=v,maxson=siz[v];
        }
    }
    void dfs2(int u,int t){
        top[u]=t;
        if(!son[u]) return;
        dfs2(son[u],t);
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(v==fa[u]||v==son[u]) continue;
            dfs2(v,v);
        }
    }
    inline void input(){
        for(int v=2;v<=n;++v) fa[v]=read()+1;
        for(int v=2;v<=n;++v){
            int w=read();
            add_edge(fa[v],v,w);
        }
        dfs1(1,0);
        dfs2(1,1);
    }
    inline int get_LCA(int u,int v){
        while(top[u]!=top[v]){
            if(dep[top[u]]>dep[top[v]]) swap(u,v);
            v=fa[top[v]];
        }
        if(dep[u]>dep[v]) swap(u,v);
        return u;
    }
    inline ll get_dis(int u,int v){
        int lca=get_LCA(u,v);
        return dis[u]+dis[v]-2*dis[lca];
    }
}
namespace CDD{
    int cd;
    bool vis[maxn];
    int siz[maxn],maxsiz[maxn],sumsiz;
    int fa[maxn];
    void dfs_siz(int u,int f){
        siz[u]=1;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(v==f||vis[v]) continue;
            dfs_siz(v,u);
            siz[u]+=siz[v];
        }
    }
    inline void init(int u){
        cd=0,sumsiz=siz[u];
    }
    void dfs_cd(int u,int f){
        maxsiz[u]=0;
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(v==f||vis[v]) continue;
            dfs_cd(v,u);
            maxsiz[u]=max(maxsiz[u],siz[v]);
        }
        maxsiz[u]=max(maxsiz[u],sumsiz-siz[u]);
        if(!cd||maxsiz[u]<maxsiz[cd]) cd=u;
    }
    void build(int u){
        vis[u]=1;
        dfs_siz(u,0);
        for(int i=head[u];i;i=e[i].nxt){
            int v=e[i].to;
            if(vis[v]) continue;
            init(v);
            dfs_cd(v,0);
            fa[cd]=u;
            build(cd);
        }
    }
    int col[maxn];
    int cntu[maxn],cntf[maxn];
    ll sumu[maxn],sumf[maxn];
    inline void update(int u){
        for(int last=0,now=u;now;last=now,now=fa[now]){
            ll d=HLD::get_dis(now,u);
            ++cntu[now],sumu[now]+=d;
            if(last) ++cntf[last],sumf[last]+=d;
        }
    }
    inline void query(int u){
        ll res=0;
        for(int last=0,now=u;now;last=now,now=fa[now]){
            ll d=HLD::get_dis(now,u);
            res+=d*cntu[now]+sumu[now];
            if(last) res-=d*cntf[last]+sumf[last];
        }
        printf("%lld\n",res);
    }
    inline void solve(){
        dfs_siz(1,0);
        init(1);
        dfs_cd(1,0);
        build(cd);
        while(m--){
            int opt=read(),u=read()+1;
            if(opt==1){
                if(col[u]) continue;
                col[u]=1;
                update(u);
            }
            else query(u);
        }
    }
}
int main(){
    freopen("color.in","r",stdin);
    freopen("color.out","w",stdout);
    n=read(),m=read();
    HLD::input();
    CDD::solve();
    return 0;
}

T2 寻宝游戏

题目暗示了做法。

考虑将判定的射线定义成由右下到左上且与水平方向夹角极小的射线,这样不会经过任何端点。

\(f_{i,j,S}\) 为到 \((i,j)\) 且每个宝藏或陷阱对应射线经过次数的奇偶状态为 \(S\) 的最小步数,转移类似 BFS,更改 \(S\) 时直接枚举每个宝藏或陷阱的位置。

点击查看代码
int n,m;
char s[25][25];
vector<pii> V;
int v[10];
int sx,sy,dx[4]={1,-1,0,0},dy[4]={0,0,1,-1};
int f[25][25][(1<<8)+10];
struct node{
    int x,y,st;
    node()=default;
    node(int x_,int y_,int st_):x(x_),y(y_),st(st_){}
};
queue<node> q;
int ans;
int main(){
    freopen("game.in","r",stdin);
    freopen("game.out","w",stdout);
    n=read(),m=read();
    for(int i=1;i<=n;++i){
        scanf("%s",s[i]+1);
        for(int j=1;j<=m;++j){
            if(s[i][j]>='1'&&s[i][j]<='8'){
                V.push_back(make_pair(i,j));
            }
        }
    }
    for(int i=1;i<=(int)V.size();++i) v[i]=read();
    for(int i=1;i<=n;++i){
        for(int j=1;j<=m;++j){
            if(s[i][j]=='B') V.push_back(make_pair(i,j));
            if(s[i][j]=='S') sx=i,sy=j;
        }
    }
    memset(f,0x3f,sizeof(f));
    f[sx][sy][0]=0;
    q.push(node(sx,sy,0));
    while(!q.empty()){
        int x=q.front().x,y=q.front().y,st=q.front().st;
        q.pop();
        for(int i=0;i<4;++i){
            int xx=x+dx[i],yy=y+dy[i];
            if(s[xx][yy]!='.'&&s[xx][yy]!='S') continue;
            int now=st;
            for(int j=0;j<(int)V.size();++j){
                if(x!=xx&&V[j].sec>y){
                    if(V[j].fir==max(x,xx)) now^=(1<<j);
                }
            }
            if(f[x][y][st]+1<f[xx][yy][now]){
                f[xx][yy][now]=f[x][y][st]+1;
                q.push(node(xx,yy,now));
            }
        }
    }
    for(int i=0;i<(1<<(int)V.size());++i){
        bool chk=1;
        int sum=0;
        for(int j=0;j<(int)V.size();++j){
            if(!(i&(1<<j))) continue;
            if(s[V[j].fir][V[j].sec]=='B') chk=0;
            else sum+=v[s[V[j].fir][V[j].sec]-'0'];
        }
        if(chk){
            ans=max(ans,sum-f[sx][sy][i]);
        }
    }
    printf("%d\n",ans);
    return 0;
}

T3 点分治

考虑树形 DP,重点是如何合并。

模拟将 \(u,v\) 两子树合并后的一个分治过程,注意到和合并之前有分别的只有 \(u,v\) 共同的连通块,因此只考虑和这个连通块有关的删除操作。在 \(u\) 子树的删除操作等价于合并前在 \(u\) 子树删除 \(u\) 所在连通块节点的操作,\(v\) 子树同理,因此这个连通块的删除顺序就是两个子树根所在连通块的删除顺序归并起来。

\(f_{u,i}\)\(u\) 子树的分治过程中,子树内节点(包括 \(u\))在删去时与 \(u\) 连通的有 \(i\) 个的方案数。转移过程即枚举第二维后考虑 \(v\) 中有多少在 \(u\) 之前删去,可以后缀和优化。

点击查看代码
int n;
vector<int> E[maxn];
int C[maxn][maxn];
int siz[maxn];
int f[maxn][maxn],tmpf[maxn];
void dfs(int u,int fa){
    siz[u]=1;
    f[u][1]=1;
    for(int v:E[u]){
        if(v==fa) continue;
        dfs(v,u);
        for(int i=1;i<=siz[u]+siz[v];++i) tmpf[i]=0;
        for(int i=1;i<=siz[u];++i){
            for(int j=0;j<=siz[v];++j){
                tmpf[i+j]=(tmpf[i+j]+1ll*f[u][i]*f[v][j]%mod*C[i+j-1][i-1]%mod)%mod;
            }
        }
        siz[u]+=siz[v];
        for(int i=1;i<=siz[u];++i) f[u][i]=tmpf[i];
    }
    for(int i=siz[u];i>=0;--i) f[u][i]=(f[u][i+1]+f[u][i])%mod;
}

int main(){
    freopen("dianfen.in","r",stdin);
    freopen("dianfen.out","w",stdout);
    n=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        E[u].push_back(v);
        E[v].push_back(u);
    }
    C[0][0]=1;
    for(int i=1;i<=n;++i){
        C[i][0]=C[i][i]=1;
        for(int j=1;j<i;++j) C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
    }
    dfs(1,0);
    printf("%d\n",f[1][0]);
    return 0;
}

7.12 冲刺国赛模拟 35

T1 树上游戏

\(P\) 的前两个限制改成无序点对数量减去满足 \(i\)\(j\) 的祖先且 \(w_i\le w_j\) 的点对 \((i,j)\) 数量。

考虑初始都在 \(G\) 中,每次移动一个节点 \(x\)

这样 \(S(G)\) 的减少量是 \(x\) 子树内大于 \(w_x\) 的个数以及 \(x\) 祖先中小于 \(w_x\) 的个数,而 \(S(P)\) 的增加量是加入前 \(V(p)\) 的大小减去 \(x\) 子树内大于等于 \(w_x\) 的个数以及 \(x\) 祖先中小于等于 \(w_x\) 的个数,在此基础上再加上 \(dep_x\)。注意到这两个变化量十分相似,只差一个“等于”。

如果 \(w\) 各不相同,两个集合的并集是全集,所以对于每个 \(x\),这个总变化量不会随加入时刻改变,就可以贪心算然后排序。

\(w\) 相同,容易发现两个存在祖先关系且 \(w\) 相同的节点一定选深度小的更优,证明考虑假设两个节点路径上没有其他 \(w\) 相同的节点,那么深度更小的子树更大,因此可以减去的贡献更多。所以从上向下取,那么变化量修改为子树内大于等于的个数以及祖先中小于的个数。

点击查看代码
int n;
int a[maxn];
struct edge{
    int to,nxt;
}e[maxn<<1];
int head[maxn],cnt;
inline void add_edge(int u,int v){
    e[++cnt].to=v,e[cnt].nxt=head[u],head[u]=cnt;
    e[++cnt].to=u,e[cnt].nxt=head[v],head[v]=cnt;
}

int fa[maxn],dep[maxn],siz[maxn],dfn[maxn],dfncnt;
void dfs(int u,int f,int d){
    fa[u]=f,dep[u]=d,siz[u]=1,dfn[u]=++dfncnt;
    for(int i=head[u];i;i=e[i].nxt){
        int v=e[i].to;
        if(v==f) continue;
        dfs(v,u,d+1);
        siz[u]+=siz[v];
    }
}

int id[maxn];
struct SegmentTree{
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
    ll sum[maxn<<2];
    int tag[maxn<<2];
    inline void push_up(int rt){
        sum[rt]=sum[rt<<1]+sum[rt<<1|1];
    }
    inline void push_down(int rt,int l,int r){
        if(tag[rt]){
            sum[rt<<1]+=1ll*(mid-l+1)*tag[rt],tag[rt<<1]+=tag[rt];
            sum[rt<<1|1]+=1ll*(r-mid)*tag[rt],tag[rt<<1|1]+=tag[rt];
            tag[rt]=0;
        }
    }
    void build(int rt,int l,int r){
        sum[rt]=0,tag[rt]=0;
        if(l==r) return;
        build(lson),build(rson);
    }
    void update(int rt,int l,int r,int pl,int pr){
        if(pl<=l&&r<=pr){
            sum[rt]+=(r-l+1),++tag[rt];
            return;
        }
        push_down(rt,l,r);
        if(pl<=mid) update(lson,pl,pr);
        if(pr>mid) update(rson,pl,pr);
        push_up(rt);
    }
    ll query(int rt,int l,int r,int pl,int pr){
        if(pl<=l&&r<=pr) return sum[rt];
        push_down(rt,l,r);
        ll res=0;
        if(pl<=mid) res+=query(lson,pl,pr);
        if(pr>mid) res+=query(rson,pl,pr); 
        return res;
    }
#undef mid
#undef lson
#undef rson
}S;

ll val[maxn],ans[maxn];

int main(){
    freopen("tree.in","r",stdin);
    freopen("tree.out","w",stdout);
    n=read();
    for(int i=1;i<=n;++i) a[i]=read();
    for(int i=1;i<n;++i){
        int u=read(),v=read();
        add_edge(u,v);
    }
    dfs(1,0,0);
    for(int i=1;i<=n;++i) id[i]=i;
    sort(id+1,id+n+1,[&](int x,int y){
        return a[x]<a[y];
    });
    S.build(1,1,n);
    for(int i=1;i<=n;){
        int j=i;
        while(j<=n&&a[id[j]]==a[id[i]]){
            S.update(1,1,n,dfn[id[j]],dfn[id[j]]+siz[id[j]]-1);
            ++j;
        }
        j=i;
        while(j<=n&&a[id[j]]==a[id[i]]){
            val[id[j]]-=S.query(1,1,n,dfn[id[j]],dfn[id[j]])-1;
            ++j;
        }
        i=j;
    }
    S.build(1,1,n);
    for(int i=n;i>=1;){
        int j=i;
        while(j>=1&&a[id[j]]==a[id[i]]){
            ll now=S.query(1,1,n,dfn[id[j]],dfn[id[j]]+siz[id[j]]-1);
            val[id[j]]-=now,ans[n]+=now;
            --j;
        }
        j=i;
        while(j>=1&&a[id[j]]==a[id[i]]){
            S.update(1,1,n,dfn[id[j]],dfn[id[j]]);
            --j;
        }
        i=j;
    }
    for(int i=1;i<=n;++i) val[i]+=dep[i];
    sort(id+1,id+n+1,[&](int x,int y){
        return val[x]>val[y];
    });
    for(int i=n;i>=1;--i){
        ans[i-1]=ans[i]+(n-i);
        ans[i-1]+=val[id[i]];
    }
    for(int i=0;i<=n;++i) printf("%lld ",ans[i]);
    printf("\n");
    return 0;
}

T2 土地划分

将有交的矩形连边,就是求两两无连边的三元生成子图个数,正难则反,分别设 \(S_{0/1/2/3}\) 为边数为 \(0/1/2/3\) 的三元生成子图个数,那么 \(S_0=\dbinom{n}{3}-S_1-S_2-S_3\)

容易发现有 \(1\) 条或 \(2\) 条边的情况存在共同特征:存在两个节点只有一条连边,那么考虑求出每个节点的度数 \(deg_i\),则 \(2S_1+2S_2=\sum deg(n-1-deg)\)

快速计算度数即扫描先求与每个矩形有交的个数,分矩形 \(i,j\) 的左边界大小关系讨论:

  • \(l_i=l_j\),将 \(l\) 相等的全部加入数据结构,计算答案单步容斥,总矩形数减去 \(d_j<u_i\) 以及 \(u_j>d_i\) 的数量,两个树状数组即可。

  • \(l_i>l_j\),朴素扫描线在 \(l_j\) 处加入 \(r_j\) 处删去,在 \(l_i\) 处查询。

  • \(l_i<l_j\),那么实际上就是查询 \(l_i<l_j\le r_i\)\(j\) 有多少,注意到可以看作前缀和,那么分别在 \(l_i,r_i\) 计算两次相减即可。

最后是两两有交的情况,考虑在左边界最大位置统计答案,依旧是扫描线的做法,倘若数据结构支持查询目前加入的线段中两两有交的三元组个数,那么只需要加入之后减去加入之前的就是在当前位置贡献的答案。

计算三元组个数设 \(val_i=\sum_{j\in S} [l_j\le i\le r_j],val_i'=\sum_{j\in S} [l_j\le i<r_j]\),这样 \(\sum\dbinom{val_i}{3}-\sum\dbinom{val_i'}{3}\) 即为答案。正确性在于同一个三元组在 \(\dbinom{val}{3}\) 中被计算长度次,不考虑右端点则少计算一次,二者相减不重不漏。

于是数据结构维护 \(\dbinom{val_i}{3}\),要求支持区间加。根据范德蒙德卷积有:

\[\dbinom{x+y}{k}=\sum_{i=0}^k\dbinom{x}{i}\dbinom{y}{k-i} \]

需要广义二项式系数,分别维护。

总复杂度 \(O(n\log n)\),常数极大。

点击查看代码
int n;
struct Rectangle{
    int l,r,u,d;
    Rectangle()=default;
    Rectangle(int l_,int r_,int u_,int d_):l(l_),r(r_),u(u_),d(d_){}
}Rc[maxn];
struct Segment{
    int x,u,d,id;
    bool type;
    Segment()=default;
    Segment(int x_,int u_,int d_,int id_,bool type_):x(x_),u(u_),d(d_),id(id_),type(type_){}
}Sg[maxn<<1];
vector<int> Vx,Vy;
int X,Y;

struct BinaryIndexedTree{
#define lowbit(x) (x&-x)
    int sum[maxn<<1];
    inline void update(int x,int k){
        while(x<=Y){
            sum[x]+=k;
            x+=lowbit(x);
        }
    }
    inline int query(int x){
        if(!x||x>Y) return 0;
        int res=0;
        while(x){
            res+=sum[x];
            x-=lowbit(x);
        }
        return res;
    }
#undef lowbit
}Bu,Bd;

struct SegmentTree{
#define mid ((l+r)>>1)
#define lson rt<<1,l,mid
#define rson rt<<1|1,mid+1,r
    struct Data{
        ll C[4];
        ll& operator[](const int &i){return C[i];}
        ll operator[](const int &i)const{return C[i];}
    }val[maxn<<3];
    int tag[maxn<<3];
    inline void calc(Data &A,Data B,Data C){
        A[0]=B[0]*C[0];
        A[1]=B[0]*C[1]+B[1]*C[0];
        A[2]=B[0]*C[2]+B[1]*C[1]+B[2]*C[0];
        A[3]=B[0]*C[3]+B[1]*C[2]+B[2]*C[1]+B[3]*C[0];
    }
    inline Data get_Data(int k){
        Data res;
        res[0]=1,res[1]=1ll*k/1,res[2]=1ll*k*(k-1)/2,res[3]=1ll*k*(k-1)*(k-2)/6;
        return res;
    }
    inline void push_up(int rt){
        for(int i=0;i<4;++i) val[rt][i]=val[rt<<1][i]+val[rt<<1|1][i];
    }
    inline void push_down(int rt){
        if(tag[rt]){
            Data now=get_Data(tag[rt]);
            calc(val[rt<<1],val[rt<<1],now),calc(val[rt<<1|1],val[rt<<1|1],now);
            tag[rt<<1]+=tag[rt],tag[rt<<1|1]+=tag[rt];
            tag[rt]=0;
        }
    }
    void build(int rt,int l,int r){
        if(l==r) return val[rt][0]=1,void();
        build(lson),build(rson);
        push_up(rt);
    }
    void update(int rt,int l,int r,int pl,int pr,int k){
        if(pl<=l&&r<=pr){
            Data now=get_Data(k);
            calc(val[rt],val[rt],now);
            tag[rt]+=k;
            return;
        }
        push_down(rt);
        if(pl<=mid) update(lson,pl,pr,k);
        if(pr>mid) update(rson,pl,pr,k);
        push_up(rt);
    }
#undef mid
#undef lson
#undef rson
}S1,S2;

int deg[maxn];
ll ans;

int main(){
    freopen("land.in","r",stdin);
    freopen("land.out","w",stdout);
    n=read();
    for(int i=1,l,r,u,d;i<=n;++i){
        l=read(),r=read(),u=read(),d=read();
        Rc[i]=Rectangle(l,r,u,d);
        Vx.push_back(l),Vx.push_back(r);
        Vy.push_back(u),Vy.push_back(d);
    }
    sort(Vx.begin(),Vx.end());
    Vx.erase(unique(Vx.begin(),Vx.end()),Vx.end());
    sort(Vy.begin(),Vy.end());
    Vy.erase(unique(Vy.begin(),Vy.end()),Vy.end());
    X=Vx.size(),Y=Vy.size();
    for(int i=1;i<=n;++i){
        Rc[i].l=lower_bound(Vx.begin(),Vx.end(),Rc[i].l)-Vx.begin()+1;
        Rc[i].r=lower_bound(Vx.begin(),Vx.end(),Rc[i].r)-Vx.begin()+1;
        Rc[i].u=lower_bound(Vy.begin(),Vy.end(),Rc[i].u)-Vy.begin()+1;
        Rc[i].d=lower_bound(Vy.begin(),Vy.end(),Rc[i].d)-Vy.begin()+1;
        Sg[2*i-1]=Segment(Rc[i].l,Rc[i].u,Rc[i].d,i,0);
        Sg[2*i]=Segment(Rc[i].r,Rc[i].u,Rc[i].d,i,1);
    }
    sort(Sg+1,Sg+2*n+1,[&](Segment A,Segment B){
        if(A.x==B.x) return A.type<B.type;
        return A.x<B.x;
    });
    for(int i=1,j,cnt=0;i<=2*n;){
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            deg[Sg[j].id]+=cnt-Bd.query(Sg[j].u-1)-(cnt-Bu.query(Sg[j].d));
            ++j;
        }
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            Bu.update(Sg[j].u,1),Bd.update(Sg[j].d,1);
            ++cnt;
            ++j;
        }
        while(j<=2*n&&Sg[j].x==Sg[i].x&&Sg[j].type){
            Bu.update(Sg[j].u,-1),Bd.update(Sg[j].d,-1);
            --cnt;
            ++j;
        }
        i=j;
    }
    for(int i=1,j,cnt=0;i<=2*n;){
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            Bu.update(Sg[j].u,1),Bd.update(Sg[j].d,1);
            ++cnt;
            ++j;  
        }
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            deg[Sg[j].id]-=cnt-Bd.query(Sg[j].u-1)-(cnt-Bu.query(Sg[j].d));
            ++j;
        }
        while(j<=2*n&&Sg[j].x==Sg[i].x&&Sg[j].type){
            deg[Sg[j].id]+=cnt-Bd.query(Sg[j].u-1)-(cnt-Bu.query(Sg[j].d));
            ++j;
        }
        i=j;
    }
    for(int i=1;i<=2*n;++i){
        if(!Sg[i].type) Bu.update(Sg[i].u,-1),Bd.update(Sg[i].d,-1);
    }
    for(int i=1,j,cnt=0;i<=2*n;){
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            Bu.update(Sg[j].u,1),Bd.update(Sg[j].d,1);
            ++cnt;
            ++j;  
        }
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            deg[Sg[j].id]+=cnt-1-Bd.query(Sg[j].u-1)-(cnt-Bu.query(Sg[j].d));
            ++j;
        }
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            Bu.update(Sg[j].u,-1),Bd.update(Sg[j].d,-1);
            --cnt;
            ++j;  
        }
        while(j<=2*n&&Sg[j].x==Sg[i].x&&Sg[j].type) ++j;
        i=j;
    }
    for(int i=1;i<=n;++i) ans-=1ll*deg[i]*(n-1-deg[i]);
    ans/=2;
    ans+=1ll*n*(n-1)*(n-2)/6;
    S1.build(1,1,Y);
    S2.build(1,1,Y);
    for(int i=1,j,cnt=0;i<=2*n;){
        j=i;
        while(j<=2*n&&Sg[j].x==Sg[i].x&&!Sg[j].type){
            ans+=S1.val[1][3]-S2.val[1][3];
            S1.update(1,1,Y,Sg[j].u,Sg[j].d,1);
            if(Sg[j].u<Sg[j].d) S2.update(1,1,Y,Sg[j].u,Sg[j].d-1,1);
            ans-=S1.val[1][3]-S2.val[1][3];
            ++j;
        }
        while(j<=2*n&&Sg[j].x==Sg[i].x&&Sg[j].type){
            S1.update(1,1,Y,Sg[j].u,Sg[j].d,-1);
            if(Sg[j].u<Sg[j].d) S2.update(1,1,Y,Sg[j].u,Sg[j].d-1,-1);
            ++j;
        }
        i=j;
    }
    printf("%lld\n",ans);
    return 0;
}

T3 算法考试

古典概型,求方案数即可。

中位数的一个标准做法是枚举中位数 \(x\),大于设 \(1\),小于设 \(-1\),等于设 \(0\),只需判断 \(0\) 是不是中位数。

由于算法流程是一个分治树形式,考虑在上面 DP,设 \(f_{u,i,j,-1/0/1}\) 表示 \(u\) 节点内有 \(i\) 个位置填 \(-1\)\(j\) 个位置填 \(1\),且分治最后贡献出 \(-1/0/1\) 的方案数。最后求出来再乘上一个 \(x^i(m-x)^j\) 的系数。这样复杂度 \(O((n+k^4)m)\)

注意到对于一段连续区间,DP 的结果是相同的,因此离散化后批量计算,这时系数的前缀和是一个 \(i+j+1\) 次多项式,拉插处理,复杂度 \(O((n+k^4)n)\)

中位数另一个性质是插入 \(k\) 个数中位数最多移动 \(k\) 位,因此只枚举原序列中位数左右 \(k\) 个区间即可,复杂度 \(O(nk+k^5)\),可以通过。

点击查看代码
inline int q_pow(int A,int B,int P){
    int res=1;
    while(B){
        if(B&1) res=1ll*res*A%P;
        A=1ll*A*A%P;
        B>>=1;
    }
    return res;
}

int n,m,k;
int a[maxn],b[maxn],sum[maxn];
vector<int> V;
vector<int> f[maxn][3];
int tmpf[maxk][maxk][9];
int son[maxn][3],L[maxn],R[maxn],siz[maxn],tot;
void dfs(int u,int l,int r){
    L[u]=l,R[u]=r,siz[u]=sum[r]-sum[l-1];
    f[u][0].resize((siz[u]+1)*(siz[u]+1)),f[u][1].resize((siz[u]+1)*(siz[u]+1)),f[u][2].resize((siz[u]+1)*(siz[u]+1));
    if(r-l<=1) return;
    int len=r-l+1;
    son[u][0]=++tot,son[u][1]=++tot,son[u][2]=++tot;
    dfs(son[u][0],l,l+(len-1)/3),dfs(son[u][1],l+(len-1)/3+1,r-len/3),dfs(son[u][2],r-len/3+1,r);
}
inline int check(int x,int y){
    if(x<y) return 0;
    else if(x==y) return 1;
    else return 2;
}
inline int id(int u,int k1,int k2){
    return k1*(siz[u]+1)+k2;
}
void solve(int u,int x){
    int l=L[u],r=R[u];
    if(l==r){
        for(int i=0;i<=siz[u];++i){
            for(int j=0;i+j<=siz[u];++j){
                for(int t=0;t<=2;++t) f[u][t][id(u,i,j)]=0;
                for(int t=0;t<=8;++t) tmpf[i][j][t]=0;
            }
        }
        if(a[l]==-1) f[u][0][id(u,1,0)]=1,f[u][1][id(u,0,0)]=1,f[u][2][id(u,0,1)]=1;
        else f[u][check(a[l],x)][id(u,0,0)]=1;
        return;
    }
    else if(l+1==r){
        for(int i=0;i<=siz[u];++i){
            for(int j=0;i+j<=siz[u];++j){
                for(int t=0;t<=2;++t) f[u][t][id(u,i,j)]=0;
                for(int t=0;t<=8;++t) tmpf[i][j][t]=0;
            }
        }
        if(a[l]==-1&&a[r]==-1) f[u][0][id(u,2,0)]=1,f[u][0][id(u,1,0)]=2,f[u][0][id(u,1,1)]=2,f[u][1][id(u,0,0)]=1,f[u][1][id(u,0,1)]=2,f[u][2][id(u,0,2)]=1;
        else if(a[l]==-1||a[r]==-1){
            if(max(a[l],a[r])<x) f[u][0][id(u,1,0)]=1,f[u][0][id(u,0,0)]=1,f[u][0][id(u,0,1)]=1;
            else if(max(a[l],a[r])==x) f[u][0][id(u,1,0)]=1,f[u][1][id(u,0,0)]=1,f[u][1][id(u,0,1)]=1;
            else f[u][0][id(u,1,0)]=1,f[u][1][id(u,0,0)]=1,f[u][2][id(u,0,1)]=1;
        }
        else f[u][min(check(a[l],x),check(a[r],x))][id(u,0,0)]=1;
        return;
    }
    int v0=son[u][0],v1=son[u][1],v2=son[u][2];
    solve(v0,x),solve(v1,x),solve(v2,x);
    for(int i=0;i<=siz[u];++i){
        for(int j=0;i+j<=siz[u];++j){
            for(int t=0;t<=2;++t) f[u][t][id(u,i,j)]=0;
            for(int t=0;t<=8;++t) tmpf[i][j][t]=0;
        }
    }
    for(int k1=0;k1<=siz[v0];++k1){
        for(int k2=0;k1+k2<=siz[v0];++k2){
            for(int k3=0;k3<=siz[v1];++k3){
                for(int k4=0;k3+k4<=siz[v1];++k4){
                    for(int t0=0;t0<=2;++t0){
                        for(int t1=0;t1<=2;++t1){
                            tmpf[k1+k3][k2+k4][t0*3+t1]=(tmpf[k1+k3][k2+k4][t0*3+t1]+1ll*f[v0][t0][id(v0,k1,k2)]*f[v1][t1][id(v1,k3,k4)]%mod)%mod;
                        }
                    }
                }
            }
        }
    }
    for(int k1=0;k1<=siz[v0]+siz[v1];++k1){
        for(int k2=0;k1+k2<=siz[v0]+siz[v1];++k2){
            for(int k3=0;k3<=siz[v2];++k3){
                for(int k4=0;k3+k4<=siz[v2];++k4){
                    for(int t=0;t<=8;++t){
                        int t0=t/3,t1=t%3;
                        for(int t2=0;t2<=2;++t2){
                            int t3=t0+t1+t2-max({t0,t1,t2})-min({t0,t1,t2});
                            f[u][t3][id(u,k1+k3,k2+k4)]=(f[u][t3][id(u,k1+k3,k2+k4)]+1ll*tmpf[k1][k2][t]*f[v2][t2][id(v2,k3,k4)]%mod)%mod;
                        }
                    }
                }
            }
        }
    }
}

int fact[maxk],inv_fact[maxk];
int pre[maxk],suf[maxk];
struct Lagrange{
    int Y[maxk];
    inline void init(int k1,int k2){
        for(int i=0;i<=k+1;++i){
            if(i) Y[i]=Y[i-1];
            Y[i]=(Y[i]+1ll*q_pow(i,k1,mod)*q_pow(m-i,k2,mod)%mod+mod)%mod;
        }
    }
    inline int get_Y(int X){
        int res=0;
        pre[0]=1,suf[k+1]=1;
        for(int i=1;i<=k+1;++i) pre[i]=1ll*pre[i-1]*(X-(i-1)+mod)%mod;
        for(int i=k;i>=0;--i) suf[i]=1ll*suf[i+1]*(X-(i+1)+mod)%mod;
        for(int i=0;i<=k+1;++i){
            int now=1ll*Y[i]*pre[i]%mod*suf[i]%mod*inv_fact[i]%mod*inv_fact[k+1-i]%mod;
            if((k+1-i)&1) res=(res-now+mod)%mod;
            else res=(res+now)%mod;
        }
        return res;
    }
}LG[41][41];
int ans;
int main(){
    freopen("algorithm.in","r",stdin);
    freopen("algorithm.out","w",stdout);
    n=read(),m=read();
    V.push_back(0),V.push_back(m);
    for(int i=1;i<=n;++i){
        a[i]=read();
        sum[i]=sum[i-1]+(a[i]==-1);
        if(a[i]!=-1){
            V.push_back(a[i]);
            if(a[i]<m) V.push_back(a[i]+1);
            b[++b[0]]=a[i];
        }
    }
    sort(V.begin(),V.end());
    V.erase(unique(V.begin(),V.end()),V.end());
    sort(b+1,b+b[0]+1);
    int mid=b[(b[0]+1)/2];
    k=sum[n];
    tot=1;
    dfs(1,1,n);
    fact[0]=1,inv_fact[0]=1;
    for(int i=1;i<=k+1;++i) fact[i]=1ll*fact[i-1]*i%mod;
    inv_fact[k+1]=q_pow(fact[k+1],mod-2,mod);
    for(int i=k;i>=1;--i) inv_fact[i]=1ll*inv_fact[i+1]*(i+1)%mod;
    for(int i=0;i<=k;++i){
        for(int j=0;i+j<=k;++j){
            LG[i][j].init(i,j);
        }
    }
    int p=lower_bound(V.begin(),V.end(),mid)-V.begin();
    for(int i=max(p-k,0);i<=min(p+k,(int)V.size()-1);++i){
        int x=V[i];
        int cnt0=0,cnt2=0;
        for(int j=1;j<=n;++j){
            cnt0+=(a[j]!=-1&&a[j]<x);
            cnt2+=(a[j]!=-1&&a[j]>x);   
        }
        solve(1,x);
        int nxt=(x==m)?m:V[i+1]-1; 
        for(int k1=0;k1<=k;++k1){
            for(int k2=0;k1+k2<=k;++k2){
                if(n&1){
                    if(k1+cnt0>=(n+1)/2) continue;
                    if(k2+cnt2>=(n+1)/2) continue;
                }
                else{
                    if(k1+cnt0>=n/2) continue;
                    if(k2+cnt2>=n/2+1) continue;
                }
                int sumpw=LG[k1][k2].get_Y(nxt);
                if(x) sumpw=(sumpw-LG[k1][k2].get_Y(x-1)+mod)%mod;
                int bf=0;
                for(int j=x;j<=nxt;++j){
                    bf=(bf+1ll*q_pow(j,k1,mod)*q_pow(m-j,k2,mod)%mod)%mod;
                }
                ans=(ans+1ll*f[1][1][id(1,k1,k2)]*sumpw%mod)%mod;
            }
        }
    }
    ans=1ll*ans*q_pow(q_pow(m+1,k,mod),mod-2,mod)%mod;
    printf("%d\n",ans);
    return 0;
}
posted @ 2023-07-10 15:40  SoyTony  阅读(42)  评论(0编辑  收藏  举报