THUWC2017

大葱的神力

LOJ

在美妙的数学王国中畅游

Luogu
LOJ
BZOJ
比较裸。
看到删边断边还有路径修改查询基本上LCT稳了。
然后这东西直接做确实不太好做,但是下面泰勒展开都给你了,直接泰勒展开就完事了。
稍微估算一下发现答案挺小的,所以展开\(10\sim12\)项就差不多了。
我是傻逼。
注意修改的时候要先splay再修改,改完之后pushup。

#include<bits/stdc++.h>
#define db double
using namespace std;
int read(){int x=0,c=getchar();while(!isdigit(c))c=getchar();while(isdigit(c))x=x*10+c-48,c=getchar();return x;}
char fetch(){char c=getchar();while(!islower(c))c=getchar();return c;}
void swap(int &a,int &b){a^=b^=a^=b;}
const int N=100007;
int fa[N],ch[N][2],rev[N],fac[11],n,Q;db sum[N][11],f[N][11];
#define lc ch[x][0]
#define rc ch[x][1]
int nroot(int x){return ch[fa[x]][0]==x||ch[fa[x]][1]==x;}
void pushup(int x){for(int i=0;i<=10;++i)sum[x][i]=sum[lc][i]+sum[rc][i]+f[x][i];}
void pushrev(int x){swap(lc,rc),rev[x]^=1;}
void pushdown(int x){if(!rev[x])return;rev[x]=0,pushrev(lc),pushrev(rc);}
void pushall(int x){if(nroot(x))pushall(fa[x]);pushdown(x);}
void rotate(int x)
{
    int y=fa[x],z=fa[y],k=ch[y][1]==x;
    if(nroot(y)) ch[z][ch[z][1]==y]=x;
    fa[x]=z,fa[y]=x,fa[ch[x][!k]]=y,ch[y][k]=ch[x][!k],ch[x][!k]=y,pushup(y);
}
void splay(int x)
{
    pushall(x);
    for(int y;nroot(x);rotate(x)) if(nroot(y=fa[x])) rotate((ch[fa[y]][0]==y)^(ch[y][0]==x)? x:y);
    pushup(x);
}
void access(int x){for(int y=0;x;x=fa[y=x])splay(x),rc=y,pushup(x);}
void makeroot(int x){access(x),splay(x),pushrev(x);}
int findroot(int x){access(x),splay(x);while(lc)x=lc;return splay(x),x;}
void split(int x,int y){makeroot(x),access(y),splay(y);}
void link(int x,int y){makeroot(x),fa[x]=y;}
void cut(int x,int y){split(x,y),ch[y][0]=fa[x]=0,pushup(y);}
void modify(int p)
{
    int opt=read();db a,b;scanf("%lf%lf",&a,&b);
    splay(p);
    if(opt==3)
    {
	f[p][0]=b,f[p][1]=a;
	for(int i=2;i<=10;++i) f[p][i]=0;
    }
    if(opt==1)
    {
	db s=sin(b),c=cos(b),x=1;
	for(int i=0;i<=10;++i) f[p][i]=(i>>1&1? -1:1)*(i&1? c:s)*x/fac[i],x*=a;
    }
    if(opt==2)
    {
	db x=1,e=exp(b);
	for(int i=0;i<=10;++i) f[p][i]=x*e/fac[i],x*=a;
    }
    pushup(p);
}
void query(int u,int v)
{
    db a,x=1,ans=0;scanf("%lf",&a);
    if(findroot(u)^findroot(v)) return (void)(puts("unreachable"));
    split(u,v);
    for(int i=0;i<=10;++i) ans+=x*sum[v][i],x*=a;
    printf("%.10lf\n",ans);
}
int main()
{
    n=read(),Q=read(),read(),fac[0]=1;
    for(int i=1;i<=10;++i) fac[i]=i*fac[i-1];
    for(int i=1;i<=n;++i) modify(i);
    for(int u,v;Q;--Q)
	switch(fetch())
	{
	case 'a':u=read()+1,v=read()+1,link(u,v);break;
	case 'd':u=read()+1,v=read()+1,cut(u,v);break;
	case 'm':modify(read()+1);break;
	case 't':u=read()+1,v=read()+1,query(u,v);break;
	}
}

随机二分图

