hdu5118,图上的推导计算

来源:互联网 发布:在淘宝购买网店 编辑:程序博客网 时间:2024/06/05 15:42

hdu5118
GRE Words Once More!

Time Limit: 5000/5000 MS (Java/Others) Memory Limit: 512000/512000 K (Java/Others)
Total Submission(s): 265 Accepted Submission(s): 48

Problem Description
Now Matt is preparing for the Graduate Record Examinations as Coach Pang did in 2013 and George did in 2011.

Thanks to modern techniques, Matt uses automata instead of old-fasioned vocabulary books.

The automata used by Matt is a directed acyclic graph (DAG) with N vertices and M edges. The vertices are conveniently numbered by 1, 2, … , N . Each edge is labeled with an integer. Additionally, some vertices are marked as special.

A GRE word is obtained by concatenating the labels on the path from vertex 1 to a special vertex.

Now, Matt has Q questions. The i-th question is asking for the length of ki-th smallest words among all the GRE words he can obtain in lexicographical order.

Input
The first line contains only one integer T , which indicates the number of test cases.

For each test case, the first line contains three integers N, M, Q (2 ≤ N ≤ 105, 0 ≤ M ≤ 105, 1 ≤ Q ≤ 105).

The second line contains N - 1 integers s2, … , sn. If the i-th vertex is special, then si = 1. Otherwise, si = 0. Vertex 1 is never special.

Each of the following M lines contains three integers ai, bi, ci denoting an edge from vertex ai to vertex bi labeled with ci (1 ≤ ai, bi ≤ N, 1 ≤ ci ≤ 109). For each vertex v, all outgoing edges are labeled with distinct integers.

Each of the following Q lines contains the integer ki (1 ≤ ki ≤ 108) of the i-th question.

Output
For each test case, output “Case #x:” in the frirst line, where x is the case number (starting from 1).

Then, for each question, output the length of the word in one line. If the word does not exist, output “-1” (without quotes) instead.

Sample Input
1
3 3 4
1 1
1 2 1
1 3 12
2 3 3
1
2
3
4

Sample Output
Case #1:
1
2
1
-1

Hint
There are 3 GRE words in total (sorted in lexicographical order):
1. (1)
2. (1, 3)
3. (12)

Source
2014ACM/ICPC亚洲区北京站-重现赛(感谢北师和上交)
题意:给定一个有无环图,边权代表一个字母(事实上用数字表示的),然后给定2~n个点的特殊性,1表示特殊,0表示不特殊,从1出发,到一个特殊点停止,这条路径上的所有字母拼成一个单词,将所有单词按照字典序排序后,然后Q组询问,问第k个单词的长度是多少。
在不看别人的题解之前不知道怎么写,光是题目都很难读懂。。。
这里写图片描述
黄色代表特殊点;像这样的图(树),每个点只有一个入边,我们发现,从1深搜,每次寻找下一个点时,总是找边权最小的边(可以事先给每个顶点出发的所有边按照权值排个序,用vector邻接表实现),那么找到第i(1<=i<=1e5)个特殊点,1~i路径上的字母组成的单词就是第i个单词,长度就是该点的深度(用ans[ i ]=d ,i表示第i个单词,d表示单词长度)。
这里写图片描述
而像这样的图,我们还是以上面方法深搜,当把2号后面的点都遍历完后回溯,1->3,发现我们3之前用过了,怎么办,这时候我们应该用一些数组搞一些事情了,把第一次从3号点出发可以得到的单词数(现在用words[ ]表示)记下来,把3号点的第一次访问时的深度记下来,这样就可利用一下words数组了,第二次访问3号点时,可以简单的知道当前3号点的深度dep_u,那么路径1->3->4的单词长度就等于1->2->3->4的路径单词长度 减 去 1->2->3的路径的单词长度加上当前深度dep_u,从3号节点出发有几个单词,就可以计算几个单词。那么怎么知道1->2->3->4的单词长度呢,好,重点到了!!!
我们在所有点第一次访问时,记录访问该点时已经访问了几个特殊点,用数组firstVis[ u ]记录,那么在我们第二次第三次…访问该点时,发现之前有a(a=firstVis[ u ])个特殊点,该点u后面有b个单词,i=a+j for(1<=j<=b),i就是第i个单词,ans[ i ]就是第i个单词的长度。ans[++num]=ans[firstVis[u]+j ]-firstDep[u](第一次访问u时,u的长度)+dep_u(当前u的深度)。
超级详细的代码:

#include <stdio.h>#include <iostream>#include <string.h>#include <algorithm>#include <vector>using namespace std;const int MAXN=1e5+10;const int MAXM=1e8;int sum;int u,v;vector< pair<int,int> >G[MAXN];int firstVis[MAXN],firstDep[MAXN],ans[MAXM+4],words[MAXN];int special[MAXN];void dfs(int u,int dep_u){    if(sum>=MAXM)///个数严格小于1e8啊        return;    if(words[u]!=-1)///之前用过u点了    {        for(int i=1; i<=words[u]; i++)        {            if(sum>=MAXM)///个数严格小于1e8啊                return;            ans[++sum]=ans[ firstVis[u]+i ]-firstDep[u]+dep_u;            ///利用u后面的单词个数,计算本次可以的到新单词的长度        }        return;///!!!!!!计算完就退出就行了,不用再往下深搜了    }    firstVis[u]=sum;///第一次遇见u,u之前有多少特殊点    firstDep[u]=dep_u;///第一次遇见u,u的深度    if(special[u])///如果是特殊点,肯定是第一次遇见啊,深度就是第sum个单词的长度        ans[++sum]=dep_u;    for(int i=0; i<G[u].size(); i++)    {        dfs(G[u][i].second,dep_u+1);    }    words[u]=sum-firstVis[u];///第一次访问u点回溯后,访问过的所有特殊点减去访问u                             ///之前的特殊点个数,就是从u出发可以得到的单词个数} int main(){    int T;    int n,m,q,w;    cin>>T;    for(int cas=1; cas<=T; cas++)    {        scanf("%d%d%d",&n,&m,&q);        for(int i=2; i<=n; i++)            scanf("%d",&special[i]);        for(int i=0; i<m; i++)        {            scanf("%d%d%d",&u,&v,&w);            G[u].push_back(make_pair(w,v));        }        for(int i=1; i<=n; i++)            sort(G[i].begin(),G[i].end());///按照first即w值排序,那么搜索时就是字典序了        memset(words,-1,sizeof(words));        sum=0;        dfs(1,0);        int x;        printf("Case #%d:\n",cas);        while(q--)        {            scanf("%d",&x);            if(x>sum)                puts("-1");            else                printf("%d\n",ans[x]);        }        for(int i=1;i<=n;i++)            G[i].clear();///!!!!别忘了清空啊    }    return 0;}

同时感谢两位大神的blog
某某神
某某某神