感觉还是上海人出题水平高?这套题写得心旷神怡的。。。总之很难就是啦
由于我实在不适应博客园这种排版和字体。。所以我的文章可能会特别难看大家见谅。。说不定回头开发一个支持全局LaTeX的博客也不错?2333
BZOJ1018 堵塞的交通:
题目大意:有一个2*n的矩阵,初始时没有边,每次可能会打通两个相邻的节点(相邻指曼哈顿距离为1)之间的无向道路或是拆毁两个相邻节点的道路,每次询问两个节点是否连通。
神奇的做法:使用动态树维护整个图的连通性!(这真的可写么?)
正确的做法:由于只有两排,使用一个线段树来维护连通性就可以了。。思路就是线段树的每个叶子节点维护四个节点的C字形的连通性,每个非叶子节点维护这一段的四个顶点的C字型(每个线段树节点其实是一个并查集) 虽然连接方式有很多种情况,但毕竟是有限的几种,手工特判一下就好了。。细节挺多的。特别要注意从两边联通这种情况。随便写一写就好了。
//date 20140624
#include <cstdio>
#include <cstring> const int maxn = ; inline int getint()
{
int ans(); char w = getchar();
while(w < '' || '' < w) w = getchar();
while('' <= w && w <= ''){ans = ans * + w - ''; w = getchar();}
return ans;
} template <typename T> inline void swap(T &a, T &b){T x = a; a = b; b = x;} /*
0---------2
| |
| |
| |
1---------3
*/
int tmp[];
struct info
{
int num[];
info(){}
info(int a, int b, int c, int d){num[] = a; num[] = b; num[] = c; num[] = d;}
void std()
{
memset(tmp, , sizeof tmp); int count = ;
for(int i = ; i < ; ++i) if(!tmp[num[i]]) tmp[num[i]] = ++count;
for(int i = ; i < ; ++i) num[i] = tmp[num[i]];
}
}; struct DSU
{
int p[];
void preset(){ for(int i = ; i <= ; ++i) p[i] = i;}
DSU(){preset();}
int getp(int x){return p[x] == x ? x : (p[x] = getp(p[x]));}
void merge(int x, int y){if((x = getp(x)) != (y = getp(y))) p[x] = y;}
}; inline info operator+ (info A, info B)
{
DSU MEOW;
for(int i = ; i < ; ++i) for(int j = i + ; j < ; ++j)
if(A.num[i] == A.num[j]) MEOW.merge(i + , j + );
for(int i = ; i < ; ++i) for(int j = i + ; j < ; ++j)
if(B.num[i] == B.num[j]) MEOW.merge(i + , j + );
info ans(MEOW.getp(), MEOW.getp(), MEOW.getp(), MEOW.getp()); ans.std();
return ans;
} int n;
info wius[];
int now[maxn]; inline void preset()
{
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
wius[] = info(, , , );
} struct zkw_sget
{
int base; info data[];
void preset()
{
for(base = ; base < n + ; base <<= );
for(int i = ; i < (base << ); ++i) data[i] = wius[];
} void change(int pos, int val)
{
data[pos + base] = wius[now[pos] = val];
for(int i = (pos + base) >> ; i; i >>= ) data[i] = data[i << ] + data[(i << ) | ];
} info mini_query(int l, int r)
{
info ansl(wius[]), ansr(wius[]);
for(l = l + base - , r = r + base + ; l < r - ; l >>= , r >>= )
{
if(!(l & )) ansl = ansl + data[l ^ ];
if( (r & )) ansr = data[r ^ ] + ansr;
}
return ansl + ansr;
} info query(int l, int r)
{
info ansl, ansm, ansr;
ansl = mini_query(, l - );
ansm = mini_query(l, r - );
ansr = mini_query(r, n);
if(ansl.num[] == ansl.num[]) ansm = wius[] + ansm;
if(ansr.num[] == ansr.num[]) ansm = ansm + wius[];
return ansm;
}
}MEOW; char order[]; int main()
{
n = getint(); preset();
MEOW.preset();
while(true)
{
scanf("%s", order);
int x1, y1, x2, y2;
switch(order[])
{
case 'E': return ; break;
case 'O': case 'C':
x1 = getint(); y1 = getint(); x2 = getint(); y2 = getint();
if(y1 > y2){swap(x1, x2); swap(y1, y2);}
if(y1 == y2)
{
MEOW.change(y1, now[y1] ^ );
}else {
if(x1 == ) MEOW.change(y1, now[y1] ^ );
else MEOW.change(y1, now[y1] ^ );
}
break; case 'A':
x1 = getint(); y1 = getint(); x2 = getint(); y2 = getint();
if(y1 > y2){swap(x1, x2); swap(y1, y2);}
info tmp = MEOW.query(y1, y2);
printf(tmp.num[x1 - ] == tmp.num[x2 + ] ? "Y\n" : "N\n");
break;
}
}
return ;
}
traffic
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个盘子挪走需要的最小次数……这就很简单了
//date 20140624
#include <cstdio>
#include <cstring> typedef long long ll;
const int maxn = ; ll n;
ll f[maxn][maxn], dp[maxn][maxn];
char order[maxn][maxn]; int main()
{
scanf("%lld", &n);
for(int i = ; i <= ; ++i)
{
scanf("%s", order[i] + );
if(!f[order[i][] - 'A' + ][]) f[order[i][] - 'A' + ][] = order[i][] - 'A' + ;
}
for(int i = ; i <= n; ++i)
for(int x = ; x <= ; ++x)
{
if(f[f[x][i - ]][i - ] == x) f[x][i] = f[x][i - ];
else f[x][i] = - x - f[x][i - ];
} dp[][] = dp[][] = dp[][] = ;
for(int i = ; i <= n; ++i)
for(int x = ; x <= ; ++x)
{
if(f[x][i - ] == f[x][i])
{
dp[x][i] = + dp[x][i - ] + dp[f[x][i]][i - ] + + dp[x][i - ];
}
else dp[x][i] = dp[ - x - f[x][i]][i - ] + dp[x][i - ] + ;
}
printf("%lld\n", dp[][n]);
return ;
}
hanoi
BZOJ1020 安全的航线:
题目大意:有若干个简单多边形(不相交)和一条折线。求这线上在所有多边形外,且距离任意一个多边形的距离的最小值最大的点到它最近的多边形的距离。
这题差点恶心死我。莫涛神犇在论文里提到了一种高效的好写的迭代算法(其实就是队列搜索加剪枝?),有兴趣可以找那篇论文。在NOI吧的资源里面找到2010国家集训队作业里面有。由于是刚学计算几何所以决定用一下传统的做法。
由于是最小值最大,所以首先二分答案d。然后将每个多边形向外扩张d单位长度(边处拓展成矩形,顶点处拓展出扇形,其实直接当圆做就可以),暴力判断当前的区域是否可以覆盖整个折线。
由于常数大了点,险些就TLE了
//date 20140625
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <vector> using namespace std;
const double EPS = 1e-;
const double INF = 999999999.0;
const int maxn = ; struct points
{
double x, y;
points(){}
points(double a, double b){x = a; y = b;}
void readin(){scanf("%lf%lf", &x, &y);}
double len2(){return x * x + y * y;}
double len(){return sqrt(len2());}
void print(){printf("%.4lf %.4lf\n", x, y);}
}; inline int sgn(double x){if(x < -EPS) return -; if(x < EPS) return ; return ;}
inline points operator+ (points A, points B){return points(A.x + B.x, A.y + B.y);}
inline points operator- (points A, points B){return points(A.x - B.x, A.y - B.y);}
inline points operator* (double x, points A){return points(x * A.x, x * A.y);}
inline double operator% (points A, points B){return A.x * B.x + A.y * B.y;}
inline double operator* (points A, points B){return A.x * B.y - A.y * B.x;}
inline int operator< (points A, points B){return fabs(A.x - B.x) > EPS ? B.x - A.x > EPS : B.y - A.y > EPS;}
inline points setlen(double k, points A){return (k / A.len()) * A;}
inline points rot90 (points A){return points(-A.y, A.x);} struct seg
{
points P, Q;
seg(){}
seg(points A, points B){P = A; Q = B;}
double len2(){return (P.x - Q.x) * (P.x - Q.x) + (P.y - Q.y) * (P.y - Q.y);}
double len(){return sqrt(len2());}
void std(){if(Q < P) swap(Q, P);}
}flight[maxn]; inline int getjd(seg l1, seg l2, points &p)
{
double s1 = (l1.P - l2.P) * (l1.Q - l2.P);
double s2 = (l1.P - l2.Q) * (l1.Q - l2.Q);
if(s1 * s2 > -EPS) return ;
if((((l2.P - l1.P) * (l2.Q - l1.P)) * ((l2.P - l1.Q) * (l2.Q - l1.Q))) > -EPS) return ;
p = l2.P + (s1 / (s1 - s2)) * (l2.Q - l2.P);
return ;
} struct polygon
{
vector<seg> s; int n;
}land[maxn]; inline int inside(polygon A, points P)
{
int numjd = ; points tmp;
for(int i = ; i <= A.n; ++i)
{
A.s[i].std();
if(sgn((A.s[i].Q - P) * (A.s[i].P - P)) == && sgn(A.s[i].P.x - P.x) <= && sgn(A.s[i].Q.x - P.x) > )
numjd++;
}
return numjd & ;
} struct circle
{
points O; double r;
circle(){}
circle(points C, double band){O = C; r = band;}
}; inline int get_line_jd(circle C, seg l, points &A, points &B)
{
points v = ((C.O - l.P) % (l.Q - l.P) / (l.Q - l.P).len2()) * (l.Q - l.P);
v = v + l.P;
double bxc = C.r * C.r - (C.O - v).len2();
if(bxc < -EPS) return ;
if(bxc < EPS) {A = v; return ;}
A = v - setlen(sqrt(bxc), l.Q - l.P); B = v + setlen(sqrt(bxc), l.Q - l.P); return ;
} inline int getjd(circle C, seg l, points &A, points &B)
{
if(get_line_jd(C, l, A, B) < ) return ;
l.std();
if(sgn(A.x - l.P.x) * sgn(A.x - l.Q.x) == ) return ;
if(sgn(B.x - l.P.x) * sgn(B.x - l.Q.x) == ) return ;
return ;
} int n, m;
vector<pair<points, int> > check;
vector<points> db; inline void work1(polygon A, seg l)
{
db.clear(); l.std(); points tmp;
for(int i = ; i <= A.n; ++i) A.s[i].std();
db.push_back(l.P); db.push_back(l.Q);
for(int i = ; i <= A.n; ++i) if(getjd(A.s[i], l, tmp)) db.push_back(tmp);
sort(db.begin(), db.end());
for(int i = ; i + < db.size(); ++i)
{
if(db[i] < db[i + ])
{
tmp = 0.5 * (db[i] + db[i + ]);
if(inside(A, tmp))
{
check.push_back(make_pair(db[i], ));
check.push_back(make_pair(db[i + ], -));
// printf("\t\t%.4lf %.4lf %.4lf %.4lf\n", db[i].x, db[i].y, db[i + 1].x, db[i + 1].y);
}
}
}
} inline void work2(circle C, seg l)
{
points A, B;
if(get_line_jd(C, l, A, B) != ) return;
if(B < A) swap(A, B);
l.std();
if(A < l.P) A = l.P;
if(l.Q < B) B = l.Q;
if(A < B)
{
check.push_back(make_pair(A, )); check.push_back(make_pair(B, -));
/* printf("\n===============\n");
A.print(); B.print();
printf("===============\n\n");*/
}
} inline int check_it(double mid)
{
polygon tmp;
for(int i = ; i <= n; ++i)
{
check.clear();
check.push_back(make_pair(flight[i].P, )); check.push_back(make_pair(flight[i].Q, ));
for(int j = ; j <= m; ++j)
{
work1(land[j], flight[i]);
for(int k = ; k <= land[j].n; ++k)
{
tmp.s.clear(); tmp.s.resize();
tmp.n = ;
points v = setlen(mid, rot90(land[j].s[k].Q - land[j].s[k].P));
tmp.s[] = seg(land[j].s[k].Q + v, land[j].s[k].P + v);
tmp.s[] = seg(land[j].s[k].P - v, land[j].s[k].Q - v);
tmp.s[] = seg(tmp.s[].Q, tmp.s[].P);
tmp.s[] = seg(tmp.s[].Q, tmp.s[].P);
work1(tmp, flight[i]);
}
for(int k = ; k <= land[j].n; ++k)
work2(circle(land[j].s[k].P, mid), flight[i]);
}
sort(check.begin(), check.end());
int sum = ;
for(int j = ; j + < check.size(); ++j)
{
sum += check[j].second;
if(check[j].first < check[j + ].first && sum <= )
{
return ;
}
}
// for(int w = 0; w < check.size(); ++w) printf("%.4lf %.4lf %d\n", check[w].first.x, check[w].first.y, check[w].second);
// printf("\n\n\n");
}
return ;
} int main()
{
scanf("%d%d", &m, &n);
for(int i = ; i < n; ++i) flight[i].P.readin(); --n; flight[n].Q.readin();
for(int i = ; i < n; ++i) flight[i].Q = flight[i + ].P; for(int i = ; i <= m; ++i)
{
scanf("%d", &land[i].n);
land[i].s.resize(land[i].n + );
for(int j = ; j <= land[i].n; ++j)
land[i].s[j].P.readin();
for(int j = ; j < land[i].n; ++j) land[i].s[j].Q = land[i].s[j + ].P;
land[i].s[land[i].n].Q = land[i].s[].P;
} // check_it(3); double Llim = , Rlim = INF, mid;
while(Rlim - Llim > EPS)
{
mid = (Llim + Rlim) * 0.5;
if(check_it(mid)) Rlim = mid;
else Llim = mid;
} printf("%.2lf\n", Llim);
return ;
}
flight
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种情况讨论的(两个人欠一个人钱或者一个人欠两个人钱)
//date 20140625
#include <cstdio>
#include <cstring> const int maxn = ;
const int maxm = ;
const int INF = 0x7F7F7F7F;
const int money[] = {, , , , , };
const int gcd[] = {, , , , , }; inline int denew(int &a, int b){if(a > b){a = b; return ;} return ;}
int x1, x2, x3, sa, sb, sc, ea, eb, ec, sum;
int a[maxm], b[maxm], c[maxm];
int dp[][maxn][maxn]; int main()
{
scanf("%d%d%d", &x1, &x2, &x3);
ea -= x1; eb += x1; eb -= x2; ec += x2; ec -= x3; ea += x3;
for(int i = ; i >= ; --i){scanf("%d", &a[i]); sa += a[i] * money[i]; ea += a[i] * money[i];}
for(int i = ; i >= ; --i){scanf("%d", &b[i]); sb += b[i] * money[i]; eb += b[i] * money[i];}
for(int i = ; i >= ; --i){scanf("%d", &c[i]); sc += c[i] * money[i]; ec += c[i] * money[i];} sum = sa + sb + sc;
if(ea < || eb < || ec < ){printf("impossible\n"); return ;} int now = ;
memset(dp, 0x7F, sizeof dp);
dp[now][sa][sb] = ;
for(int i = ; i < ; ++i)
{
for(int j = ; j < maxn; ++j) for(int k = ; k < maxn; ++k) dp[now ^ ][j][k] = dp[now][j][k];
int x = , y = ;
while((ea - x) % gcd[i]) ++x;
while((eb - y) % gcd[i]) ++y;
if((ec - (sum - x - y)) % gcd[i]) continue;
for(int j = x; j < maxn; j += gcd[i])
{
for(int k = y; k < maxn; k += gcd[i])
{
int vc = sum - j - k;
if(vc < ) break;
if(dp[now][j][k] == INF) continue;
for(int r = ; r <= a[i]; ++r) if(j >= money[i] * r)
for(int l = ; l <= r; ++l)
if(k + money[i] * l < maxn && vc + money[i] * (r - l) < maxn)
denew(dp[now ^ ][j - money[i] * r][k + money[i] * l], dp[now][j][k] + r); for(int r = ; r <= b[i]; ++r) if(k >= money[i] * r)
for(int l = ; l <= r; ++l)
if(j + money[i] * l < maxn && vc + money[i] * (r - l) < maxn)
denew(dp[now ^ ][j + money[i] * l][k - money[i] * r], dp[now][j][k] + r); for(int r = ; r <= c[i]; ++r) if(vc >= money[i] * r)
for(int l = ; l <= r; ++l)
if(j + money[i] * l < maxn && k + money[i] * (r - l) < maxn)
denew(dp[now ^ ][j + money[i] * l][k + money[i] * (r - l)], dp[now][j][k] + r); for(int r = ; r <= a[i]; ++r) if(j >= money[i] * r)
for(int l = ; l <= b[i]; ++l)
if(k >= money[i] * l && vc + money[i] * (r + l) < maxn)
denew(dp[now ^ ][j - money[i] * r][k - money[i] * l], dp[now][j][k] + l + r); for(int r = ; r <= a[i]; ++r) if(j >= money[i] * r)
for(int l = ; l <= c[i]; ++l)
if(vc >= money[i] * l && k + money[i] * (l + r) < maxn)
denew(dp[now ^ ][j - money[i] * r][k + money[i] * (l + r)], dp[now][j][k] + l + r); for(int r = ; r <= b[i]; ++r) if(k >= money[i] * r)
for(int l = ; l <= c[i]; ++l)
if(vc >= money[i] * l && j + money[i] * (l + r) < maxn)
denew(dp[now ^ ][j + money[i] * (l + r)][k - money[i] * r], dp[now][j][k] + l + r);
}
}
now ^= ; } if(dp[now][ea][eb] == INF) printf("impossible\n");
else printf("%d\n", dp[now][ea][eb]);
return ;
}
debt
BZOJ1022 小约翰的游戏:
题目大意:反Nim游戏判断输赢
结论题:如果所有石子数异或和为1且没有某堆石子数量>1,或者某堆石子数量>1但是总数量异或和为0时,后手胜,否则先手必胜
数学归纳法就能证明了
//date 20140626
#include <cstdio>
#include <cstring> const int maxn = ; int t, n;
int st[maxn]; int main()
{
scanf("%d", &t);
for(int tc = ; tc <= t; ++tc)
{
scanf("%d", &n);
int x, type = , sum = ;
for(int i = ; i <= n; ++i) {scanf("%d", &x); if(x > ) type = ; sum ^= x;}
sum = sum > ;
if(type ^ sum) printf("Brother\n"); else printf("John\n");
} return ;
}
John
BZOJ1023 仙人掌图:
题目大意:求给定仙人掌的直径
标准的做法应该是DFS出一棵生成树然后DP。但是为了处理更一般的仙人掌问题我采用了点双缩点然后树+环DP的方法。
首先缩点之后会得到一棵生成树,先计算出每个点向下走的最大距离。然后计算出每个点向上走的最大距离,用一个单调队列就可以了(时间仓促没说清楚,过两天另开个文章说这题好了)
//date 20140627
#include <cstdio>
#include <cstring>
#include <vector> using namespace std;
const int maxn = ;
const int maxm = maxn << ; inline int getint()
{
int ans(); char w = getchar();
while(w < '' || '' < w) w = getchar();
while('' <= w && w <= '') {ans = ans * + w - ''; w = getchar();}
return ans;
} inline int min(int a, int b){return a < b ? a : b;}
inline int denew(int &a, int b){if(a > b){a = b; return ;} return ;}
inline int innew(int &a, int b){if(a < b){a = b; return ;} return ;} int n, m;
struct edge {int v, next;}E1[maxm], E2[maxm];
int a1[maxn], a2[maxn], nedge1, nedge2;
inline void add1(int u, int v){E1[++nedge1].v = v; E1[nedge1].next = a1[u]; a1[u] = nedge1;}
inline void add2(int u, int v){E2[++nedge2].v = v; E2[nedge2].next = a2[u]; a2[u] = nedge2;}
int dfn[maxn], low[maxn], ncol, timer, dpt[maxn], p[maxn], q[maxn], ind[maxn];
int dp1[maxn], dp2[maxn], dq[maxn], maxval[maxn], cmaxval[maxn];
vector<int> cyc[maxn], tmp; inline void tarjan(int v0)
{
static int d[maxn], now[maxn], stack[maxn], ins[maxn], stop, last, i, j;
memcpy(now, a1, sizeof a1);
for(dfn[v0] = low[v0] = timer += ins[d[last = ] = stack[stop = ] = v0] = ; last; )
{
if(!(j = now[i = d[last]]))
{
if(--last)
{
if(dfn[d[last]] == low[i])
{
for(++ncol; ins[i]; ins[stack[stop--]] = )
{
add2(n + ncol, stack[stop]); add2(stack[stop], n + ncol);
}
add2(n + ncol, d[last]); add2(d[last], n + ncol);
}
denew(low[d[last]], low[i]);
}
continue;
}
if(!dfn[E1[j].v])
{
ins[d[++last] = stack[++stop] = E1[j].v] = ; low[E1[j].v] = dfn[E1[j].v] = ++timer;
}else denew(low[i], dfn[E1[j].v]);
now[i] = E1[j].next;
}
} inline void DFS1(int v0)
{
static int d[maxn], now[maxn], last, i, j;
memcpy(now, a2, sizeof a2);
for(dpt[d[last = ] = v0] = ; last; )
{
if(!(j = now[i = d[last]])){--last; continue;}
if(!dpt[E2[j].v])
{
dpt[E2[j].v] = dpt[p[d[++last] = E2[j].v] = i] + ;
if(E2[j].v > n)
{
for(int w = a2[E2[j].v], sgn = ; w; w = E2[w].next)
{
if(E2[w].v == i) sgn = ;
if(sgn) cyc[E2[j].v].push_back(E2[w].v);
}
for(int w = a2[E2[j].v]; w; w = E2[w].next)
{
if(E2[w].v == i) break;
cyc[E2[j].v].push_back(E2[w].v);
}
}
++ind[i];
}
now[i] = E2[j].next;
}
} inline void BFS1()
{
int st = , ed = ;
for(int i = ; i <= n + ncol; ++i) if(!ind[i]) q[++ed] = i;
while(st < ed)
{
int x = q[++st];
if(!(--ind[p[x]])) q[++ed] = p[x];
}
} inline void dynamic_programming1()
{
for(int t = , i = q[]; t <= n + ncol; i = q[++t]) if(i > n)
{
for(int j = ; j < cyc[i].size(); ++j) innew(dp1[i], dp1[cyc[i][j]] + min(j, cyc[i].size() - j));
innew(dp1[p[i]], dp1[i]);
}
} inline void dynamic_programming2()
{
dp2[] = ;
int st, ed;
for(int t = n + ncol, i = q[n + ncol]; t; i = q[--t])
{
if(i > n)
{
if(dp1[i] == maxval[p[i]]) innew(dp2[i], cmaxval[p[i]]);
else innew(dp2[i], maxval[p[i]]); tmp.clear();
int tsz = cyc[i].size(), htsz = tsz >> ;
for(int w = ; w <= ; ++w)
{
tmp.push_back(dp2[i] - (w - ) * tsz);
for(int j = ; j < tsz; ++j) tmp.push_back(dp1[cyc[i][j]] - j - (w - ) * tsz);
}
st = ed = ;
for(int j = tsz - htsz + ; j <= tsz; ++j)
{
while(st < ed && tmp[dq[ed]] < tmp[j]) --ed;
dq[++ed] = j;
}
for(int j = tsz + ; j < (tsz << ); ++j)
{
while(st < ed && dq[st + ] < j - htsz) ++st;
if(st < ed) innew(dp2[cyc[i][j - tsz]], j + tmp[dq[st + ]]);
while(st < ed && tmp[dq[ed]] < tmp[j]) --ed;
dq[++ed] = j;
} tmp.clear();
for(int w = ; w <= ; ++w)
{
tmp.push_back(dp2[i] + (w - ) * tsz);
for(int j = ; j < tsz; ++j) tmp.push_back(dp1[cyc[i][j]] + j + (w - ) * tsz);
}
st = ed = ;
for(int j = (tsz << ) + htsz - ; j >= (tsz << ); --j)
{
while(st < ed && tmp[dq[ed]] < tmp[j]) --ed;
dq[++ed] = j;
}
for(int j = (tsz << ) - ; j > tsz; --j)
{
while(st < ed && dq[st + ] > j + htsz) ++st;
if(st < ed) innew(dp2[cyc[i][j - tsz]], -j + tmp[dq[st + ]]);
while(st < ed && tmp[dq[ed]] < tmp[j]) --ed;
dq[++ed] = j;
}
}else{
maxval[i] = dp2[i];
for(int j = a2[i]; j; j = E2[j].next) if(E2[j].v != p[i])
{
if(dp1[E2[j].v] > maxval[i])
{
cmaxval[i] = maxval[i];
maxval[i] = dp1[E2[j].v];
}else innew(cmaxval[i], dp1[E2[j].v]);
}
}
}
} int main()
{
n = getint(); m = getint();
for(int i = ; i <= m; ++i)
{
int x = getint(), last = getint(), now;
for(int i = ; i < x; ++i)
{
now = getint();
add1(now, last); add1(last, now);
last = now;
}
} tarjan(); DFS1(); BFS1();
dynamic_programming1();
dynamic_programming2(); int ans = ;
for(int i = ; i <= ncol; ++i) innew(ans, dp2[i + n] + dp1[i + n]);
printf("%d\n", ans);
return ;
}
cactus