hihocoder #1136 : Professor Q's Software

时间限制: 10000ms
单点时限: 1000ms
内存限制: 256MB

描述

Professor Q develops a new software. The software consists of N modules which are numbered from 1 to N. The i-th module will be started up by signal Si. If signal Si is generated multiple times, the i-th module will also be started multiple times. Two different modules may be started up by the same signal. During its lifecircle, the i-th module will generate Ki signals: E1, E2, ..., EKi. These signals may start up other modules and so on. Fortunately the software is so carefully designed that there is no loop in the starting chain of modules, which means eventually all the modules will be stoped. Professor Q generates some initial signals and want to know how many times each module is started.hihocoder #1136 : Professor Q's Software_第1张图片

输入

The first line contains an integer T, the number of test cases. T test cases follows.

For each test case, the first line contains contains two numbers N and M, indicating the number of modules and number of signals that Professor Q generates initially.

The second line contains M integers, indicating the signals that Professor Q generates initially.

Line 3~N + 2, each line describes an module, following the format S, K, E1, E2, ... , EK. S represents the signal that start up this module. K represents the total amount of signals that are generated during the lifecircle of this module. And E1 ... EK are these signals.

For 20% data, all N, M <= 10
For 40% data, all N, M <= 103
For 100% data, all 1 <= T <= 5, N, M <= 105, 0 <= K <= 3, 0 <= S, E <= 105.

Hint: HUGE input in this problem. Fast IO such as scanf and BufferedReader are recommended.

输出

For each test case, output a line with N numbers Ans1, Ans2, ... , AnsN. Ansi is the number of times that the i-th module is started. In case the answers may be too large, output the answers modulo 142857 (the remainder of division by 142857).

样例输入
3
3 2
123 256
123 2 456 256
456 3 666 111 256
256 1 90
3 1
100
100 2 200 200
200 1 300
200 0
5 1
1
1 2 2 3
2 2 3 4
3 2 4 5
4 2 5 6
5 2 6 7
样例输出
1 1 3
1 2 2
1 1 2 3 5
解题思路:这个题目的思路很简单,建立好图之后,DFS扫一遍就ok了。。。但看官方题解,这个算法还不是最优的,最优的是建立的图之后进行拓扑排序。。。仔细想想确实是这样的,只能说观察不够细致。。

解题思路

通过题意,我们首先可以确定,不同模块之间可以按照以下原则构成一个有向无环图:

  • 初始信号流,即 M 个初始信号,我们将其设定为模块0发出的信号
  • 若模块i发出的信号能够使得模块j被激活,我们连接一条从ij的有向边(i,j)

比如数据:

3 2
123 256
123 2 456 256
456 3 666 111 256
256 1 90

对应的图为:

hihocoder #1136 : Professor Q's Software_第2张图片

由于原题中给出的信号在0~10^5之间,因此我们可以用一个大小为10^5的数组signList来记录信号可以激活的模块,辅助我们构造整个图:

Input n
Input m
// 将初始数据流做为 module 0
For i = 1 .. m
    Input sign
    module[0].sendSign.push(sign)
End For
// 读取其他module的信息
For i = 1 .. n
    Input sign
    module[i].activeSign = sign
    signList[ sign ].canActiveModule.push(i)
    Input num
    For j = 1 .. num
        Input sign
        module[i]sendSign.push(sign)
    End For
End For
// 构造有向无环图
For i = 0 .. n
    For sign in module[i].sendSign
        For j in signList[ sign ].canActiveModule
            addEdge(i, j)
        End For
    End For
End For

在得到有向无环图之后,一个简单的做法是直接在上面做一次DFS,去统计每个点被访问到的次数:

DFS(nowModule):
    If (nowModule not 0) Then
        activeCount[ nowModule ] = activeCount[ nowModule ] + 1
    End If
    For each j in (nowModule, j)
        DFS(j)
    End For

该算法的时间复杂度非常高,但由于本题没有设计专门针对的数据,所以在测试时也能通过所有的测试点。

但是显然这不是我们要的最优算法,本题实际考察的算法为拓扑排序(toposort)。

利用拓扑排序,在O(n + m)的时间内计算出所有点被访问的次数,具体的算法讲解可以参见hiho一下第48期

在本题中,访问次数对应的为第48期题目中的病毒数量。因此我们在构造完图之后,可以使用同样的算法来解决:

// 在构造图时同时统计入度
For i = 0 .. n
    For sign in module[i].sendSign
        For j in signList[ sign ].canActiveModule
            addEdge(i, j)
            inDegree[j] = inDegree[j] + 1
        End For
    End For
End For

// 进行拓扑排序
tail = 0;
For i = 0 .. n  // 这里一定要从0开始,因为Module 0也是图中的点
    If (inDegree[i] == 0) Then  // 入度为0的点
        sequence[tail] = i
        tail = tail + 1
    End If
End For

activeCount[0] = 1  // 设定初始信号流的访问次数为1
activeCount[1 .. n] = 0
i = 0
While (i < tail) Then
    nowModule = sequence[i]
    For each j in (nowModule, j)
        activeCount[j] = activeCount[j] + activeCount[ nowModule ]
        inDegree[j] = inDegree[j] - 1
        If (inDegree[j] == 0) Then
            sequence[tail] = j
            tail = tail + 1
        End If
    End For
    i = i + 1
End While

最后再将activeCount数组依次输出即可。由于本题有多组数据,在实现时一定要注意初始化。


AC:
#include<cstdio>
#include<cstring>
using namespace std;

const int maxn = 1e5+5;
struct SOFTWARE
{
	int start;
	int num;
	int E[5];
}sft[maxn];

struct VERTEX
{
	int startE;
	int first;
}ver[maxn];

struct Node
{
	int id;
	int next;
}list[maxn];
int n,m,sign[maxn],cnt,vis[maxn];

void add(int e,int id)
{
	ver[e].startE = e;
	list[cnt].id = id;
	if(ver[e].first == -1) ver[e].first = cnt++;
	else
	{
		list[cnt].next = ver[e].first;
		ver[e].first = cnt++;
	}
}

void init()
{
	memset(vis,0,sizeof(vis));
	memset(list,-1,sizeof(list));
	memset(ver,-1,sizeof(ver));
	cnt = 0;
}

void dfs(int cur,int e)
{
	if(ver[e].first == -1) return;
	for(int i = ver[e].first; i != -1; i = list[i].next)
	{
		int k = list[i].id;
		vis[k]++;
		for(int j = 1; j <= sft[k].num; j++)
			dfs(k,sft[k].E[j]);
	}
}

int main()
{	
	int t;
	scanf("%d",&t);
	while(t--)
	{
		init();
		scanf("%d%d",&n,&m);
		for(int i = 1; i <= m; i++)
			scanf("%d",&sign[i]);
		for(int i = 1; i <= n; i++)
		{
			scanf("%d%d",&sft[i].start,&sft[i].num);
			add(sft[i].start,i);
			for(int j = 1; j <= sft[i].num; j++)
				scanf("%d",&sft[i].E[j]);
		}
		for(int i = 1; i <= m; i++)
			dfs(0,sign[i]);
		for(int i = 1; i <= n; i++)
			printf("%d ",vis[i]);
		printf("\n");
	}
	return 0;
}


你可能感兴趣的:(hihoCoder)