题目描述
在古代,当炼金术士寻找黄金时,全世界一共熟悉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分。。。)
那么,我们先来想一想正常的思路。
(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;
}