WC 2018 題解

WC 2018 題解

一些感覺.jpg

題目難度相較前些年會相對簡單一點?(FAKE.jpgnode

平均碼量符合WC風格?(甚至更多一點ios

出題人良心!dom

[WC2018] 通道

  • 一個不知道對不對的$\log ^3$大常數解法:對於第一棵樹邊分治,第二顆樹建虛樹而後邊分治,最後一顆樹再建虛樹DP

顯然我是不可能寫的,這輩子都不可能寫的。優化

那麼顯然,咱們仍是要分析性質的!ui

咱們想到把第一個樹的點權第二個樹的點權附到第三棵樹上,而後作直徑就完事了。spa

對於邊權非負,貪心解決樹的直徑是對的,全部連接兩個樹以後的直徑顯然就是原先的$4$個直徑的斷點的$C(4,2)$的組合最大的那個。code

而後顯然,第一棵樹的點權即爲$dis_x-dis_{now}$,第二顆樹的點權即爲:$dis_x$,這樣,附到第三棵樹上求直徑便可。get

而後首先至關因而一開始只有一坨的點,而後在第二顆樹的虛樹上合併便可,那麼合併的過程當中就能夠知道在第二顆樹的$LCA$了,更新答案的時候減去便可。string

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
#include <bitset>
using namespace std;
#define ll long long
#define nc() (p1==p2&&(p2=(p1=buf)+fread(buf,1,100000,stdin),p1==p2)?EOF:*p1++)
char buf[100000],*p1,*p2;
inline int rd()
{
    register int x=0;register char c=nc();
    while(c<48)c=nc();while(c>47)x=((x+(x<<2))<<1)+(c^48),c=nc();
    return x;
}
inline ll Rd()
{
    register ll x=0;register char c=nc();
    while(c<48)c=nc();while(c>47)x=((x+(x<<2))<<1)+(c^48),c=nc();
    return x;
}
int n,size,q[100005],col[100005],lg[200005];ll val[100005],ans;
 
namespace T3
{
    const int N = 100005;
    struct node{int to,next;ll val;}e[N<<1];
    int head[N],cnt,idx[N],tims;ll mn[18][N<<1],dis[N];
    inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;}
    void dfs(int x,int from)
    {
        mn[0][++tims]=dis[x];idx[x]=tims;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from)dis[to1]=dis[x]+e[i].val,dfs(to1,x),mn[0][++tims]=dis[x];
        }
    }
    void init()
    {
        memset(head,-1,sizeof(head));ll z;
        for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add(x,y,z),add(y,x,z);
        dfs(1,0);for(int i=2;i<=tims;i++)lg[i]=lg[i>>1]+1;
        for(int i=1;i<=17;i++)
            for(int j=1;j+(1<<i)-1<=tims;j++)
                mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
    }
    inline ll get_lca(int x,int y){if(x==y)return 0ll;if(x>y)swap(x,y);int tmp=lg[y-x+1];return min(mn[tmp][x],mn[tmp][y-(1<<tmp)+1]);}
    inline ll get_dis(int x,int y){if(x==y)return 0ll;return dis[x]+dis[y]-(get_lca(idx[x],idx[y])<<1);}
}
 
