WC 2018 题解

WC 2018 题解

一些感受.jpg

题目难度相较前些年会相对简单一点?(FAKE.jpg

平均码量符合WC风格?(甚至更多一点

出题人良心!

[WC2018] 通道

  • 一个不知道对不对的$\log ^3$大常数解法:对于第一棵树边分治,第二颗树建虚树然后边分治,最后一颗树再建虚树DP

显然我是不可能写的,这辈子都不可能写的。

那么显然,我们还是要分析性质的!

我们想到把第一个树的点权第二个树的点权附到第三棵树上,然后做直径就完事了。

对于边权非负,贪心解决树的直径是对的,所有链接两个树之后的直径显然就是原先的$4$个直径的断点的$C(4,2)$的组合最大的那个。

然后显然,第一棵树的点权即为$dis_x-dis_{now}$,第二颗树的点权即为:$dis_x$,这样,附到第三棵树上求直径即可。

然后首先相当于是一开始只有一坨的点,然后在第二颗树的虚树上合并即可,那么合并的过程中就可以知道在第二颗树的$LCA$了,更新答案的时候减去即可。

#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$分

显然,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;
}
posted @ 2019-03-29 19:17  Winniechen  阅读(465)  评论(0编辑  收藏  举报