2018.11.01-dtoj-4020-式神守护(yukari)———关于错排

题目描述:

幽幽子站在西行妖下,她需要解封西行妖最后的力量。
西行妖可以当作一个有n 个点的树,每个点都有一些符文,初始每个点符文个数为1。
幽幽子可以施展魔法,将符文随机移动,来解封封印。
每个点上的符文可以看作是一个1~m 的排列,原本的状态为1,2,3,4,……,m 按顺序排列
(m 为符文的个数)。想要解封一个点上的封印,要求排列中对于任意的i, p i i  。幽幽子
每次的魔法效果是随机形成一个排列,尝试能否解除封印。
幽幽子每次会走过一条路径,从s 到t,对每个点施展一次魔法,并询问能解封的点的
期望个数。
现在有Q 次操作:
Add s t x 在s 到t 的路径上每个点加x 个新的符文。
Multi s t x 在s 到t 的路径上,每个点符文个数*x。
Query s t 求从s 到t 解封点的期望个数是多少。
(注意:每次Query 操作是独立的,即前一次Query 中施展的魔法在Query 结束时被立
即撤销,所有走过的路径上点的符文排列变为p i i  ,对后续操作不产生影响)

输入:

第一行一个数n,表示树的点数。
下面n-1 行,每行两个数u,v,表示有一条边连接u 和v。
下面一行一个Q,表示操作次数。
下面Q 行,每行一个操作:
Add s t x 在s 到t 的路径上每个点加x 个符文。
Multi s t x 在s 到t 的路径上,每个点符文个数*x。
Query s t 求从s 到t 解封点的期望个数是多少。

输出:

对于每次询问Query,输出一行一个实数(为了避免卡精度,所以只需要保留1 位小数),
表示解封点的期望个数。

数据范围:

n,m≤8*104

算法标签:树链剖分

小延伸——关于错排:

错排的递推式子:

D(n)=(D(n-1)+D(n-2))*(n-1)

因为你每次可以选择从n-1个已经满足错拍的排列中选择任意一个数与当前新加入的数进行交换,或者从n-2的已经满足的排列中,选择一个本在原位的数字的新加入的数字交换位置。

思路:

打表或者感性认知发现,当字符的序列达到一定长度后单个数字的贡献会逐渐接近一个数,且修改操作除了*1特判,其他必然增大,计算发现精度范围内增大仍有变化的分界点大概是20,因此操作的时候一旦字符长度高于20,我们就不再进行操作,因此每个数至多被修改20次,效率可观。区间操作用树剖维护。

