哈尔滨理工大学第七届程序设计竞赛决赛(网络赛-高年级组)F - 团结就是力量
题目描述
从小老师就教育我们,一根筷子容易折断,而一捆筷子不容易折断。
因为要出战世界杯,我们需要考虑派一只队伍出战,而我们希望出战的队伍的团结力最大。
而一个队伍的团结力取决于每个人的性格,每个人都有一个性格基因【(由字符串表示),比如小明的性格基因为:abbcde】,性格基因的排列方式是可以通过一个人的后天培养而改变的,其改变方式就是类似于循环,【小明的性格基因为:abbcde,他可以变成:bbcdea,bcdeab,cdeabb,deabbc,eabbcd】 。
一个队伍中如果最多有x个人的性格基因可以完全相等的话,那么这个队伍的团结力就是x。
比如一个队伍有五个人:
小明:abbcde
小红:bbcdea
大明:cdeabb
大红:efg
小紫:fge
明显小明小红和大明的性格基因可以变成相等的,大红和小紫的性格基因可以变成相等的, 这个最多有3个人的性格基因可以完全相等的,所以这个五人队伍的团结力就是3;
现在已知可以出战的人数为n个,每个人都有一个性格基因字符串,而作为一只队伍出战的话,需要队伍中的每个人都互相达成共识。同时也已知m个信息,每个信息是:
a想要和b一起出战【注意,这里只是a的一厢情愿】,只有当a想要和b一起出战,并且b也想要和a一起出战的时候,两个人才能一起出战。想要一起出战是可以具有传递性的,比如a想要和b一起出战,b想要和c一起出战的话,那么a也可以想要和c一起出战。
我们肯定希望派出的队伍的团结力最大,请计算出这个最大团结力。
输入描述:
本题包含多组数据,第一行输入两个数字n,m,分别表示一共有n个人,以及m个出战信息 。
接下来n行,每行输入一个字符串,表示每个人的性格基因。
再接下来m行,每行两个编号x,y,表示x想要和y出战
数据范围:
5<=n<=100000
1<=m<=100000
1<=x,y<=n
每个数据的字符串长度和不超过100000
输出描述:
每组数据输出一行,表示最大团结力。
示例1
输入
5 5 abbcde bbcdea cdeabb efg fge 1 2 2 3 3 4 4 5 5 1 6 7 abbcde bbcdea cdeabb efg fge gef 1 2 2 3 3 1 4 5 5 6 6 4 2 4
输出
3 3
说明
第一个样例题干中有所描述。这里1想和2出战,2想和3出战,3想和4出战,4想和5出战,5又想和1出战,那么就相当于每个人都想要互相一起出战,所以这就是一个队伍。
第二个样例中,123号三个人是一个队伍,456号是一个队伍,虽然2想和4一起出战,但是已知m条信息中,不能构成4想和2出战的信息出来,所以六个人不能变成一个队伍。
题解
强连通分量,字符串的最小表示法,字符串哈希。
根据题意,一个强连通分量内的人可以一起出征,接下来就是求每个强连通分量的最大团结力。
也就是计算每个强连通分量内最多几个人的字符串旋转后可以相同。
我们可以将每个字符串旋转成字典序最小的那个,然后$hash$成一个值,接下来就是看哪个数字最多就可以了。
#include <bits/stdc++.h> using namespace std; const long long mod = 1e9 + 7; const int maxn = 100000 + 10; int n, m; long long val[maxn]; char s[maxn]; int h[maxn]; int to[maxn]; int nx[maxn]; int sz; int ans; long long b[maxn]; int u; int getmin() { int len = strlen(s); int i=0,j=1,k=0,t; while(i < len && j < len && k < len) { t = s[(i + k) % len] - s[(j + k) % len]; if (!t) k ++; else { if (t > 0) i += k + 1; else j += k + 1; if (i == j) j ++; k = 0; } } return i < j ? i : j; } void add(int x, int y) { to[sz] = y; nx[sz] = h[x]; h[x] = sz; sz ++; } /*强连通分量*/ int Stop;//栈中的元素个数 int cnt;//记录连通分量的个数 int visitNum;//记录遍历的步数 int DFN[maxn]; //记录节点u第一次被访问时的步数 int LOW[maxn]; //记录与节点u和u的子树节点中最早的步数 bool instack[maxn];//记录节点u是否在栈中 int Stap[maxn];//栈 int Belong[maxn];//记录每个节点属于的强连通分量编号 void tarjan(int i) { int j; DFN[i] = LOW[i] = ++visitNum; instack[i] = true; Stap[++ Stop] = i;//将当前节点压入栈中 for (int id = h[i]; id != -1; id = nx[id]) { j = to[id]; if (!DFN[j]) { //j还没有被访问过 tarjan(j); //父节点是子节点的子节点 if (LOW[j] < LOW[i]) { LOW[i] = LOW[j]; } } //与j相连,但是j已经被访问过,且还在栈中 //用子树节点更新节点第一次出现的时间 else if (instack[j] && DFN[j] < LOW[i]) { LOW[i] = DFN[j]; } } //节点i是强连通分量的根 if (DFN[i] == LOW[i]) { cnt++; //输出找到的强连通分量 //cout<<"连通分量"<<cnt<<": "; //退栈,直至找到根为止 u = 0; do { j = Stap[Stop --]; instack[j] = false; //cout << j << " "; b[u ++] = val[j]; Belong[j] = cnt; } while (j != i); //cout << endl; sort(b, b + u); if(u) { int sum = 1; for(int v = 1; v < u; v ++) { if(b[v] == b[v - 1]) { sum ++; } else { ans = max(ans, sum); sum = 1; } } ans = max(ans, sum); } } } void solve() { Stop = cnt = visitNum = 0; memset(DFN, 0, sizeof DFN); for(int i = 1; i <= n; i ++) { if (!DFN[i]) {//有可能图不是连通图 tarjan(i); } } } int main() { while(~scanf("%d%d", &n, &m)) { for(int i = 1; i <= n; i ++) { scanf("%s", s); int p = getmin(); long long ha = 0; for(int i = p; s[i]; i ++) { ha = ha * 131LL % mod; ha = ha + s[i]; ha = ha % mod; } for(int i = 0; i < p; i ++) { ha = ha * 131LL % mod; ha = ha + s[i]; ha = ha % mod; } val[i] = ha; h[i] = -1; } sz = 0; for(int i = 1; i <= m; i ++) { int x, y; scanf("%d%d", &x, &y); if(x == y) continue; add(x, y); } ans = 0; solve(); printf("%d\n", ans); } return 0; }