Luogu
LOJ
BZOJ
这个算法极其的诡异。
首先看到这题的第一反应是这个期望是个假的,我们可以枚举每种边的情况,但是这样的复杂度肯定过不了。
然后我们转而枚举匹配的情况,计算这个匹配出现的概率,这样的话状态数是\(30\choose15\)的,感性上是差不多能过的。
然后我们发现边组的情况不太好处理,所以我们先处理只有第一种边的情况。
然后我们发现这就是个裸的记忆化状压搜索,非常的好写。
一个需要注意的点是我们的枚举要做到不重不漏。
漏是不太可能了,我们考虑如何枚举才能够不重算。
首先我们应该给方案中的边排个序,否则这个边集会以不同的顺序被算很多次。按左端点排序就行了。
那么我们在转移的时候就只需要枚举左边编号最小的点的出边,然后往删掉这条边的状态转移就行了,注意要乘上\(\frac12\)的系数。
现在我们要想一个办法来处理边组。这也是本算法看起来最玄学的地方。
我们先不加证明地给出处理方式:
对于\(1\)类边组,我们先把它拆成两条\(0\)类边,然后再对于比较小的那条边(按上面给出的大小关系)给它加一个绑定,当我们枚举到这条边的时候有\(\frac14\)的概率直接选上这两条边。
对于\(2\)类边组把概率改为\(-\frac14\)就行了。
这个方法乍一看上去非常的玄学。我们结合我们记忆化搜索的过程理解。
如果我们强行认为这两条边独立并且只建两条\(0\)类边,那么第一条边有\(\frac12\)的概率单独出现在匹配中,第二条边也有\(\frac12\)的概率单独出现在匹配中,有\(\frac14\)的概率两条边同时出现在匹配中。
而真实的情况是,第一条边有\(\frac12\)的概率单独出现在匹配中,有\(\frac12\)的概率和第二条边同时出现在匹配中。为什么会有单独出现这种情况呢?因为我们考虑的是边出现在匹配中的概率而不是出现在二分图中的概率。
如果能够理解这点那么这个方法就不怎么玄学了,也就很好地说明了为什么我们选择建两条\(0\)类边再补一个\(\frac14\)的绑定边。
然后再按照上面的方法大力记忆化状压dp了就行了。
注意一点,如果我们要计算一条边所绑定的边带来的贡献,需要满足这条边的绑定边全部出现在了当前状态中,因为我们要同时把这些边全都删了。

#include<bits/stdc++.h>
#define ll long long
#define pb push_back
using namespace std;
int read(){int x;scanf("%d",&x);return x;}
const int N=15,P=1000000007,i2=500000004,i4=250000002,i34=750000005;
int inc(int a,int b){return a+=b,a>=P? a-P:a;}
int dec(int a,int b){return a-=b,a<0? a+P:a;}
int mul(int a,int b){return 1ll*a*b%P;}
int power(int a,int k){int r=1;for(;k;k>>=1,a=mul(a,a))if(k&1)r=mul(a,r);return r;}
int n,m,U,E[N],p[N][N],e[N][N];unordered_map<int,int>dp;
int dfs(int S)
{
    if(!S)return 1;
    if(dp.count(S)) return dp[S];
    int mn=0,ans=0,r;
    for(int i=0;i<n;++i) if(S>>i&1) {mn=i;break;}
    r=S>>n&E[mn];
    for(int i=0,k,x;i<n;++i)
        if(r>>i&1)
        {
            k=S^(1<<mn)^(1<<(n+i)),ans=inc(ans,mul(i2,dfs(k))),x=e[mn][i];
            if(x&&((x&k)==x)) ans=inc(ans,mul(p[mn][i],dfs(k^x)));
        }
    return dp[S]=ans;
}
int main()
{
    n=read(),m=read(),U=(1<<n)-1;
    for(int i=1,f,u1,u2,v1,v2;i<=m;++i)
    {
	f=read(),u1=read()-1,v1=read()-1;
	if(!f){E[u1]|=1<<v1;continue;}
	u2=read()-1,v2=read()-1;
	if(u1>u2) swap(u1,u2),swap(v1,v2);
	E[u1]|=1<<v1,E[u2]|=1<<v2,e[u1][v1]=1<<u2|1<<(v2+n),p[u1][v1]=f==1? i4:i34;
    }
    printf("%d",mul(dfs(U<<n|U),power(2,n)));
}
posted @ 2019-12-06 19:37  Shiina_Mashiro  阅读(223)  评论(0编辑  收藏  举报