SHOI2008 题目总结
感觉还是上海人出题水平高?这套题写得心旷神怡的。。。总之很难就是啦
由于我实在不适应博客园这种排版和字体。。所以我的文章可能会特别难看大家见谅。。说不定回头开发一个支持全局LaTeX的博客也不错?2333
BZOJ1018 堵塞的交通:
题目大意:有一个2*n的矩阵,初始时没有边,每次可能会打通两个相邻的节点(相邻指曼哈顿距离为1)之间的无向道路或是拆毁两个相邻节点的道路,每次询问两个节点是否连通。
神奇的做法:使用动态树维护整个图的连通性!(这真的可写么?)
正确的做法:由于只有两排,使用一个线段树来维护连通性就可以了。。思路就是线段树的每个叶子节点维护四个节点的C字形的连通性,每个非叶子节点维护这一段的四个顶点的C字型(每个线段树节点其实是一个并查集) 虽然连接方式有很多种情况,但毕竟是有限的几种,手工特判一下就好了。。细节挺多的。特别要注意从两边联通这种情况。随便写一写就好了。
1 //date 20140624 2 #include <cstdio> 3 #include <cstring> 4 5 const int maxn = 105000; 6 7 inline int getint() 8 { 9 int ans(0); char w = getchar(); 10 while(w < '0' || '9' < w) w = getchar(); 11 while('0' <= w && w <= '9'){ans = ans * 10 + w - '0'; w = getchar();} 12 return ans; 13 } 14 15 template <typename T> inline void swap(T &a, T &b){T x = a; a = b; b = x;} 16 17 /* 18 0---------2 19 | | 20 | | 21 | | 22 1---------3 23 */ 24 int tmp[10]; 25 struct info 26 { 27 int num[4]; 28 info(){} 29 info(int a, int b, int c, int d){num[0] = a; num[1] = b; num[2] = c; num[3] = d;} 30 void std() 31 { 32 memset(tmp, 0, sizeof tmp); int count = 0; 33 for(int i = 0; i < 4; ++i) if(!tmp[num[i]]) tmp[num[i]] = ++count; 34 for(int i = 0; i < 4; ++i) num[i] = tmp[num[i]]; 35 } 36 }; 37 38 struct DSU 39 { 40 int p[7]; 41 void preset(){ for(int i = 1; i <= 6; ++i) p[i] = i;} 42 DSU(){preset();} 43 int getp(int x){return p[x] == x ? x : (p[x] = getp(p[x]));} 44 void merge(int x, int y){if((x = getp(x)) != (y = getp(y))) p[x] = y;} 45 }; 46 47 inline info operator+ (info A, info B) 48 { 49 DSU MEOW; 50 for(int i = 0; i < 3; ++i) for(int j = i + 1; j < 4; ++j) 51 if(A.num[i] == A.num[j]) MEOW.merge(i + 1, j + 1); 52 for(int i = 0; i < 3; ++i) for(int j = i + 1; j < 4; ++j) 53 if(B.num[i] == B.num[j]) MEOW.merge(i + 3, j + 3); 54 info ans(MEOW.getp(1), MEOW.getp(2), MEOW.getp(5), MEOW.getp(6)); ans.std(); 55 return ans; 56 } 57 58 int n; 59 info wius[8]; 60 int now[maxn]; 61 62 inline void preset() 63 { 64 wius[0] = info(1, 2, 3, 4); 65 wius[1] = info(1, 2, 1, 3); 66 wius[2] = info(1, 1, 2, 3); 67 wius[3] = info(1, 1, 1, 2); 68 wius[4] = info(1, 2, 3, 2); 69 wius[5] = info(1, 2, 1, 2); 70 wius[6] = info(1, 1, 2, 1); 71 wius[7] = info(1, 1, 1, 1); 72 } 73 74 75 struct zkw_sget 76 { 77 int base; info data[300000]; 78 void preset() 79 { 80 for(base = 1; base < n + 2; base <<= 1); 81 for(int i = 1; i < (base << 1); ++i) data[i] = wius[0]; 82 } 83 84 void change(int pos, int val) 85 { 86 data[pos + base] = wius[now[pos] = val]; 87 for(int i = (pos + base) >> 1; i; i >>= 1) data[i] = data[i << 1] + data[(i << 1) | 1]; 88 } 89 90 info mini_query(int l, int r) 91 { 92 info ansl(wius[5]), ansr(wius[5]); 93 for(l = l + base - 1, r = r + base + 1; l < r - 1; l >>= 1, r >>= 1) 94 { 95 if(!(l & 1)) ansl = ansl + data[l ^ 1]; 96 if( (r & 1)) ansr = data[r ^ 1] + ansr; 97 } 98 return ansl + ansr; 99 } 100 101 info query(int l, int r) 102 { 103 info ansl, ansm, ansr; 104 ansl = mini_query(1, l - 1); 105 ansm = mini_query(l, r - 1); 106 ansr = mini_query(r, n); 107 if(ansl.num[2] == ansl.num[3]) ansm = wius[7] + ansm; 108 if(ansr.num[0] == ansr.num[1]) ansm = ansm + wius[7]; 109 return ansm; 110 } 111 }MEOW; 112 113 char order[10]; 114 115 int main() 116 { 117 n = getint(); preset(); 118 MEOW.preset(); 119 while(true) 120 { 121 scanf("%s", order); 122 int x1, y1, x2, y2; 123 switch(order[0]) 124 { 125 case 'E': return 0; break; 126 case 'O': case 'C': 127 x1 = getint(); y1 = getint(); x2 = getint(); y2 = getint(); 128 if(y1 > y2){swap(x1, x2); swap(y1, y2);} 129 if(y1 == y2) 130 { 131 MEOW.change(y1, now[y1] ^ 2); 132 }else { 133 if(x1 == 1) MEOW.change(y1, now[y1] ^ 1); 134 else MEOW.change(y1, now[y1] ^ 4); 135 } 136 break; 137 138 case 'A': 139 x1 = getint(); y1 = getint(); x2 = getint(); y2 = getint(); 140 if(y1 > y2){swap(x1, x2); swap(y1, y2);} 141 info tmp = MEOW.query(y1, y2); 142 printf(tmp.num[x1 - 1] == tmp.num[x2 + 1] ? "Y\n" : "N\n"); 143 break; 144 } 145 } 146 return 0; 147 }
BZOJ1019 汉诺塔:
题目大意:就是正常的三个柱子的汉诺塔,然后一共有六种可能的移动操作,给这些操作排了序,非次选择操作中中合法的(不能把一个大的挪到小的上,不能挪动上一次挪过的盘子)中优先级最高的,问这样多少次可以将所有的盘子挪到另外一个柱子上
首先这样做一定可以挪完是很显然的,而我们每次合法的操作其实不超过3个。由于原版汉诺塔的正解就是递归,那个递归就是DP,所以这东西我们还考虑是否可以DP。非常好,每次状态之和上一次有关,所以显然是可以DP的。
用f[x][i]表示将x号柱子上的从小到大最小的i个盘子整个挪走会挪到哪。
考虑转移方程\[f[x][i] = \left\{ \begin{array}{cl} f[x][i-1] & f[f[x][i-1]][i-1] = x\\ 6-x-f[x][i-1]&else \end{array}\right.\]
这个貌似很显然?首先如果将i-1个盘子挪到某个位置后,一定该挪第i个了,如果再挪那i-1个会挪回原来的位置,那么第i个不可能挪到和i-1相同的位置(那是不合法的),那么只能挪到与x和f[x][i-1]都不同的位置:6 - x - f[x][i - 1]。接下来我们考虑那i-1个应该挪到哪。如果挪回去了,那么第i个也必须挪回去(因为i-1出环了,所以不可能挪到它上面)。
然后dp[x][i]表示将x柱子上的i个盘子挪走需要的最小次数……这就很简单了
1 //date 20140624 2 #include <cstdio> 3 #include <cstring> 4 5 typedef long long ll; 6 const int maxn = 35; 7 8 ll n; 9 ll f[maxn][maxn], dp[maxn][maxn]; 10 char order[maxn][maxn]; 11 12 int main() 13 { 14 scanf("%lld", &n); 15 for(int i = 1; i <= 6; ++i) 16 { 17 scanf("%s", order[i] + 1); 18 if(!f[order[i][1] - 'A' + 1][1]) f[order[i][1] - 'A' + 1][1] = order[i][2] - 'A' + 1; 19 } 20 for(int i = 2; i <= n; ++i) 21 for(int x = 1; x <= 3; ++x) 22 { 23 if(f[f[x][i - 1]][i - 1] == x) f[x][i] = f[x][i - 1]; 24 else f[x][i] = 6 - x - f[x][i - 1]; 25 } 26 27 dp[1][1] = dp[2][1] = dp[3][1] = 1; 28 for(int i = 2; i <= n; ++i) 29 for(int x = 1; x <= 3; ++x) 30 { 31 if(f[x][i - 1] == f[x][i]) 32 { 33 dp[x][i] = 1 + dp[x][i - 1] + dp[f[x][i]][i - 1] + 1 + dp[x][i - 1]; 34 } 35 else dp[x][i] = dp[6 - x - f[x][i]][i - 1] + dp[x][i - 1] + 1; 36 } 37 printf("%lld\n", dp[1][n]); 38 return 0; 39 }
BZOJ1020 安全的航线:
题目大意:有若干个简单多边形(不相交)和一条折线。求这线上在所有多边形外,且距离任意一个多边形的距离的最小值最大的点到它最近的多边形的距离。
这题差点恶心死我。莫涛神犇在论文里提到了一种高效的好写的迭代算法(其实就是队列搜索加剪枝?),有兴趣可以找那篇论文。在NOI吧的资源里面找到2010国家集训队作业里面有。由于是刚学计算几何所以决定用一下传统的做法。
由于是最小值最大,所以首先二分答案d。然后将每个多边形向外扩张d单位长度(边处拓展成矩形,顶点处拓展出扇形,其实直接当圆做就可以),暴力判断当前的区域是否可以覆盖整个折线。
由于常数大了点,险些就TLE了
1 //date 20140625 2 #include <cstdio> 3 #include <cstring> 4 #include <algorithm> 5 #include <cmath> 6 #include <vector> 7 8 using namespace std; 9 const double EPS = 1e-9; 10 const double INF = 999999999.0; 11 const int maxn = 30; 12 13 struct points 14 { 15 double x, y; 16 points(){} 17 points(double a, double b){x = a; y = b;} 18 void readin(){scanf("%lf%lf", &x, &y);} 19 double len2(){return x * x + y * y;} 20 double len(){return sqrt(len2());} 21 void print(){printf("%.4lf %.4lf\n", x, y);} 22 }; 23 24 inline int sgn(double x){if(x < -EPS) return -1; if(x < EPS) return 0; return 1;} 25 inline points operator+ (points A, points B){return points(A.x + B.x, A.y + B.y);} 26 inline points operator- (points A, points B){return points(A.x - B.x, A.y - B.y);} 27 inline points operator* (double x, points A){return points(x * A.x, x * A.y);} 28 inline double operator% (points A, points B){return A.x * B.x + A.y * B.y;} 29 inline double operator* (points A, points B){return A.x * B.y - A.y * B.x;} 30 inline int operator< (points A, points B){return fabs(A.x - B.x) > EPS ? B.x - A.x > EPS : B.y - A.y > EPS;} 31 inline points setlen(double k, points A){return (k / A.len()) * A;} 32 inline points rot90 (points A){return points(-A.y, A.x);} 33 34 struct seg 35 { 36 points P, Q; 37 seg(){} 38 seg(points A, points B){P = A; Q = B;} 39 double len2(){return (P.x - Q.x) * (P.x - Q.x) + (P.y - Q.y) * (P.y - Q.y);} 40 double len(){return sqrt(len2());} 41 void std(){if(Q < P) swap(Q, P);} 42 }flight[maxn]; 43 44 inline int getjd(seg l1, seg l2, points &p) 45 { 46 double s1 = (l1.P - l2.P) * (l1.Q - l2.P); 47 double s2 = (l1.P - l2.Q) * (l1.Q - l2.Q); 48 if(s1 * s2 > -EPS) return 0; 49 if((((l2.P - l1.P) * (l2.Q - l1.P)) * ((l2.P - l1.Q) * (l2.Q - l1.Q))) > -EPS) return 0; 50 p = l2.P + (s1 / (s1 - s2)) * (l2.Q - l2.P); 51 return 1; 52 } 53 54 struct polygon 55 { 56 vector<seg> s; int n; 57 }land[maxn]; 58 59 inline int inside(polygon A, points P) 60 { 61 int numjd = 0; points tmp; 62 for(int i = 1; i <= A.n; ++i) 63 { 64 A.s[i].std(); 65 if(sgn((A.s[i].Q - P) * (A.s[i].P - P)) == 1 && sgn(A.s[i].P.x - P.x) <= 0 && sgn(A.s[i].Q.x - P.x) > 0) 66 numjd++; 67 } 68 return numjd & 1; 69 } 70 71 struct circle 72 { 73 points O; double r; 74 circle(){} 75 circle(points C, double band){O = C; r = band;} 76 }; 77 78 inline int get_line_jd(circle C, seg l, points &A, points &B) 79 { 80 points v = ((C.O - l.P) % (l.Q - l.P) / (l.Q - l.P).len2()) * (l.Q - l.P); 81 v = v + l.P; 82 double bxc = C.r * C.r - (C.O - v).len2(); 83 if(bxc < -EPS) return 0; 84 if(bxc < EPS) {A = v; return 1;} 85 A = v - setlen(sqrt(bxc), l.Q - l.P); B = v + setlen(sqrt(bxc), l.Q - l.P); return 2; 86 } 87 88 inline int getjd(circle C, seg l, points &A, points &B) 89 { 90 if(get_line_jd(C, l, A, B) < 2) return 0; 91 l.std(); 92 if(sgn(A.x - l.P.x) * sgn(A.x - l.Q.x) == 1) return 0; 93 if(sgn(B.x - l.P.x) * sgn(B.x - l.Q.x) == 1) return 0; 94 return 1; 95 } 96 97 int n, m; 98 vector<pair<points, int> > check; 99 vector<points> db; 100 101 inline void work1(polygon A, seg l) 102 { 103 db.clear(); l.std(); points tmp; 104 for(int i = 1; i <= A.n; ++i) A.s[i].std(); 105 db.push_back(l.P); db.push_back(l.Q); 106 for(int i = 1; i <= A.n; ++i) if(getjd(A.s[i], l, tmp)) db.push_back(tmp); 107 sort(db.begin(), db.end()); 108 for(int i = 0; i + 1 < db.size(); ++i) 109 { 110 if(db[i] < db[i + 1]) 111 { 112 tmp = 0.5 * (db[i] + db[i + 1]); 113 if(inside(A, tmp)) 114 { 115 check.push_back(make_pair(db[i], 1)); 116 check.push_back(make_pair(db[i + 1], -1)); 117 // printf("\t\t%.4lf %.4lf %.4lf %.4lf\n", db[i].x, db[i].y, db[i + 1].x, db[i + 1].y); 118 } 119 } 120 } 121 } 122 123 inline void work2(circle C, seg l) 124 { 125 points A, B; 126 if(get_line_jd(C, l, A, B) != 2) return; 127 if(B < A) swap(A, B); 128 l.std(); 129 if(A < l.P) A = l.P; 130 if(l.Q < B) B = l.Q; 131 if(A < B) 132 { 133 check.push_back(make_pair(A, 1)); check.push_back(make_pair(B, -1)); 134 /* printf("\n===============\n"); 135 A.print(); B.print(); 136 printf("===============\n\n");*/ 137 } 138 } 139 140 inline int check_it(double mid) 141 { 142 polygon tmp; 143 for(int i = 1; i <= n; ++i) 144 { 145 check.clear(); 146 check.push_back(make_pair(flight[i].P, 0)); check.push_back(make_pair(flight[i].Q, 0)); 147 for(int j = 1; j <= m; ++j) 148 { 149 work1(land[j], flight[i]); 150 for(int k = 1; k <= land[j].n; ++k) 151 { 152 tmp.s.clear(); tmp.s.resize(5); 153 tmp.n = 4; 154 points v = setlen(mid, rot90(land[j].s[k].Q - land[j].s[k].P)); 155 tmp.s[1] = seg(land[j].s[k].Q + v, land[j].s[k].P + v); 156 tmp.s[3] = seg(land[j].s[k].P - v, land[j].s[k].Q - v); 157 tmp.s[2] = seg(tmp.s[1].Q, tmp.s[3].P); 158 tmp.s[4] = seg(tmp.s[3].Q, tmp.s[1].P); 159 work1(tmp, flight[i]); 160 } 161 for(int k = 1; k <= land[j].n; ++k) 162 work2(circle(land[j].s[k].P, mid), flight[i]); 163 } 164 sort(check.begin(), check.end()); 165 int sum = 0; 166 for(int j = 0; j + 1 < check.size(); ++j) 167 { 168 sum += check[j].second; 169 if(check[j].first < check[j + 1].first && sum <= 0) 170 { 171 return 0; 172 } 173 } 174 // for(int w = 0; w < check.size(); ++w) printf("%.4lf %.4lf %d\n", check[w].first.x, check[w].first.y, check[w].second); 175 // printf("\n\n\n"); 176 } 177 return 1; 178 } 179 180 int main() 181 { 182 scanf("%d%d", &m, &n); 183 for(int i = 1; i < n; ++i) flight[i].P.readin(); --n; flight[n].Q.readin(); 184 for(int i = 1; i < n; ++i) flight[i].Q = flight[i + 1].P; 185 186 for(int i = 1; i <= m; ++i) 187 { 188 scanf("%d", &land[i].n); 189 land[i].s.resize(land[i].n + 1); 190 for(int j = 1; j <= land[i].n; ++j) 191 land[i].s[j].P.readin(); 192 for(int j = 1; j < land[i].n; ++j) land[i].s[j].Q = land[i].s[j + 1].P; 193 land[i].s[land[i].n].Q = land[i].s[1].P; 194 } 195 196 // check_it(3); 197 198 double Llim = 0, Rlim = INF, mid; 199 while(Rlim - Llim > EPS) 200 { 201 mid = (Llim + Rlim) * 0.5; 202 if(check_it(mid)) Rlim = mid; 203 else Llim = mid; 204 } 205 206 printf("%.2lf\n", Llim); 207 return 0; 208 }
BZOJ1021 循环的债务:
题目大意:A欠B钱,B欠C钱,C欠A钱。给出每个人手中拥有的100、50、20、10、5、1元纸币数量,然后问最多交换多少张纸币可以请算债务
首先无论怎样都可以归结为两个人欠一个人钱或者一个人欠两个人钱。从小到大处理每一种纸币。设dp[i][sa][sb]表示处理到第i种纸币,a共有sa元,b有sb元的少交换次数。由纸币的面值决定了,使用这种纸币时必须保证当前的欠款整除它和后面所有面值的gcd(因为小于gcd的必须在这种面值之前被搞定)然后转移方程有点复杂……我分了6种情况讨论的(两个人欠一个人钱或者一个人欠两个人钱)
1 //date 20140625 2 #include <cstdio> 3 #include <cstring> 4 5 const int maxn = 1050; 6 const int maxm = 7; 7 const int INF = 0x7F7F7F7F; 8 const int money[] = {1, 5, 10, 20, 50, 100}; 9 const int gcd[] = {1, 5, 10, 10, 50, 100}; 10 11 inline int denew(int &a, int b){if(a > b){a = b; return 1;} return 0;} 12 int x1, x2, x3, sa, sb, sc, ea, eb, ec, sum; 13 int a[maxm], b[maxm], c[maxm]; 14 int dp[2][maxn][maxn]; 15 16 int main() 17 { 18 scanf("%d%d%d", &x1, &x2, &x3); 19 ea -= x1; eb += x1; eb -= x2; ec += x2; ec -= x3; ea += x3; 20 for(int i = 5; i >= 0; --i){scanf("%d", &a[i]); sa += a[i] * money[i]; ea += a[i] * money[i];} 21 for(int i = 5; i >= 0; --i){scanf("%d", &b[i]); sb += b[i] * money[i]; eb += b[i] * money[i];} 22 for(int i = 5; i >= 0; --i){scanf("%d", &c[i]); sc += c[i] * money[i]; ec += c[i] * money[i];} 23 24 sum = sa + sb + sc; 25 if(ea < 0 || eb < 0 || ec < 0){printf("impossible\n"); return 0;} 26 27 int now = 0; 28 memset(dp, 0x7F, sizeof dp); 29 dp[now][sa][sb] = 0; 30 for(int i = 0; i < 6; ++i) 31 { 32 for(int j = 0; j < maxn; ++j) for(int k = 0; k < maxn; ++k) dp[now ^ 1][j][k] = dp[now][j][k]; 33 int x = 0, y = 0; 34 while((ea - x) % gcd[i]) ++x; 35 while((eb - y) % gcd[i]) ++y; 36 if((ec - (sum - x - y)) % gcd[i]) continue; 37 for(int j = x; j < maxn; j += gcd[i]) 38 { 39 for(int k = y; k < maxn; k += gcd[i]) 40 { 41 int vc = sum - j - k; 42 if(vc < 0) break; 43 if(dp[now][j][k] == INF) continue; 44 for(int r = 0; r <= a[i]; ++r) if(j >= money[i] * r) 45 for(int l = 0; l <= r; ++l) 46 if(k + money[i] * l < maxn && vc + money[i] * (r - l) < maxn) 47 denew(dp[now ^ 1][j - money[i] * r][k + money[i] * l], dp[now][j][k] + r); 48 49 for(int r = 0; r <= b[i]; ++r) if(k >= money[i] * r) 50 for(int l = 0; l <= r; ++l) 51 if(j + money[i] * l < maxn && vc + money[i] * (r - l) < maxn) 52 denew(dp[now ^ 1][j + money[i] * l][k - money[i] * r], dp[now][j][k] + r); 53 54 for(int r = 0; r <= c[i]; ++r) if(vc >= money[i] * r) 55 for(int l = 0; l <= r; ++l) 56 if(j + money[i] * l < maxn && k + money[i] * (r - l) < maxn) 57 denew(dp[now ^ 1][j + money[i] * l][k + money[i] * (r - l)], dp[now][j][k] + r); 58 59 for(int r = 0; r <= a[i]; ++r) if(j >= money[i] * r) 60 for(int l = 0; l <= b[i]; ++l) 61 if(k >= money[i] * l && vc + money[i] * (r + l) < maxn) 62 denew(dp[now ^ 1][j - money[i] * r][k - money[i] * l], dp[now][j][k] + l + r); 63 64 for(int r = 0; r <= a[i]; ++r) if(j >= money[i] * r) 65 for(int l = 0; l <= c[i]; ++l) 66 if(vc >= money[i] * l && k + money[i] * (l + r) < maxn) 67 denew(dp[now ^ 1][j - money[i] * r][k + money[i] * (l + r)], dp[now][j][k] + l + r); 68 69 for(int r = 0; r <= b[i]; ++r) if(k >= money[i] * r) 70 for(int l = 0; l <= c[i]; ++l) 71 if(vc >= money[i] * l && j + money[i] * (l + r) < maxn) 72 denew(dp[now ^ 1][j + money[i] * (l + r)][k - money[i] * r], dp[now][j][k] + l + r); 73 } 74 } 75 now ^= 1; 76 77 } 78 79 if(dp[now][ea][eb] == INF) printf("impossible\n"); 80 else printf("%d\n", dp[now][ea][eb]); 81 return 0; 82 }
BZOJ1022 小约翰的游戏:
题目大意:反Nim游戏判断输赢
结论题:如果所有石子数异或和为1且没有某堆石子数量>1,或者某堆石子数量>1但是总数量异或和为0时,后手胜,否则先手必胜
数学归纳法就能证明了
1 //date 20140626 2 #include <cstdio> 3 #include <cstring> 4 5 const int maxn = 60; 6 7 int t, n; 8 int st[maxn]; 9 10 int main() 11 { 12 scanf("%d", &t); 13 for(int tc = 1; tc <= t; ++tc) 14 { 15 scanf("%d", &n); 16 int x, type = 0, sum = 0; 17 for(int i = 1; i <= n; ++i) {scanf("%d", &x); if(x > 1) type = 1; sum ^= x;} 18 sum = sum > 0; 19 if(type ^ sum) printf("Brother\n"); else printf("John\n"); 20 } 21 22 return 0; 23 }
BZOJ1023 仙人掌图:
题目大意:求给定仙人掌的直径
标准的做法应该是DFS出一棵生成树然后DP。但是为了处理更一般的仙人掌问题我采用了点双缩点然后树+环DP的方法。
首先缩点之后会得到一棵生成树,先计算出每个点向下走的最大距离。然后计算出每个点向上走的最大距离,用一个单调队列就可以了(时间仓促没说清楚,过两天另开个文章说这题好了)
1 //date 20140627 2 #include <cstdio> 3 #include <cstring> 4 #include <vector> 5 6 using namespace std; 7 const int maxn = 105000; 8 const int maxm = maxn << 2; 9 10 inline int getint() 11 { 12 int ans(0); char w = getchar(); 13 while(w < '0' || '9' < w) w = getchar(); 14 while('0' <= w && w <= '9') {ans = ans * 10 + w - '0'; w = getchar();} 15 return ans; 16 } 17 18 inline int min(int a, int b){return a < b ? a : b;} 19 inline int denew(int &a, int b){if(a > b){a = b; return 1;} return 0;} 20 inline int innew(int &a, int b){if(a < b){a = b; return 1;} return 0;} 21 22 int n, m; 23 struct edge {int v, next;}E1[maxm], E2[maxm]; 24 int a1[maxn], a2[maxn], nedge1, nedge2; 25 inline void add1(int u, int v){E1[++nedge1].v = v; E1[nedge1].next = a1[u]; a1[u] = nedge1;} 26 inline void add2(int u, int v){E2[++nedge2].v = v; E2[nedge2].next = a2[u]; a2[u] = nedge2;} 27 int dfn[maxn], low[maxn], ncol, timer, dpt[maxn], p[maxn], q[maxn], ind[maxn]; 28 int dp1[maxn], dp2[maxn], dq[maxn], maxval[maxn], cmaxval[maxn]; 29 vector<int> cyc[maxn], tmp; 30 31 inline void tarjan(int v0) 32 { 33 static int d[maxn], now[maxn], stack[maxn], ins[maxn], stop, last, i, j; 34 memcpy(now, a1, sizeof a1); 35 for(dfn[v0] = low[v0] = timer += ins[d[last = 1] = stack[stop = 1] = v0] = 1; last; ) 36 { 37 if(!(j = now[i = d[last]])) 38 { 39 if(--last) 40 { 41 if(dfn[d[last]] == low[i]) 42 { 43 for(++ncol; ins[i]; ins[stack[stop--]] = 0) 44 { 45 add2(n + ncol, stack[stop]); add2(stack[stop], n + ncol); 46 } 47 add2(n + ncol, d[last]); add2(d[last], n + ncol); 48 } 49 denew(low[d[last]], low[i]); 50 } 51 continue; 52 } 53 if(!dfn[E1[j].v]) 54 { 55 ins[d[++last] = stack[++stop] = E1[j].v] = 1; low[E1[j].v] = dfn[E1[j].v] = ++timer; 56 }else denew(low[i], dfn[E1[j].v]); 57 now[i] = E1[j].next; 58 } 59 } 60 61 inline void DFS1(int v0) 62 { 63 static int d[maxn], now[maxn], last, i, j; 64 memcpy(now, a2, sizeof a2); 65 for(dpt[d[last = 1] = v0] = 1; last; ) 66 { 67 if(!(j = now[i = d[last]])){--last; continue;} 68 if(!dpt[E2[j].v]) 69 { 70 dpt[E2[j].v] = dpt[p[d[++last] = E2[j].v] = i] + 1; 71 if(E2[j].v > n) 72 { 73 for(int w = a2[E2[j].v], sgn = 0; w; w = E2[w].next) 74 { 75 if(E2[w].v == i) sgn = 1; 76 if(sgn) cyc[E2[j].v].push_back(E2[w].v); 77 } 78 for(int w = a2[E2[j].v]; w; w = E2[w].next) 79 { 80 if(E2[w].v == i) break; 81 cyc[E2[j].v].push_back(E2[w].v); 82 } 83 } 84 ++ind[i]; 85 } 86 now[i] = E2[j].next; 87 } 88 } 89 90 inline void BFS1() 91 { 92 int st = 0, ed = 0; 93 for(int i = 1; i <= n + ncol; ++i) if(!ind[i]) q[++ed] = i; 94 while(st < ed) 95 { 96 int x = q[++st]; 97 if(!(--ind[p[x]])) q[++ed] = p[x]; 98 } 99 } 100 101 inline void dynamic_programming1() 102 { 103 for(int t = 1, i = q[1]; t <= n + ncol; i = q[++t]) if(i > n) 104 { 105 for(int j = 1; j < cyc[i].size(); ++j) innew(dp1[i], dp1[cyc[i][j]] + min(j, cyc[i].size() - j)); 106 innew(dp1[p[i]], dp1[i]); 107 } 108 } 109 110 inline void dynamic_programming2() 111 { 112 dp2[1] = 0; 113 int st, ed; 114 for(int t = n + ncol, i = q[n + ncol]; t; i = q[--t]) 115 { 116 if(i > n) 117 { 118 if(dp1[i] == maxval[p[i]]) innew(dp2[i], cmaxval[p[i]]); 119 else innew(dp2[i], maxval[p[i]]); 120 121 tmp.clear(); 122 int tsz = cyc[i].size(), htsz = tsz >> 1; 123 for(int w = 1; w <= 3; ++w) 124 { 125 tmp.push_back(dp2[i] - (w - 1) * tsz); 126 for(int j = 1; j < tsz; ++j) tmp.push_back(dp1[cyc[i][j]] - j - (w - 1) * tsz); 127 } 128 st = ed = 0; 129 for(int j = tsz - htsz + 1; j <= tsz; ++j) 130 { 131 while(st < ed && tmp[dq[ed]] < tmp[j]) --ed; 132 dq[++ed] = j; 133 } 134 for(int j = tsz + 1; j < (tsz << 1); ++j) 135 { 136 while(st < ed && dq[st + 1] < j - htsz) ++st; 137 if(st < ed) innew(dp2[cyc[i][j - tsz]], j + tmp[dq[st + 1]]); 138 while(st < ed && tmp[dq[ed]] < tmp[j]) --ed; 139 dq[++ed] = j; 140 } 141 142 tmp.clear(); 143 for(int w = 1; w <= 3; ++w) 144 { 145 tmp.push_back(dp2[i] + (w - 1) * tsz); 146 for(int j = 1; j < tsz; ++j) tmp.push_back(dp1[cyc[i][j]] + j + (w - 1) * tsz); 147 } 148 st = ed = 0; 149 for(int j = (tsz << 1) + htsz - 1; j >= (tsz << 1); --j) 150 { 151 while(st < ed && tmp[dq[ed]] < tmp[j]) --ed; 152 dq[++ed] = j; 153 } 154 for(int j = (tsz << 1) - 1; j > tsz; --j) 155 { 156 while(st < ed && dq[st + 1] > j + htsz) ++st; 157 if(st < ed) innew(dp2[cyc[i][j - tsz]], -j + tmp[dq[st + 1]]); 158 while(st < ed && tmp[dq[ed]] < tmp[j]) --ed; 159 dq[++ed] = j; 160 } 161 }else{ 162 maxval[i] = dp2[i]; 163 for(int j = a2[i]; j; j = E2[j].next) if(E2[j].v != p[i]) 164 { 165 if(dp1[E2[j].v] > maxval[i]) 166 { 167 cmaxval[i] = maxval[i]; 168 maxval[i] = dp1[E2[j].v]; 169 }else innew(cmaxval[i], dp1[E2[j].v]); 170 } 171 } 172 } 173 } 174 175 int main() 176 { 177 n = getint(); m = getint(); 178 for(int i = 1; i <= m; ++i) 179 { 180 int x = getint(), last = getint(), now; 181 for(int i = 1; i < x; ++i) 182 { 183 now = getint(); 184 add1(now, last); add1(last, now); 185 last = now; 186 } 187 } 188 189 tarjan(1); DFS1(1); BFS1(); 190 dynamic_programming1(); 191 dynamic_programming2(); 192 193 int ans = 0; 194 for(int i = 1; i <= ncol; ++i) innew(ans, dp2[i + n] + dp1[i + n]); 195 printf("%d\n", ans); 196 return 0; 197 }