以下代码:
#include<bits/stdc++.h>
#define il inline
#define D double
#define _(d) while(d(isdigit(ch=getchar())))
using namespace std;
const int N=8e4+5;
char s[10];
D kk[N],jc[N],sum[N<<2];
int n;
int head[N],ne[N<<1],to[N<<1],cnt;
int fa[N],d[N],sz[N],son[N],top[N],dfn[N],tot,minn[N<<2];
il int read(){int x,f=1;char ch;_(!)ch=='-'?f=-1:f;x=ch^48;_()x=(x<<1)+(x<<3)+(ch^48);return f*x;}
il void insert(int x,int y){ne[++cnt]=head[x];head[x]=cnt;to[cnt]=y;}
void dfs1(int x){
    sz[x]=1;int maxn=-1;
    for(int i=head[x];i;i=ne[i]){
        if(fa[x]==to[i])continue;
        d[to[i]]=d[x]+1;fa[to[i]]=x;
        dfs1(to[i]);sz[x]+=sz[to[i]];
        if(maxn<sz[to[i]])maxn=sz[to[i]],son[x]=to[i];
    }
}
void dfs2(int x,int rt){
    dfn[x]=++tot;top[x]=rt;
    if(!son[x])return;
    dfs2(son[x],rt);
    for(int i=head[x];i;i=ne[i]){
        if(dfn[to[i]])continue;
        dfs2(to[i],to[i]);
    }
}
il void update(int x){
    minn[x]=min(minn[x<<1],minn[x<<1|1]);
    sum[x]=sum[x<<1]+sum[x<<1|1];
}
il D Iq(int x,int l,int r,int ql,int qr){
    if(ql<=l&&r<=qr)return sum[x];
    int mid=(l+r)>>1;D res=0.0;
    if(ql<=mid)res+=Iq(x<<1,l,mid,ql,qr);
    if(mid<qr)res+=Iq(x<<1|1,mid+1,r,ql,qr);
    return res;
}
il D Tq(int x,int y){
    D res=0.0;
    while(top[x]!=top[y]){
        if(d[top[x]]<d[top[y]])swap(x,y);
        res+=Iq(1,1,n,dfn[top[x]],dfn[x]);x=fa[top[x]];
    }
    if(d[x]>d[y])swap(x,y);
    res+=Iq(1,1,n,dfn[x],dfn[y]);
    return res;
}
void Ia(int x,int l,int r,int ql,int qr,int v){
    if(minn[x]>=20)return;
    if(l==r){
        minn[x]+=v;
        if(minn[x]>20)sum[x]=kk[20];
        else sum[x]=kk[minn[x]];
        return;
    }
    int mid=(l+r)>>1;
    if(ql<=mid)Ia(x<<1,l,mid,ql,qr,v);
    if(mid<qr)Ia(x<<1|1,mid+1,r,ql,qr,v);
    update(x);
}
il void Ta(int x,int y,int v){
    while(top[x]!=top[y]){
        if(d[top[x]]<d[top[y]])swap(x,y);
        Ia(1,1,n,dfn[top[x]],dfn[x],v);x=fa[top[x]];
    }
    if(d[x]>d[y])swap(x,y);
    Ia(1,1,n,dfn[x],dfn[y],v);
}
void Im(int x,int l,int r,int ql,int qr,int v){
    if(minn[x]>=20)return;
    if(l==r){
        minn[x]*=v;
        if(minn[x]>20)sum[x]=kk[20];
        else sum[x]=kk[minn[x]];
        return;
    }
    int mid=(l+r)>>1;
    if(ql<=mid)Im(x<<1,l,mid,ql,qr,v);
    if(mid<qr)Im(x<<1|1,mid+1,r,ql,qr,v);
    update(x);
}
il void Tm(int x,int y,int v){
    while(top[x]!=top[y]){
        if(d[top[x]]<d[top[y]])swap(x,y);
        Im(1,1,n,dfn[top[x]],dfn[x],v);x=fa[top[x]];
    }
    if(d[x]>d[y])swap(x,y);
    Im(1,1,n,dfn[x],dfn[y],v);
}
void build(int x,int l,int r){
    if(l==r){minn[x]=1;return;}
    int mid=(l+r)>>1;
    build(x<<1,l,mid);build(x<<1|1,mid+1,r);
    update(x);
}
int main()
{
    n=read();for(int i=1;i<n;i++){int x=read(),y=read();insert(x,y);insert(y,x);}
    d[1]=1;dfs1(1);dfs2(1,1);
    kk[0]=1.0;kk[1]=0.0;for(int i=2;i<=20;i++)kk[i]=(D)(i-1)*(kk[i-1]+kk[i-2]);
    jc[0]=1;for(int i=1;i<=20;i++)jc[i]=jc[i-1]*(D)i;
    for(int i=1;i<=20;i++)kk[i]=kk[i]/jc[i];build(1,1,n);
    int Q=read();while(Q--){
        scanf(" %s",&s);int x=read(),y=read();
        if(s[0]=='A'){
            int k=read();Ta(x,y,k);
        }
        else if(s[0]=='M'){
            int k=read();if(k!=1)Tm(x,y,k);
        }
        else if(s[0]=='Q'){
            printf("%.1lf\n",Tq(x,y));
        }
    }
  return 0;
}
View Code

 

转载于:https://www.cnblogs.com/Jessie-/p/9888772.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值