9月7号~
开学啦,时间有点紧张。。
暑假算是白费啦~
今天开始刷大白书DP习题。。
一天一个!
废话不多说。。开刷!
Partitioning by Palindromes
第一天~
题目传送:UVA - 11584 - Partitioning by Palindromes
分类:DP入门题。
分析:因为是考虑回文串,很容易想到用两个指针来找以当前点为中点的回文串,每次找到一个回文串就进行更新,不过要注意偶数回文和奇数回文的情况
WA了两次,没注意到每次找到回文串都要更新还有刚开始需要更新一下。。
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
char s[1005];
int dp[1005];//dp[i]表示以i为结尾的子串能够划分的最少回文串
int main() {
int n;
s[0] = '$';
scanf("%d", &n);
while(n -- ) {
scanf("%s", s + 1);
int len = strlen(s + 1);
for(int i = 0; i <= len; i ++) dp[i] = i;
for(int i = 1; i <= len; i ++) {
int p = i - 1, q = i + 1;//左右两个指针
int l = 1;//s[i]为中点时的回文串的长度
dp[i] = min(dp[i], dp[p] + 1);//记得更新所有可能的情况
while(s[i] == s[q] && q <= len) {
l ++;
q ++;
dp[q - 1] = min(dp[q - 1], dp[p] + 1);//记得每走一步都要记得更新一下,不然只更新最后一步会错,比如bdbacabcb
}
while(p >= 0 && q <= len && s[p] == s[q]) {
l += 2;
p --;
q ++;
dp[q - 1] = min(dp[q - 1], dp[p] + 1);
}
dp[q - 1] = min(dp[q - 1], dp[p] + 1);
}
printf("%d\n", dp[len]);
}
return 0;
}
Salesmen
第二天~
题目传送:UVALive - 4256 - Salesmen
分类:DP入门题。
分析:数据比较小,直接考虑暴力递推之。。
此题较顺利,,1A。
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0xfffffff
using namespace std;
int n, n1, n2;
int mp[105][105];
int A[205];
int dp[205][105];//dp[i][j]表示序列中第i个数字为j时需要修改的最少的数
int main() {
int T;
scanf("%d", &T);
while(T --) {
memset(mp, 0, sizeof(mp));
scanf("%d %d", &n1, &n2);
int u, v;
for(int i = 0; i < n2; i ++) {
scanf("%d %d", &u, &v);
mp[u][v] = 1;
mp[v][u] = 1;
}
for(int i = 1; i <= n1; i ++) mp[i][i] = 1;
scanf("%d", &n);
for(int i = 1; i <= n; i ++) {
scanf("%d", &A[i]);
}
//初始化
for(int i = 0; i < 205; i ++) {
for(int j = 0; j < 105; j ++) {
dp[i][j] = INF;
}
}
for(int i = 1; i <= n1; i ++) {
if(i != A[1]) dp[1][i] = 1;
else dp[1][i] = 0;
}
//DP递推
for(int p = 2; p <= n; p ++) {//枚举序列的第几个位置
for(int i = 1; i <= n1; i ++) {//枚举该位置的每个可能取值
if(i != A[p]) {//此位置改变
for(int j = 1; j <= n1; j ++) {
if(mp[i][j] == 1) {
dp[p][i] = min(dp[p][i], dp[p-1][j] + 1);
}
}
}
else if(i == A[p]) {
for(int j = 1; j <= n1; j ++) {
if(mp[i][j] == 1) {
dp[p][i] = min(dp[p][i], dp[p-1][j]);
}
}
}
}
}
//for(int i = 1; i <= n; i ++) {
// for(int j = 1; j <= n1; j ++) {
// cout << dp[i][j] << " ";
// }
// cout << endl;
//}
int ans = INF;
for(int i = 1; i <= n1; i ++) {
ans = min(ans, dp[n][i]);
}
printf("%d\n", ans);
}
return 0;
}
Wavio Sequence
第三天~
题目传送:UVA - 10534 - Wavio Sequence
分类:序列型DP(最长上升子序列)
分析:根据题目意思,可以知道只需要考虑每一个点作为中点时,维护往前的最长上升子序列,和往后的最长下降子序列即可,两个一前一后的子序列之间只要取较小的那个值就是此点的k+1,则更新答案ans = max(ans, 2* (k + 1) - 1)。1A。
其中最长上升子序列用了lower_bound,复杂度为O(n*logn)。
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
int n;
int dp1[10005];//dp1[i]表示从1到i的i个数中的最长上升子序列
int dp2[10005];//dp2[i]表示从i到n的n-i+1个数中的最长下降子序列
int a[10005];
int g[10005];//辅助数组
int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 1; i <= n; i ++) {
scanf("%d" , &a[i]);
}
for(int i = 1; i <= n; i ++) g[i] = INF;
for(int i = 1; i <= n; i ++) {
int k = lower_bound(g + 1, g + n + 1, a[i]) - g;
g[k] = a[i];
dp1[i] = k;
}
for(int i = 1; i <= n; i ++) g[i] = INF;
for(int i = n; i >= 1; i --) {
int k = lower_bound(g + 1, g + n + 1, a[i]) - g;
g[k] = a[i];
dp2[i] = k;
}
int ans = 0;
for(int i = 1; i <= n; i ++) {
int t = min(dp1[i], dp2[i]);
ans = max(ans, t * 2 - 1);
}
printf("%d\n", ans);
}
return 0;
}
Fewest Flops
第四天~
题目传送:UVA - 11552 - Fewest Flops
分类:简单DP,重在设计状态以及状态怎么转移
分析:分成len/k个组就行了,然后定义dp[i][j]表示第i组以j结尾时的最小块数。然后递推即可,注意递推的时候要判断当前状态和前一状态是否存在这个字母,不然会出错,这里分别WA了一下。
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
#define min(a, b) (a) < (b) ? (a) : (b)
using namespace std;
int k;
char s[1005];
int d[1005][26];//d[i][0~25]代表第i个组内是否存在a~z这些字母
int cnt[1005];//cnt[i]代表第i组有多少个不同的字母
int dp[1005][26];//d[i][0~25]代表第i个组以a~z结尾时的最小块数
int main() {
int T;
scanf("%d", &T);
while(T --) {
memset(cnt, 0, sizeof(cnt));
memset(d, 0, sizeof(d));
scanf("%d %s", &k, s);
int len = strlen(s);
int zu = len / k;
int p = 0;
for(int i = 1; i <= zu; i ++) {
for(int j = 0; j < k; j ++, p ++) {
if(d[i][s[p] - 'a'] == 0) {
cnt[i] ++;
d[i][s[p] - 'a'] = 1;
}
}
}
//for(int i = 1; i <= zu; i ++) cout << cnt[i] << " "; cout << endl;
//边界
for(int i = 0; i <= zu; i ++) {
for(int j = 0; j < 26; j ++) {
dp[i][j] = INF;
}
}
for(int i = 0; i < 26; i ++) {
dp[1][i] = cnt[1];
}
//递推
for(int i = 2; i <= zu; i ++) {
for(int j = 0; j < 26; j ++) {//递推当前的26个字母
if(d[i][j] == 1) {//判断当前是否存在j这个字母
for(int k = 0; k < 26; k ++) {//递推前一个状态的26个字母
if(d[i-1][k]) {//判断前一个状态是否存在k这个字母
if((d[i][k] == 1 && k != j) || (d[i][k] == 1 && k == j && cnt[i] == 1)) {
dp[i][j] = min(dp[i][j], dp[i-1][k] + cnt[i] - 1);
}
else dp[i][j] = min(dp[i][j], dp[i-1][k] + cnt[i]);
}
}
}
}
}
int ans = INF;
//for(int i = 1; i <= zu; i ++) {
// for(int j = 0; j < 26; j ++) {
// cout << dp[i][j] << " ";
// }
// cout << endl;
//}
for(int i = 0; i < 26; i ++) {
ans = min(ans, dp[zu][i]);
}
printf("%d\n", ans);
}
return 0;
}
Palindromic Subsequence
第五天~
题目传送:UVA - 11404 - Palindromic Subsequence
分类:序列型DP
分析:就是类似的LCS问题,如果输出长度就很简单,不过这里要输出回文串,想了半天不知怎么存,无奈搜了下题解,原来这里可以写在一个结构体里面来记录最小字典序,还要注意那个最小字典序可能不是回文串,需要奇偶分别判断一下输出
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = 1005;
struct node {
int len;
string str;
}dp[maxn][maxn];//类似LCS的dp,只不过加上了此时的最小字典序
char s1[maxn];
char s2[maxn];
int n, len;
int main() {
while(scanf("%s", s1 + 1) != EOF) {
len = strlen(s1 + 1);
s2[len + 1] = '\0';
for(int i = 1; i <= len; i ++) {
s2[len - i + 1] = s1[i];
}
//初始化
for(int i = 0; i <= len; i ++) {
dp[0][i].len = 0;
dp[0][i].str = "";
}
//printf("%s %s\n", s1 + 1, s2 + 1);
//递推
for(int i = 1; i <= len; i ++) {
for(int j = 1; j <= len; j ++) {
if(s1[i] == s2[j]) {
dp[i][j].len = dp[i-1][j-1].len + 1;
dp[i][j].str = dp[i-1][j-1].str + s1[i];
}
else {
if(dp[i-1][j].len > dp[i][j-1].len) {
dp[i][j].len = dp[i-1][j].len;
dp[i][j].str = dp[i-1][j].str;
}
else if(dp[i][j-1].len > dp[i-1][j].len) {
dp[i][j].len = dp[i][j-1].len;
dp[i][j].str = dp[i][j-1].str;
}
else {
dp[i][j].len = dp[i-1][j].len;
dp[i][j].str = min(dp[i-1][j].str, dp[i][j-1].str);
}
}
}
}
int ma = dp[len][len].len;
string ans = dp[len][len].str;
if(ma & 1) {
for(int i = 0; i < ma / 2; i ++) {
cout << ans[i];
}
for(int i = ma / 2; i >= 0; i --) {
cout << ans[i];
}
cout << endl;
}
else {
for(int i = 0; i < ma / 2; i ++) {
cout << ans[i];
}
for(int i = ma / 2 - 1; i >= 0; i --) {
cout << ans[i];
}
cout << endl;
}
}
return 0;
}
Cellular Network
第六天~
有点累。。
题目传送:UVALive - 4731 - Cellular Network
分类:贪心+概率DP
分析:根据递推式可以很清楚的知道要将概率先从大到小排序,因为要尽可能小。
此外,设dp[i][j]表示前i个分成j组的最小期望值,递推即可
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
double dp[105][105];//dp[i][j]表示前i个分成j组的最小值
double u[105];
double p[105];
double qzh[105];
int n, w;
bool cmp(double a, double b) {
return a > b;
}
int main() {
int T;
scanf("%d", &T);
while(T --) {
scanf("%d %d", &n, &w);
double sum = 0;
for(int i = 1; i <= n; i ++) {
scanf("%lf", &u[i]);
sum += u[i];
}
for(int i = 1; i <= n; i ++) {
p[i] = u[i] / sum;
}
sort(p + 1, p + n + 1, cmp);
//for(int i = 1; i <= n; i ++) cout << p[i] << " "; cout << endl;
for(int i = 1; i <= n; i ++) {
qzh[i] = qzh[i-1] + p[i];
}
for(int i = 1; i <= n; i ++) {//递推前i个
dp[i][1] = i * qzh[i];
for(int j = 2; j <= w && j <= i; j ++) {//分成j组
dp[i][j] = INF;
for(int k = j - 1; k < i; k ++) {//枚举前一状态k个分为j-1组时的情况
dp[i][j] = min(dp[i][j], dp[k][j-1] + i * (qzh[i] - qzh[k]));
}
}
}
printf("%.4lf\n", dp[n][w]);
}
return 0;
}
Mega Man’s Mission
第七天~
题目传送:UVA - 11795 - Mega Man’s Mission
分类:状态压缩DP
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = (1 << 16) + 5;
int n;
LL dp[maxn];//dp[i]表示状态为i时的顺序总数
int kill[maxn];//kill[i]表示状态为i时可以消灭的机器人
int wuqi[35];//武器属性
int main() {
int T;
scanf("%d", &T);
for(int cas = 1; cas <= T; cas ++) {
memset(wuqi, 0, sizeof(wuqi));
char s[35];
scanf("%d", &n);
for(int i = 0; i <= n; i ++) {
scanf("%s", s);
wuqi[i] = 0;
for(int j = 0; s[j]; j ++) {
if(s[j] == '1') {
wuqi[i] |= (1 << j);
}
}
}
int tot = (1 << n) - 1;
kill[0] = wuqi[0];
for(int s = 1; s <= tot; s ++) {
kill[s] = wuqi[0];
for(int i = 1; i <= n; i ++) {
if(s & (1 << (i - 1))) kill[s] |= wuqi[i];
}
}
memset(dp, 0, sizeof(dp));
dp[0] = 1;//一个机器人都不杀的方案为1
for(int i = 1; i <= tot; i ++) {//枚举全集
for(int j = 1; j <= n; j ++) {//枚举当前集合去掉第j个机器人的子集
if(i & (1 << (j -1))) {
int zi = i ^ (1 << (j - 1));
if(kill[zi] & (1 << (j - 1))) {
dp[i] += dp[zi];
}
}
}
}
//for(int i = 0; i <= tot; i ++) {
// printf("%d %d %d\n", i, dp[i], wuqi[i]);
//}
printf("Case %d: %lld\n", cas, dp[tot]);
}
return 0;
}
Jump
第八天~
有点破事,,隔了一天才弄
题目分类:变形的Joseph问题
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = 500005;
int f[maxn];
int a[5];
int n, k;
int main() {
int T;
scanf("%d", &T);
while(T --) {
scanf("%d %d", &n, &k);
for(int i = 1; i <= 3; i ++) {
f[i] = (k - 1) % i;
for(int j = i + 1; j <= n; j ++) f[j] = (f[j - 1] + k) % j;
a[i] = (1 + f[n]) % n;
if(a[i] <= 0) a[i] += n;
}
printf("%d %d %d\n", a[3], a[2], a[1]);
}
return 0;
}
Martian Mining
第九天~
题目传送:UVALive - 3530 - Martian Mining
有点累了,直接上代码吧。。
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = 505;
int n, m;
int A[maxn][maxn];
int B[maxn][maxn];
int dpa[maxn][maxn];//dpa[i][j]表示以i,j为右下角,1,1为左上角所围成的矩形里,在i,j这个点运输A可以得到的最大值
int dpb[maxn][maxn];//dpb[i][j]表示以i,j为右下角,1,1为左上角所围成的矩形里,在i,j这个点运输B可以得到的最大值
int sum_row[maxn][maxn];
int sum_col[maxn][maxn];
int main() {
while(scanf("%d %d", &n, &m) != EOF) {
if(n == 0 && m == 0) break;
memset(dpa, 0, sizeof(dpa));
memset(dpb, 0, sizeof(dpb));
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
scanf("%d", &A[i][j]);
sum_row[i][j] = sum_row[i][j-1] + A[i][j];
}
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
scanf("%d", &B[i][j]);
sum_col[i][j] = sum_col[i-1][j] + B[i][j];
}
}
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= m; j ++) {
dpa[i][j] = max(dpa[i-1][j], dpb[i-1][j]) + sum_row[i][j];
dpb[i][j] = max(dpa[i][j-1], dpb[i][j-1]) + sum_col[i][j];
}
}
printf("%d\n", max(dpa[n][m], dpb[n][m]));
}
return 0;
}
Paths through the Hourglass
第十天~
题目传送:UVA - 10564 - Paths through the Hourglass
啊啊啊啊啊,,不行了啊,,感觉做不动了,,虽然这个还是比较水的题。。因为没有注意到有个地方要反过来,,萎了好久好久。。。
分类:类似01背包的题
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = 385;
int n, S;
int a[45][25];
LL dp[45][25][maxn];//dp[i][j][k]的值表示在位置i,j的时候,选了位于i,j的数时有多少路径上的数之和等于k(这里是逆推)
int main() {
while(scanf("%d %d", &n, &S) != EOF) {
if(n == 0 && S == 0) break;
for(int i = 1; i <= n; i ++) {
for(int j = 1; j <= n - i + 1; j ++) {
scanf("%d", &a[i][j]);
}
}
for(int i = n + 1; i <= 2 * n - 1; i ++) {
for(int j = 1; j <= i - n + 1; j ++) {
scanf("%d", &a[i][j]);
}
}
if(S >= 360) {
printf("0\n\n");
continue;
}
//因为要打印路径,,所以采用逆推比较好
memset(dp, 0, sizeof(dp));
for(int i = 1; i <= n; i ++) {
dp[2 * n - 1][i][a[2 * n - 1][i]] = 1;
}
for(int i = 2 * n - 2; i >= n; i --) {
for(int j = 1; j <= i - n + 1; j ++) {
for(int k = S; k >= 0; k --) {
dp[i][j][k + a[i][j]] += dp[i + 1][j][k] + dp[i + 1][j + 1][k];
}
}
}
for(int i = n - 1; i >= 1; i --) {
for(int j = 1; j <= n - i + 1; j ++) {
for(int k = S; k >= 0; k --) {
dp[i][j][k + a[i][j]] += dp[i + 1][j - 1][k] + dp[i + 1][j][k];
}
}
}
LL cnt = 0;
for(int i = 1; i <= n; i ++) {
cnt += dp[1][i][S];
}
cout << cnt << endl;
if(cnt == 0) {
cout << endl;
continue;
}
int p;
for(int i = 1; i <= n; i ++) {
if(dp[1][i][S] >= 1) {
p = i;
break;
}
}
printf("%d ", p - 1);
int prev = S;//打印路径
for(int i = 2; i <= n; i ++) {
if(dp[i][p-1][S-a[i-1][p]]) {
cout << 'L';
S -= a[i-1][p];
p = p - 1;
}
else {
cout << 'R';
S -= a[i-1][p];
}
}
for(int i = n + 1; i <= 2 * n - 1; i ++) {
if(dp[i][p][S-a[i-1][p]]) {
cout << 'L';
S -= a[i-1][p];
}
else {
cout << 'R';
S -= a[i-1][p];
p = p + 1;
}
}
cout << endl;
}
return 0;
}
Strategic game
第11天~
题目传送:UVALive - 2038 - Strategic game
题目分类:树形DP
题目分析:
基础的树形DP(最小顶点覆盖)
首先定义:
- dp[i][0]代表以i为根节点的子树,不选根节点时所需要的最小点数。
- dp[i][1]代表以i为根节点的子树,选根节点时所需要的最小点数。
先考虑不选根节点时,因为要覆盖所有边,所以他的子节点都要选。
再考虑选了根节点时,此时,他的子节点可选可不选,取较小的那个即可。则有状态转移方程:
- dp[u][0] = ∑ dp[v][1] (v为u的子节点)
- dp[u][1] = ∑ min(dp[v][0], dp[v][1]) (v为u的子节点)
AC代码:
#include <map>
#include <set>
#include <list>
#include <cmath>
#include <deque>
#include <queue>
#include <stack>
#include <bitset>
#include <cctype>
#include <cstdio>
#include <string>
#include <vector>
#include <complex>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <sstream>
#include <utility>
#include <iostream>
#include <algorithm>
#include <functional>
#define LL long long
#define INF 0x7fffffff
using namespace std;
const int maxn = 1505;
int n;
vector<int> G[maxn];
int dp[maxn][2];//dp[i][0]表示以i为根的子树的根节点不选的情况,dp[i][1]表示以i为根的子树的根节点选了的情况
void dfs(int u, int fa) {
dp[u][0] = 0;
dp[u][1] = 1;
int d = G[u].size();
for(int i = 0; i < d; i ++) {
int v = G[u][i];
if(v != fa) {
dfs(v, u);
}
}
for(int i = 0; i < d; i ++) {
int v = G[u][i];
if(v != fa) {
dp[u][0] += dp[v][1];
dp[u][1] += min(dp[v][0], dp[v][1]);
}
}
}
int main() {
while(scanf("%d", &n) != EOF) {
for(int i = 0; i <= n; i ++) {
G[i].clear();
}
int u, v, t;
for(int i = 0; i < n; i ++) {
scanf("%d:(%d)", &u, &t);
for(int i = 1; i <= t; i ++) {
scanf("%d", &v);
G[u].push_back(v);
G[v].push_back(u);
}
}
memset(dp, 0, sizeof(dp));
dfs(0, -1);
printf("%d\n", min(dp[0][0], dp[0][1]));
}
return 0;
}