namespace T2
{
    const int N = 100005;
    struct node{int to,next;ll val;}e[N<<1];
    int head[N],cnt,mn[18][N<<1],idx[N],tims,dep[N];ll dis[N];
    inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;}
    inline void add(int x,int y){e[cnt]=(node){y,head[x],0ll};head[x]=cnt++;}
    inline int cmp1(int x,int y){return dep[x]<dep[y]?x:y;}
    inline bool cmp(int x,int y){return idx[x]<idx[y];}
    void dfs(int x,int from)
    {
        mn[0][++tims]=x;idx[x]=tims;dep[x]=dep[from]+1;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from)dis[to1]=dis[x]+e[i].val,dfs(to1,x),mn[0][++tims]=x;
        }
    }
    inline int get_lca(int x,int y){if(x==y)return x;x=idx[x],y=idx[y];if(x>y)swap(x,y);int tmp=lg[y-x+1];return cmp1(mn[tmp][x],mn[tmp][y-(1<<tmp)+1]);}
    inline ll get_dis(int x,int y){return dis[x]+dis[y]-(dis[get_lca(x,y)]<<1);}
    void init()
    {
        memset(head,-1,sizeof(head));memset(col,-1,sizeof(col));ll z;
        for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add(x,y,z),add(y,x,z);
        dfs(1,0);
        for(int i=1;i<=17;i++)
            for(int j=1;j+(1<<i)-1<=tims;j++)
                mn[i][j]=cmp1(mn[i-1][j],mn[i-1][j+(1<<i-1)]);
        memset(head,-1,sizeof(head));cnt=0;tims=0;
    }
    struct DP
    {
        int x,y;ll len;
        DP(){x=y=0;len=-1ll;}
        DP(int a,int b){x=a,y=b,len=T3::get_dis(a,b)+val[a]+val[b]+dis[a]+dis[b];}
        DP(int a,int b,ll c){x=a,y=b,len=c;}
        inline bool operator < (const DP &a) const {return len<a.len;}
        inline friend DP operator + (const DP &a,const DP &b)
        {
            if(!a.x)return b;if(!b.x)return a;
            return max(max(a,b),max(max(DP(a.x,b.x),DP(a.y,b.x)),max(DP(a.x,b.y),DP(a.y,b.y))));
        }
        inline friend DP operator * (const DP &a,const DP &b)
        {
            if(!a.x)return DP();if(!b.x)return DP();
            return max(max(DP(a.x,b.x),DP(a.y,b.x)),max(DP(a.x,b.y),DP(a.y,b.y)));
        }
    }f[N][2];
    int sta[N],top;
    void dfs_dp(int x)
    {
        f[x][0]=f[x][1]=DP();if(col[x]!=-1)f[x][col[x]]=DP(x,x);
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;dfs_dp(to1);
            for(int j=0;j<2;j++)ans=max(ans,(f[x][j]*f[to1][!j]).len-(dis[x]<<1));
            for(int j=0;j<2;j++)f[x][j]=f[x][j]+f[to1][j];
        }head[x]=-1;
        // printf("%d %d %d %lld %d %d %lld\n",x,f[x][0].x,f[x][0].y,f[x][0].len,f[x][1].x,f[x][1].y,f[x][1].len);
    }
    void build()
    {
        sort(q+1,q+size+1,cmp);cnt=top=0;sta[++top]=1;
        for(int i=1;i<=size;i++)
        {
            int x=q[i],lca=get_lca(x,sta[top]);
            while(top&&dep[lca]<=dep[sta[top-1]])add(sta[top-1],sta[top]),top--;
            if(sta[top]!=lca)add(lca,sta[top]),sta[top]=lca;if(sta[top]!=x)sta[++top]=x;
        }while(top>1)add(sta[top-1],sta[top]),top--;dfs_dp(1);
        for(int i=1;i<=size;i++)col[q[i]]=-1,val[q[i]]=0ll;
    }
}
 
namespace T1
{
    const int N = 200005;
    struct node{int to,next;ll val;}e[N<<1];
    int head[N],cnt,tot;vector<int>v[N];vector<ll>va[N];
    inline void add_edge(int x,int y,ll z){v[x].push_back(y),va[x].push_back(z);}
    inline void add(int x,int y,ll z){e[cnt]=(node){y,head[x],z};head[x]=cnt++;}
    int vis[N],siz[N],mx,rot,sn;//0 fa 1 ls 2 rs
    void dfs_change(int x,int from)
    {
        int tmp=0,lst=x,lim=v[x].size();
        for(int i=0;i<lim;i++)
        {
            int to1=v[x][i];
            if(to1!=from)
            {
                if(tmp&&(tmp!=lim-(x==1)))++tot,add(lst,tot,0ll),add(tot,lst,0ll),lst=tot;
                tmp++,add(lst,to1,va[x][i]),add(to1,lst,va[x][i]);
                dfs_change(to1,x);
            }
        }
    }
    void get_root(int x,int from)
    {
        siz[x]=1;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from&&!vis[i>>1])
            {
                get_root(to1,x);siz[x]+=siz[to1];
                if(max(siz[to1],sn-siz[to1])<mx)rot=i,mx=max(siz[to1],sn-siz[to1]);
            }
        }
    }
    int flg=0;
    void calc(int x,int from,ll dis)
    {
        if(x<=n)q[++size]=x,col[x]=flg,val[x]=dis;siz[x]=1;
        for(int i=head[x];i!=-1;i=e[i].next)
        {
            int to1=e[i].to;
            if(to1!=from&&!vis[i>>1])calc(to1,x,dis+e[i].val),siz[x]+=siz[to1];
        }
    }
    void dfs(int x)
    {
        mx=1<<30;sn=siz[x];rot=0;get_root(x,0);
        if(!rot)return ;vis[rot>>1]=1;//printf("%d %d\n",e[rot].to,e[rot^1].to);
        size=0;flg=0;calc(e[rot].to,0,0);flg=1;calc(e[rot^1].to,0,e[rot].val);
        T2::build();int tmp=e[rot^1].to;dfs(e[rot].to);dfs(tmp);
    }
    void init()
    {
        memset(head,-1,sizeof(head));tot=n;ll z;
        for(int i=1,x,y;i<n;i++)x=rd(),y=rd(),z=Rd(),add_edge(x,y,z),add_edge(y,x,z);
        T2::init();T3::init();dfs_change(1,0);siz[1]=tot;dfs(1);
    }
}
 
