5.28联考题解

A bzoj3777
先不考虑本质不同,计算总的方案数,问题相当于一个人每步至少跨越k个格子,求走到这n个格子中某个格子后停下来的方案数
我们设这个人一开始在无穷远,第一步走到的位置是0,然后设他走到第i个格子的方案数是 f[i] f [ i ] ,不考虑循环对末尾选的限制,有 f[i]=ikj=0f[j] f [ i ] = ∑ j = 0 i − k f [ j ] ,转移可以用前缀和优化到 O(1) O ( 1 )
要考虑循环,计算总的方案数的话,只需枚举第一步走到第 i i 个格子,这种情况下对应的方案数是j=0min(ni,nk)f[j]

加上本质不同的限制,就是套上一个burnside
他一共有n种置换,第i种是顺时针旋转i个格子,枚举i,要计算旋转i后和原来相同的方案数
我们现在要计算顺时针旋转 i i 格后和原来相同的方案数
对于一个位置pos,我们将他旋转若干个 i i 后到达的位置标成黑色,有一个结论是大小为n的环,顺时针旋转i格的置换环有 (i,n) ( i , n ) 个,也就是每隔 (i,n) ( i , n ) 就有一个黑格子(之前某道置换题blog证过就不再证了),因此我们可以理解为这n个格子以 (i,n) ( i , n ) 为循环节

因为一个置换环中被选与不选的状态相同,我们枚举第一个选的置换环 i i ,令d=(i,n),只要在这 d d 个格子里面合法,在这n个格子里也合法,而在这d个格子中每个置换环只有一个格子,就和上面要算得东西差不多了,方案数是 min(dk,di)j=0f[j] ∑ j = 0 m i n ( d − k , d − i ) f [ j ]
(可以发现其实顺时针旋n个不变时的方案数就是上面算的不考虑本质不同的方案数)
这个 j j 不用枚举,柿子是可以O(1)算的

总复杂度 O(nlogn) O ( n l o g n ) (log的复杂度来自gcd)

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

int gcd(int a,int b){return !a?b:gcd(b%a,a);}
const int maxn = 210000;
const int mod  = 1e9+7;
inline void add(int &a,const int &b){a+=b;if(a>=mod)a-=mod;}
inline void dec(int &a,const int &b){a-=b;if(a<0)a+=mod;}

int pw(int x,int k)
{
    int re=1;
    for(;k;k>>=1,x=(ll)x*x%mod) if(k&1)
        re=(ll)re*x%mod;
    return re;
}
int inv(int x){ return pw(x,mod-2); }

int n,K;
int f[maxn],s[maxn],g[maxn];

int main()
{
    freopen("refrain.in","r",stdin);
    freopen("refrain.out","w",stdout);

    scanf("%d%d",&n,&K);
    f[0]=1; s[0]=1;
    for(int i=1;i<=n;i++)
    {
        f[i]=i-K<0?0:s[i-K];
        s[i]=(s[i-1]+f[i])%mod;
    }
    g[0]=f[0];
    for(int i=1;i<=n;i++) g[i]=(g[i-1]+s[i])%mod;

    int ans=0,ansn=0;
    for(int i=K;i<=n;i++)
    {
        if(i==n) ansn=ans;
        int d=gcd(i,n); if(d<K) continue;
        add(ans,(ll)s[d-K]*K%mod);
        add(ans,g[d-K]);
        dec(ans,s[d-K]);
    }
    ansn=(ans-ansn+mod)%mod;
    printf("%d\n%lld\n",ansn,(ll)ans*inv(n)%mod);

    return 0;
}

B bzoj3778
就是求白点构成的不包含黑点的凸包的最大面积
思考构建凸包的过程,找一个最下最左的点,将其他点极角排序,然后逆时针建进凸包里
我们可以用同样的方法做这道题
枚举最终答案的凸包里最下最左的点,将其他点极角排序,那么凸包上的点一定是按顺序出现的,然后我们就可以做一个dp,设 f[i][j][k] f [ i ] [ j ] [ k ] 表示dp到第 i i 个点,当前凸包上最后两个点是j,k,当前凸包的最大面积
输出方案的话记录一下每个状态是从哪个状态转移过来的就行了

复杂度 O(n3(n+m)) O ( n 3 ( n + m ) )

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define db double
#define pb push_back
#define SZ(x) ((int)x.size())
using namespace std;

const int maxn = 105;
const double eps = 1e-7;

int n,m;
struct Point
{
    int x,y,i;
    friend inline bool operator ==(const Point &a,const Point &b){ return a.x==b.x&&a.y==b.y; }
    friend inline Point operator -(const Point &a,const Point &b){return (Point){a.x-b.x,a.y-b.y};}
    friend inline int operator *(const Point &a,const Point &b){return a.x*b.y-a.y*b.x;}
    db Len() { return sqrt((db)x*x+(db)y*y); }
}p[maxn],pm[maxn],t[maxn]; int tp;
inline bool cmp(const Point &x,const Point &y)
{
    return (x-t[0])*(y-x)>0;
}

