zoukankan      html  css  js  c++  java
  • 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
    ",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
    ",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
    ",ans);}
    

    [WC2018] 州区划分

    其实只要会如何判断是否有欧拉回路即可拿到$44$分

    显然,DP方程:$f[S]=sumlimits_{ssub S}f[s] imes g[S - s]$,其中$f[s]$表示$s$这些点构成的点集的方案数,$g[s]$表示$[check (s)]sumlimits_{iin s}w_i$

    这个东西跑$3^{21}$即使是$10s$开$O2$,也是不太现实的

    那么就需要用到FWT的高级操作了...

    显然,可以把枚举子集看做两个集合的交集为这个集合,也就是集合取或。

    但是这样显然会有重复的情况,那么就需要用一点别的思想:设$f[i][S]$表示用$i$个$1$组成$S$的方案数。

    显然,第一感觉这个东西没啥用处,但是可以这样:$f[i][S]=[x|y==S]sumlimits_{j=1}^i f[j][x] imes 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
    ",f[n][len-1]);
    }
    

    [WC2018] 即时战略

    讲道理,我觉得难点在链上...

    可以看出,他的询问次数基本在$nlog n$级别,(除了链.jpg

    那么考虑暴力如何处理:显然是每次随机找到一个点,然后向下拓展即可。

    这样的询问复杂度为$n^2$级别的(我觉得高级一点的随机化能拿很高分.jpg

    然后显然你发现,这个东西的确切询问次数为:$sumlimits_{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;
    }
    
  • 相关阅读:
    年终总结 2016-08-28 22:04 422人阅读 评论(26) 收藏
    [mysql]MySQL Daemon failed to start 2016-08-14 21:27 1121人阅读 评论(18) 收藏
    solrr初步了解 2016-07-31 22:29 380人阅读 评论(4) 收藏
    基于spring-boot的测试桩设计--几种常见的controller
    利用Factory-boy和sqlalchemy来批量生成数据库表数据
    job中shell脚本异常(删除不存在容器),导致job被打断执行的问题 脚本优化方法
    利用Factory-boy来生成实例数据
    pytest相关资源收集
    pytest 用 @pytest.mark.usefixtures("fixtureName")装饰类,可以让执行每个case前,都执行一遍指定的fixture
    pytest fixture 利用 params参数实现用例集合
  • 原文地址:https://www.cnblogs.com/Winniechen/p/10623382.html
Copyright © 2011-2022 走看看