zoukankan      html  css  js  c++  java
  • [SCOI2016]背单词

    题目

    题目

    做法

    我们把每个单词反过来,然后如果(st[i])(st[j])的前缀,且不存在(st[k])(st[j])的前缀,且(st[i])(st[k])的前缀,那么(i)(j)的父亲,显然这样构造不存在环,且是一个森林。(构造方法:字典树)

    不难发现,如果你每个点都等着祖先拿完再拿(拿就是背下来的意思),那么价值都是≤(n)的,总的价值小于等于(n^2),也就是说第一条规则就是废的,一个点一定得在祖先拿完之后再拿。

    好,假设现在构造出一种拿点方案:(a_1,a_2,a_3,...,a_n)(b)数组的定义是:如果(a_i=j),那么(b_j=i)

    总代价就是(sumlimits_{i=1}^{n}b[i]-b[fa[i]])。(当然,对于一个点没有父亲就设其父亲为(0)(b[0])永远为(0)

    考虑通过调换位置让这个更加优秀,对于(x)而言((x)需要满足在(a)数组中其的任意一个子树节点到(x)的这么一段区间中不存在一个不是(x)子树的点,即(x)的子树是连续的一段,这样有什么好处,我们移动(x)子树这么连续的一段时,值会改变的只有(b[x]-b[fa[x]])),如果其到(fa[x])的这一段方案中,如果存在(y)点不在(fa[x])的子树当中((y)也需要满足跟(x)同样的要求,且(b[fa[y]]>b[fa[x]]),假设存在(y)不在(fa[x])的子树,那么一定存在满足要求的(y)

    这样的话,我们只需要把(y)连通其子树放到(fa[x])前面即可,这样增加量为:(-(b[y]-b[fa[x]])+size[y]-size[y]=-(b[y]-b[fa[x]]))(分别为(y,fa[x],x)的减少量),而这个是严格小于(0)的,即可以让结果更小,我们称此为一次操作。

    在这里插入图片描述

    好,那么为什么假设存在(y)不在(fa[x])的子树,那么一定存在满足要求的(y),考虑如果(y)不满足与(x)同样的要求,那么我们先去处理(y)的子树,那么新处理的点(z)(z)(y)的子树且(z)的祖先是(y)),其(b[z]<b[fa[z]]<b[y]<b[fa[x]]),这样不断循环下去,(b[z])最终最多会等于(n),也就是一定会停下来,所以一定可以通过一定的操作数构造出(y)(x)同样的要求。

    那么如果(b[fa[y]]>b[fa[x]])呢?

    那么就把(y)设为(fa[y]),然后重复同样的步骤。

    这样,我们最终构造出来的方案是什么呢?

    不难发现,其实就是一个(DFS)序,即一个点被拿之后直接拿完其子树是最优秀的。

    因此可以直接(DFS)走一遍。

    怎样的(dfs)序是最快的呢?

    不难发现,每个点的权值只与其父亲有关,所以(x)遍历儿子的顺序会改变每个儿子贡献的权值。

    设儿子序列为(a_1,a_2,a_3,...,a_k)

    那么答案就为:(1+(1+size[a_1])+(1+size[a_1]+size[a_2])...)

    考虑如果(size[a_{i}]>size[a_{i+1}])的话,交换(a_{i},a_{i+1})会有什么影响?不难发现,其余儿子的贡献不变,只有(a_i,a_{i+1})变了,增加量为(size[a_{i+1}]-size[a_i]),而这个是小于(0)的,也就是更加的优秀,然后结合冒泡排序的思想,不难发现,优先遍历(size)小的儿子是最优秀的。

    时间复杂度:(O(nlogn+|len|))

    #include<cstdio>
    #include<cstring>
    #include<algorithm>
    #define  N  510000
    using  namespace  std;
    typedef  long  long  LL;
    struct  TREE
    {
    	int  y,next;
    }a[N];int  len,last[N];
    inline  void  ins(int  x,int  y){len++;a[len].y=y;a[len].next=last[x];last[x]=len;}
    
    struct  node
    {
    	int  v,son[30];
    }tr[N];int  cnt=1,rt=1/*根*/;
    inline  int  add(char  *s,int  id)
    {
    	int  now=1;
    	for(int  i=strlen(s)-1;i>=0;i--)
    	{
    		int  x=s[i]-'a';
    		if(!tr[now].son[x])tr[now].son[x]=++cnt;
    		now=tr[now].son[x];
    	}
    	tr[now].v=id;
    	return  now;
    }
    void  dfs1(int  x,int  pre)
    {
    	if(tr[x].v)ins(pre,tr[x].v);
    	for(int  i=0;i<26;i++)
    	{
    		if(tr[x].son[i])dfs1(tr[x].son[i],tr[x].v?tr[x].v:pre);
    	}
    }
    int  siz[N];
    LL  ans;
    int  list[N],top;
    inline  bool  cmp(int  x,int  y){return  siz[x]<siz[y];}
    void  dfs2(int  x)
    {
    	siz[x]=1;
    	for(int  k=last[x];k;k=a[k].next)
    	{
    		int  y=a[k].y;
    		dfs2(y);siz[x]+=siz[y];
    	}
    	top=0;
    	for(int  k=last[x];k;k=a[k].next)list[++top]=a[k].y;
    	if(top)
    	{
    		sort(list+1,list+top+1,cmp);
    		LL  sum=1;
    		for(int  i=1;i<=top;i++)ans+=sum,sum+=siz[list[i]];
    	}
    }
    int  n;char  st[N];
    int  main()
    {
    	scanf("%d",&n);
    	for(int  i=1;i<=n;i++)
    	{
    		scanf("%s",st);
    		add(st,i);
    	}
    	dfs1(1,0);
    	dfs2(0);
    	printf("%lld
    ",ans);
    	return  0;
    }
    
  • 相关阅读:
    git处理冲突提交,撤销提交命令
    RocketMQ(4.7.0)单机与集群搭建,控制台搭建,并与springboot整合
    JVM进程的优雅关闭
    MySQL字段等值查询时,尾部有空格也能匹配上的坑
    带有连接池的Http客户端工具类HttpClientUtil
    比较两个jar包的版本号
    源码解读SLF4J绑定日志实现的原理
    log4j输出到控制台的性能问题
    多表查询
    分页优化
  • 原文地址:https://www.cnblogs.com/zhangjianjunab/p/13879314.html
Copyright © 2011-2022 走看看