int In_Line(const Point &x,const Point &y,const Point &q)
{
    return (y-x)*(q-y)==0&&fabs((x-q).Len()+(y-q).Len()-(x-y).Len())<eps;
}
int In_Convex(const Point &x,const Point &y,const Point &z,const Point &q)
{
    if(x==y) return In_Line(x,z,q);
    return (y-x)*(q-y)>=0&&(z-y)*(q-z)>=0&&(x-z)*(q-x)>=0;
}

int ans;
vector<Point>v;

int f[maxn][maxn][maxn],pre[maxn][maxn][maxn];

int main()
{
    //freopen("resonance.in","r",stdin);
    //freopen("resonance.out","w",stdout);

    scanf("%d%d",&n,&m);
    for(int i=1;i<=n;i++) scanf("%d%d",&p[i].x,&p[i].y),p[i].i=i;
    for(int i=1;i<=m;i++) scanf("%d%d",&pm[i].x,&pm[i].y);

    ans=0;
    for(int i=1;i<=n;i++)
    {
        t[0]=p[i];
        tp=0; for(int j=1;j<=n;j++) if(p[j].y>p[i].y||(p[j].y==p[i].y&&p[j].x>p[i].x)) t[++tp]=p[j];
        sort(t+1,t+tp+1,cmp);

        for(int x=0;x<=tp;x++) for(int y=0;y<=tp;y++) for(int k=0;k<=tp;k++)
            f[x][y][k]=pre[x][y][k]=-1;

        f[0][0][0]=0;
        for(int j=0;j<tp;j++)
        {
            Point tx=t[j+1];
            for(int y=0;y<=j;y++)
            {
                Point ty=t[y];
                int ok=1;
                for(int k=1;k<=m&&ok;k++)
                    ok&=!In_Convex(t[0],ty,tx,pm[k]);

                int ad=(ty-t[0])*(tx-ty);
                for(int x=0;x<=y;x++) if(f[j][x][y]!=-1)
                {
                    if(f[j+1][y][j+1]<f[j][x][y]+ad&&(ty-t[x])*(tx-ty)>=0&&ok)
                        f[j+1][y][j+1]=f[j][x][y]+ad,pre[j+1][y][j+1]=x;
                    if(f[j+1][x][y]<f[j][x][y])
                        f[j+1][x][y]=f[j][x][y],pre[j+1][x][y]=y;
                }
            }
        }
        for(int x=1;x<tp;x++) for(int y=x+1;y<=tp;y++) if(f[tp][x][y]>ans)
        {
            ans=f[tp][x][y]; v.clear();
            v.pb(t[y]); v.pb(t[x]);
            int ni=tp,px=x,py=y;
            while(ni)
            {
                int ny=pre[ni][px][py];
                if(ny!=py) 
                {
                    if(ny!=px) v.pb(t[ny]);
                    py=ny,swap(px,py);
                }
                ni--;
            }
        }
    }

    printf("%.2lf\n",ans/2.0);
    /*printf("%d\n",SZ(v));
    for(int i=0;i<SZ(v);i++)
    {
        printf("%d",v[i].i);
        if(i+1!=SZ(v)) putchar(' ');
    }putchar('\n');*/

    return 0;
}

C bzoj3779
一棵LCT,每次Access,或者makeroot一个点,或者询问一个点子树里所有点到根路径上虚边个数的平均数

先假设没有换根操作
根据LCT的复杂度证明,每次Access,虚改实,实改虚的边数均摊是 O(logn) O ( l o g n ) 级别的,我们可以维护一棵LCT,Access时把要修改的边找出来,在dfs序上维护每个点到根路径上的虚边数,每次虚改实,实改虚就是对一个子树区间+1或-1,用线段树很容易做到 O(nlog2n) O ( n l o g 2 n )

加上换根的话,修改子树的时候讨论一下子树的实际范围就好了

code:

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
#define db double
#define pb push_back
#define mp make_pair
#define SZ(x) ((int)x.size())
using namespace std;

inline void read(int &x)
{
    char c; while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') (x*=10)+=c-'0';
}
const int maxn = 210000;
const int maxd = 20;

int n,m;
struct edge{int y,nex;}a[maxn<<1]; int len,fir[maxn];
inline void ins(const int x,const int y){a[++len]=(edge){y,fir[x]};fir[x]=len;}

int root;
int cnt,dfn[maxn],siz[maxn],ff[maxn],df[maxn][maxd],dep[maxn];
void dfs(const int x)
{
    for(int i=1;i<maxd;i++) df[x][i]=df[df[x][i-1]][i-1];
    siz[x]=1; dfn[x]=++cnt;
    for(int k=fir[x],y=a[k].y;k;k=a[k].nex,y=a[k].y) if(y!=ff[x])
    {
        ff[y]=x; df[y][0]=x;
        dep[y]=dep[x]+1;
        dfs(y);
        siz[x]+=siz[y];
    }
}