int main(){scanf("%d",&n);T1::init();printf("%lld\n",ans);}

[WC2018] 州區劃分

其實只要會如何判斷是否有歐拉回路便可拿到$44$分it

顯然,DP方程:$f[S]=\sum\limits_{s\sub S}f[s]\times g[S - s]$,其中$f[s]$表示$s$這些點構成的點集的方案數,$g[s]$表示$[check (s)]\sum\limits_{i\in s}w_i$

這個東西跑$3^{21}$即便是$10s$開$O2$,也是不太現實的

那麼就須要用到FWT的高級操做了...

顯然,能夠把枚舉子集看作兩個集合的交集爲這個集合,也就是集合取或。

可是這樣顯然會有重複的狀況,那麼就須要用一點別的思想:設$f[i][S]$表示用$i$個$1$組成$S$的方案數。

顯然,第一感受這個東西沒啥用處,可是能夠這樣:$f[i][S]=[x|y==S]\sum\limits_{j=1}^i f[j][x]\times g[i-j][y]$

能夠看出,這樣$x,y$之間就不存在交集,也就是說,將重複的狀況徹底去掉了。

代碼.jpg

#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
#include <bitset>
using namespace std;
#define N 22
#define M 1<<21
#define ll long long
#define mod 998244353
int w[N],n,m,p,map[N][N],d[N],q[N],vis[N],tims;
int f[N][M],g[N][M],cnt[M],s[M];
int q_pow(int x,int n){int ret=1;for(;n;n>>=1,x=(ll)x*x%mod)if(n&1)ret=(ll)ret*x%mod;return ret;}
bool check(int S)
{
    memset(d,0,sizeof(d));
    for(int i=1;i<=n;i++)
        for(int j=1;j<=n;j++)
            if(map[i][j]&&((1<<i-1)&S)&&((1<<j-1)&S))d[i]++;
    int l=0,r=0;tims++;
    for(int i=1;i<=n;i++)if(((1<<i-1)&S)&&(d[i]&1))return 0;
    for(int i=1;i<=n;i++)if((1<<i-1)&S){q[r++]=i;vis[i]=tims;break;}
    while(l<r)
    {
        int x=q[l++];
        for(int i=1;i<=n;i++)if(vis[i]!=tims&&map[x][i]&&((1<<i-1)&S))vis[i]=tims,q[r++]=i;
    }
    return r==cnt[S];
}
void FWT(int *a,int len,int opt)
{
    for(int k=2;k<=len;k<<=1)
        for(int i=0,t=k>>1;i<len;i+=k)
            for(int j=i;j<i+t;j++)
                if(opt==1)a[j+t]=(a[j+t]+a[j])%mod;
                else a[j+t]=(a[j+t]-a[j]+mod)%mod;
}
int main()
{
    scanf("%d%d%d",&n,&m,&p);
    for(int i=1,x,y;i<=m;i++)scanf("%d%d",&x,&y),map[x][y]=map[y][x]=1;
    for(int i=1;i<=n;i++)scanf("%d",&w[i]);
    f[0][0]=1;
    int len=1<<n;
    for(int S=1;S<len;S++)
    {
        cnt[S]=cnt[S>>1]+(S&1);
        for(int i=1;i<=n;i++)if(S&(1<<i-1))s[S]+=w[i];
        if(p==0)s[S]=1;
        else if(p==2)s[S]=(ll)s[S]*s[S]%mod;
        if(!check(S))g[cnt[S]][S]=s[S];
        s[S]=q_pow(s[S],mod-2);f[0][S]=1;
    }
    for(int i=1;i<=n;i++)FWT(g[i],len,1);
    for(int i=1;i<=n;i++)
    {
        for(int j=1;j<=i;j++)
            for(int S=0;S<len;S++)
                f[i][S]=(f[i][S]+(ll)f[i-j][S]*g[j][S])%mod;
        FWT(f[i],len,-1);
        for(int S=0;S<len;S++)
            if(cnt[S]!=i)f[i][S]=0;
            else f[i][S]=(ll)f[i][S]*s[S]%mod;
        if(i!=n)FWT(f[i],len,1);
    }
    printf("%d\n",f[n][len-1]);
}

