洛谷P3294 [SCOI2016]背单词——题解

题目传送node

阅读理解题题意解释能够看这位大佬的博客ios

  发现求后缀与倒序求前缀是等价的,而找前缀天然就想到了trie树。将全部字符串翻转后再建入trie树中,再对每个字符串翻转后从trie树中找前缀,就能找到一个字符串的全部后缀了。git

  由第三种状况知咱们要想最小化总代价,则最小化一个字符串与最靠近它的后缀间的距离应该也是一个要考虑的因素。其实一个串最近的后缀其实必定是它的全部后缀中长度最大的,由于它的后缀中长度短的也必定是长度大的后缀,而且还要避免第一种状况的出现(即序列在一个字符串后面的串中不能有这个字符串的后缀)。故咱们只要知道每一个字符串最长的后缀就好。若一个串是另外一个串的后缀,则能够从这个串向另外一个串连一条有向边,则最终会造成一个森林。ide

  这时避免了第一种状况后,还有两种状况。咱们作了这么多题,知道一种状况总应该比多种状况好作。故考虑将两种状况转化为一种。发现若是咱们在全部题目给出的字符串的基础上再加入一个处于序列第0个位置的空串,那么第二种状况也就转化为了第三种状况,而且对答案没有影响。这种转化对于当前的森林,只要再建个标号为0的空串节点连向全部树的根就行了。优化

  此时问题就转化为:给树的节点编号,要求父亲节点的序号比儿子节点小,且根节点的序号为0,并使得儿子节点的编号减父亲节点编号的差的和最小,求最小的和。考虑怎么选点编号。做者一开始也懵的一逼因而看了看这位大佬博客关于选法的证实部分(“考虑建出……Q.E.D”)终于明白若是选到了一个点,就要一口气把它的子树都选了,而且优先选子树大小最小的儿子。dfs就搞定了呀。至于对某个点的儿子从子树大小小的往大的选,能够用堆维护。spa

具体实现请看代码:3d

  1 #include<iostream>
  2 #include<cstdio>
  3 #include<cstring>
  4 #include<queue>
  5 
  6 #define max(a,b) ((a)>(b)?(a):(b))
  7 
  8 using namespace std;
  9 
 10 const int N=100005,LEN=510005;
 11 
 12 int tree[510005][26],cnt,n,l,dfs;
 13 int ed[LEN],in[N],lst[N],to[N],nxt[N],ecnt,dfn[N];
 14 
 15 long long siz[N],ans;
 16 
 17 string word[N];
 18 
 19 char ch;
 20 
 21 inline int read()
 22 {
 23     int x=0;
 24     ch=getchar();
 25     while(!isdigit(ch)) ch=getchar();
 26     while(isdigit(ch)) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
 27     return x;
 28 }
 29 
 30 inline void getstring(string &a)
 31 {
 32     a="";
 33     ch=getchar();
 34     while(ch<'a'||ch>'z')
 35         ch=getchar();
 36     while(ch>='a'&&ch<='z')
 37         a+=ch,ch=getchar();
 38 }
 39 
 40 inline void insert(const string &a,int j)
 41 {
 42     l=a.length();
 43     int now=0,num;
 44     for(int i=l-1;i>=0;--i)//要将字符串倒序插入trie树中 
 45     {
 46         num=a[i]-'a';
 47         if(!tree[now][num])
 48             tree[now][num]=++cnt;
 49         now=tree[now][num];
 50     }
 51     ed[now]=j;
 52 }
 53 
 54 inline void addedge(int u,int v)
 55 {
 56     nxt[++ecnt]=lst[u];
 57     lst[u]=ecnt;
 58     to[ecnt]=v;
 59 }
 60 
 61 inline int fin(const string &a)
 62 {
 63     int now=0,num,ret=-1;
 64     l=a.length();
 65     for(int i=l-1;i>=0;--i)//查后缀就是倒序查前缀 
 66     {
 67         num=a[i]-'a';
 68         if(!tree[now][num])
 69             return ret;
 70         now=tree[now][num];
 71         if(ed[now]&&i)
 72             ret=ed[now];
 73     }
 74     return ret;
 75 }
 76 
 77 void dfssiz(int u)
 78 {
 79     siz[u]=1;
 80     for(int e=lst[u];e;e=nxt[e])
 81     {
 82         dfssiz(to[e]);
 83         siz[u]+=siz[to[e]];
 84     }
 85 }
 86 
 87 struct node{
 88     int lar,ord;
 89 }head;
 90 
 91 inline bool operator < (const node &a,const node &b)
 92 {
 93     return a.lar>b.lar;//大根堆变小根堆 
 94 }
 95 
 96 void dfsans(int u)
 97 {
 98     priority_queue<node>hep;
 99     for(int e=lst[u];e;e=nxt[e])
100     {
101         hep.push((node){siz[to[e]],to[e]});    
102     }
103     while(!hep.empty())
104     {
105         head=hep.top();
106         hep.pop();
107         dfn[head.ord]=++dfs;//编号过程 
108         ans+=dfn[head.ord]-dfn[u];
109         dfsans(head.ord);
110     }
111 }
112 
113 int main()
114 {
115     n=read();
116     for(int i=1;i<=n;++i)
117     {
118         getstring(word[i]);//字符串的读入优化 
119         insert(word[i],i);
120     }
121     int u;
122     for(int i=1;i<=n;++i)
123     {
124         u=fin(word[i]);
125         if(u!=-1)
126         {
127             addedge(u,i);
128             in[i]++;
129         }
130     }
131     for(int i=1;i<=n;++i)
132         if(!in[i])//没有入度的点就是森林中树的根 
133             addedge(0,i);
134     dfssiz(0);//求一下每一个点的子树大小。 
135     dfsans(0);//统计答案。 
136     printf("%lld",ans);
137     return 0;
138 }
AC代码
相关文章
相关标签/搜索