struct Segment
{
    ll seg[maxn<<2],flag[maxn<<2];
    void pushdown(const int x,const int l,const int r)
    {
        if(!flag[x]) return;
        int fl=flag[x]; flag[x]=0;
        int mid=(l+r)>>1,lc=x<<1,rc=lc|1;

        seg[lc]+=(ll)(mid-l+1)*fl; seg[rc]+=(ll)(r-mid)*fl;
        flag[lc]+=fl; flag[rc]+=fl;
    }
    void pushup(const int x){seg[x]=seg[x<<1]+seg[x<<1|1];}
    int lx,rx,c;
    void upd(const int x,const int l,const int r)
    {
        if(rx<l||r<lx) return;
        if(lx<=l&&r<=rx) { seg[x]+=(ll)(r-l+1)*c;flag[x]+=c;return; }
        pushdown(x,l,r);
        int mid=(l+r)>>1;
        upd(x<<1,l,mid); upd(x<<1|1,mid+1,r);
        pushup(x);
    }
    ll query(const int x,const int l,const int r)
    {
        if(rx<l||r<lx) return 0;
        if(lx<=l&&r<=rx) return seg[x];
        pushdown(x,l,r);
        int mid=(l+r)>>1;
        return query(x<<1,l,mid)+query(x<<1|1,mid+1,r);
    }
    void Upd(int x,int tc)
    {
        c=tc?1:-1;
        if(dfn[root]>=dfn[x]&&dfn[root]<dfn[x]+siz[x])
        {
            lx=1,rx=dfn[x]-1; if(lx<=rx) upd(1,1,cnt);
            lx=dfn[x]+siz[x],rx=cnt; if(lx<=rx) upd(1,1,cnt);
        }
        else lx=dfn[x],rx=dfn[x]+siz[x]-1,upd(1,1,cnt);
    }
    db q(int x)
    {
        ll ans=0; int Siz;
        if(x==root)
        {
            Siz=n;
            lx=1,rx=cnt,ans=query(1,1,cnt);
        }
        else if(dfn[root]>=dfn[x]&&dfn[root]<dfn[x]+siz[x])
        {
            int y=root; for(int i=maxd-1;i>=0;i--) if(dep[df[y][i]]>dep[x]) y=df[y][i];

            Siz=n-siz[y];
            lx=1,rx=dfn[y]-1; ans+=query(1,1,cnt);
            lx=dfn[y]+siz[y],rx=cnt; if(lx<=rx) ans+=query(1,1,cnt);
        }
        else
        {
            Siz=siz[x];
            lx=dfn[x],rx=dfn[x]+siz[x]-1,ans=query(1,1,cnt);
        }
        return (db)ans/Siz;
    }
}seg;

struct Link_Cut_Tree
{
    int son[maxn][2],fa[maxn],rev[maxn];
    void pushdown(int x)
    {
        if(rev[x]) 
        {
            rev[x]=0;
            rev[son[x][0]]^=1,rev[son[x][1]]^=1;
            swap(son[x][0],son[x][1]);
        }
    }
    bool isrt(const int x){ return son[fa[x]][0]!=x&&son[fa[x]][1]!=x; }
    void rot(int x)
    {
        int y=fa[x],z=fa[y];
        if(!isrt(y)) son[z][son[z][1]==y]=x;
        fa[x]=z;
        int l=son[y][1]==x;
        fa[son[y][l]=son[x][!l]]=y;
        fa[son[x][!l]=y]=x;
    }
    int t[maxn],tp;
    void splay(int x)
    {
        int tx=x; while(!isrt(tx)) t[++tp]=tx,tx=fa[tx]; t[++tp]=tx;
        while(tp) pushdown(t[tp--]);

        for(;!isrt(x);rot(x))
        {
            int y=fa[x],z=fa[y];
            if(!isrt(y)) rot(((son[y][1]==x)^(son[z][1]==y))?x:y);
        }
    }
    int go(int x,int dir)
    {
        pushdown(x);
        while(son[x][dir]) 
        {
            x=son[x][dir];
            pushdown(x);
        }
        return x;
    }
    void Access(int x)
    {
        int y=0;
        for(;x;y=x,x=fa[x])
        {
            splay(x);
            int ty=go(son[x][1],0);
            if(ty) seg.Upd(dfn[x]>dfn[ty]?x:ty,1);
            int nty=go(y,0);
            if(nty) seg.Upd(dfn[x]>dfn[nty]?x:nty,0);

            son[x][1]=y;
        }
    }
    void makeroot(int x)
    {
        Access(x); splay(x); rev[x]^=1;
        root=x;
    }
    void build(const int x)
    {
        for(int i=2;i<=n;i++) fa[i]=ff[i],seg.Upd(i,1);
    }
}LCT;


char str[110];

int main()
{
    //freopen("recompile.in","r",stdin);
    //freopen("recompile.out","w",stdout);

    read(n); read(m);
    for(int i=1;i<n;i++)
    {
        int x,y; read(x);read(y);
        ins(x,y); ins(y,x);
    }
    dep[1]=1; dfs(root=1);
    LCT.build(1);

    for(int i=1;i<=m;i++)
    {
        scanf("%s",str); int x;read(x);
        if(str[2]=='L') LCT.Access(x);
        else if(str[2]=='C') LCT.makeroot(x);
        else printf("%.10lf\n",seg.q(x)+1.0);
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值