[WC2018] 即時戰略

講道理,我以爲難點在鏈上...

能夠看出,他的詢問次數基本在$n\log n$級別,(除了鏈.jpg

那麼考慮暴力如何處理:顯然是每次隨機找到一個點,而後向下拓展便可。

這樣的詢問複雜度爲$n^2$級別的(我以爲高級一點的隨機化能拿很高分.jpg

而後顯然你發現,這個東西的確切詢問次數爲:$\sum\limits_{i=1}^ndep_i$

這個東西,你想優化就很簡單了!

考慮,每次只會查詢$\log n$次,那麼能夠用點分樹或者LCT(隨機剖分.jpg

每次最多跳$\log n$層,由於每次跳的時候,要麼跳一整條鏈,要麼在$Splay$上跳$\log n$次。

那麼就能夠找到未遍歷到的節點啦!

找到後暴力拓展的複雜度顯然正確.jpg

那麼對於鏈來講,相對麻煩一些,每次隨機的找一個未遍歷的點,分別對左右兩端拓展,若是往左沒被遍歷就往左,不然必定在右側。

對於這樣的東西,他的指望錯誤次數在$\log n$之內,多隨機打亂兩次就行了嘛...(點黑誰也不能怪.jpg

代碼!

#include "rts.h"
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <cstdlib>
#include <queue>
#include <iostream>
#include <bitset>
using namespace std;
#define N 300005
#define ls ch[rt][0]
#define rs ch[rt][1]
#define get(rt) (ch[f[rt]][0]!=rt)
#define isroot(rt) (ch[f[rt]][0]!=rt&&ch[f[rt]][1]!=rt)
int ch[N][2],f[N],vis[N],idx[N],mn[N],mx[N];
inline void PushUp(int rt)
{
    mn[rt]=mx[rt]=rt;
    if(ls)mn[rt]=mn[ls];
    if(rs)mx[rt]=mx[rs];
}
inline void rotate(int rt)
{
    int x=f[rt],y=f[x],k=get(rt);
    if(!isroot(x))ch[y][ch[y][0]!=x]=rt;
    ch[x][k]=ch[rt][!k];f[ch[x][k]]=x;
    ch[rt][!k]=x;f[x]=rt;f[rt]=y;PushUp(x);PushUp(rt);
}
inline void Splay(int rt)
{
    for(;!isroot(rt);rotate(rt))
        if(!isroot(f[rt]))rotate(get(f[rt])==get(rt)?f[rt]:rt);
}
inline void access(int rt){int t=0;while(rt)Splay(rt),rs=t,PushUp(rt),t=rt,rt=f[rt];}
inline int get_root(int rt){while(!isroot(rt))rt=f[rt];return rt;}
void play(int n,int T,int dataType)
{
    for(int i=2;i<=n;i++)idx[i]=i;
    random_shuffle(idx+2,idx+n+1);
    if(dataType==3)
    {
        vis[1]=1;int l=1,r=1;
        for(int i=2;i<=n;i++)
        {
            int x=idx[i],now;if(vis[x])continue;
            if(!vis[now=explore(l,x)])
            {
                while(now!=x)vis[now]=1,now=explore(now,x);
                vis[x]=1;l=x;
            }else
            {
                now=explore(r,x);
                while(now!=x)vis[now]=1,now=explore(now,x);
                vis[x]=1;r=x;
            }
        }
        return;
    }
    else
    {
        vis[1]=1;mn[1]=mx[1]=1;
        for(int i=2;i<=n;i++)
        {
            if(vis[idx[i]])continue;
            int x=idx[i],rt=get_root(1),ret;
            while(!vis[x])
            {
                ret=explore(rt,x);
                if(mn[rs]==ret)rt=rs;
                else if(mx[ls]==ret)rt=ls;
                else if(vis[ret])rt=get_root(ret);
                else vis[ret]=1,mn[ret]=mx[ret]=ret,f[ret]=rt,rt=ret;
            }
            access(x);
        }
    }return;
}
相關文章
相關標籤/搜索