[COCI 2017/2018Round #6] Alkemija 题解

题目描述

在古代,当炼金术士寻找黄金时,全世界一共熟悉N种不同的物质,用1到N进行表示。在经过多年的努力之后,炼金术士们发现了一个有趣的规律。在一个反应中,可以将物质集{X1,X2,…,XL}转化为另一个物质集{Y1,Y2,…,YR}。例如,物质集{1,4,5}可能会发生反应一次转化为物质集{2,6}.
Josko是一位现代炼金术士,他有M种不同的物质,表示为A1,A2,…AM,每种物质有无限量这么多。Josko想知道,他可以通过古代炼金术士的反应清单反应后,他能够拥有哪些物质?

输入格式
第一行输入两个数字N(1≤N≤100000),M(1≤M≤100000),表示古代炼金术士所熟悉的N种物质以及Josko所拥有的物质种类。
第二行输入M个数字Ai,表示Josko所拥有哪些种物质。
第三行输入一个数字K(1≤K≤100000),表示古代炼金术士所知道的反应数。
接下来输入3*K行,每三行中的第一行输入两个数字L和R(1≤L,R≤N),表示反应前和反应后的物质种类数。第二行输入L个数字Xi,表示反应前物质的种类。第三行输入R个数字Yi表示反应后的种类。
题目保证L和R的总和分别不超过100000.

输出格式
第一行输出一个数字X,表示Josko最终能得到多少种物质。
第二行输出X个数,每个数表示Josko反应后得到的物质种类。

样例输入输出

Sample Input 1
4 2
1 2
2
2 1
1 2
3
2 1
1 3
4
Sample Output 1
4
1 2 3 4

Sample Input 2
6 3
1 4 5
3
3 2
2 3 4
1 6
1 3
4
1 5 6
1 1
6
2
Sample Output 2
5
1 2 4 5 6

题解

首先,这道题不是可以用几重循环就可以搞定的,虽然看上去挺像的。。。
其次,看着这数据范围,好多零啊 ,也不是能用暴力解决的。。。(我在考试的时候就是暴力 + 优化,骗了80分。。。)

那么,我们先来想一想正常的思路。

Created with Raphaël 2.2.0 开始 完成可以进行且尚未进行的实验 没有新元素产生 结束 yes no

(Ps:第一次画流程图,语言表述有点奇怪。。。)
思路很好想,暴力也很好打,80分代码如下:

#include <cstdio>
#include <iostream>
#include <vector>
using namespace std;

const int N = 100000;
int n, m, k, cnt;
bool have_[N + 5], used[N + 5];
vector < int > before_[N + 5], after_[N + 5];

int main () {
	//freopen ("alkemija.in", "r", stdin);
	//freopen ("alkemija.out", "w", stdout);
	
	scanf ("%d %d", &m, &n);
	
	for (int i = 1; i <= n; ++ i) {
		int x;
		scanf ("%d", &x);
		have_[x] = true;
	}
	
	scanf ("%d", &k);
	for (int i = 1; i <= k; ++ i) {
		int l, r;
		scanf ("%d %d", &l, &r);
		for (int j = 1; j <= l; ++ j) {
			int x;
			scanf ("%d", &x);
			before_[i].push_back( x );
		}
		for (int j = 1; j <= r; ++ j) {
			int x;
			scanf ("%d", &x);
			after_[i].push_back( x );
		}
	}
	
	bool changed = true;
	cnt = n;
	while ( changed == true && cnt < m) {
		changed = false;
		
		for (int i = 1; i <= k; ++ i) {
			if ( used[i] == true )
				continue;
			
			bool flag = true;
			for (int j = 0; j < before_[i].size(); ++ j)
				if (have_[ before_[i][j] ] == false) {
					flag = false;
					break;
				}
			
			if (flag == true) {
				used[i] = true;
				for (int j = 0; j < after_[i].size(); ++ j)
					if ( have_[ after_[i][j] ] == false ) {
						++ cnt;
						have_[ after_[i][j] ] = true;
						changed = true;
					}
			}
		}
	}
	
	printf ("%d\n", cnt);
	int i;
	for (i = 1; i <= m; ++ i)
		if (have_[i] == true) {
			printf ("%d", i);
			++ i;
			break;
		}
	for ( ; i <= m; ++ i)
		if (have_[i] == true)
			printf(" %d", i);
	putchar ('\n');
	return 0;
}

不过,想要Ac该怎么办呢?
我仔细观察,暴力的代码主要是会重复计算很多次,导致时间超限,那么,如何避免呢?
本题不能从实验下手,只能从元素下手,如果我们可以保证每种元素只参与一次实验,不就不会超时了吗?
所以,我们联想到用vector来保存已有且尚未使用的元素,再用一个vector来保存可以进行的实验。为了实现,我们需要在每个元素上向需要它作为原材料的实验连一条边;再在每个实验上向它可以产出的元素连一条边,然后再进行暴搜中的那种循环就可以了。(原谅我不会算时间复杂度。。。)
感谢万能的STL,感谢万能的vector

参考代码

以下代码已Ac

#include <cstdio>
#include <iostream>
#include <vector>
#include <queue> 
using namespace std;

const int N = 100000;
int n, m, k, need_[N + 5], last_, cnt;
bool have_[N + 5];
vector < int > waiting, fac;	//可以进行且尚未的实验, 术士拥有且尚未使用的元素 
vector < int > before_[N + 5], after_[N + 5];	//由原材料可以进行的实验, 由实验产出的原材料 
priority_queue < int, vector < int >, greater < int > > ans;

int main () {
	//freopen ("alkemija .in", "r", stdin);
	//freopen ("alkemija .out", "w", stdout);
	
	scanf ("%d %d", &m, &n);
	for (int i = 1; i <= n; ++ i) {
		int x;
		scanf ("%d", &x);
		have_[x] = true;
		fac.push_back( x );
		ans.push( x );
	}
	cnt = n;
	
	scanf ("%d", &k);
	for (int i = 1; i <= k; ++ i) {
		int l, r;
		scanf ("%d %d", &l, &r);
		need_[i] = l;
		for (int j = 1; j <= l; ++ j) {
			int x;
			scanf ("%d", &x);
			before_[x].push_back( i );
		}
		for (int j = 1; j <= r; ++ j) {
			int x;
			scanf ("%d", &x);
			after_[i].push_back( x );
		}
	}
	
	for (int i = 0; i < fac.size(); ++ i) {
		int x = fac[i];
		
		for (int j = 0; j < before_[x].size(); ++ j) {
			int t = before_[x][j];
			-- need_[t];
			if (need_[t] == 0)
				waiting.push_back( t );
		}
		
		for (; last_ < waiting.size(); ++ last_) {
			int t = waiting[last_];
			for (int k = 0; k < after_[t].size(); ++ k) {
				int factor = after_[t][k];
				if (have_[factor] == false) {
					++ cnt;
					have_[factor] = true;
					ans.push( factor );
					fac.push_back( factor );
				}
			}
		}
	}
	
	printf ("%d\n", cnt);
	printf ("%d", ans.top());
	ans.pop();
	while (! ans.empty()) {
		printf (" %d", ans.top());
		ans.pop();
	}
	putchar ('\n');
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值