题目描述
一年一度的假面舞会又开始了,栋栋也兴致勃勃的参加了今年的舞会。今年的面具都是主办方特别定制的。每个参加舞会的人都可以在入场时选择一 个自己喜欢的面具。每个面具都有一个编号,主办方会把此编号告诉拿该面具的人。为了使舞会更有神秘感,主办方把面具分为k (k≥3)类,并使用特殊的技术将每个面具的编号标在了面具上,只有戴第i 类面具的人才能看到戴第i+1 类面具的人的编号,戴第k 类面具的人能看到戴第1 类面具的人的编号。 参加舞会的人并不知道有多少类面具,但是栋栋对此却特别好奇,他想自己算出有多少类面具,于是他开始在人群中收集信息。 栋栋收集的信息都是戴第几号面具的人看到了第几号面具的编号。如戴第2号面具的人看到了第5 号面具的编号。栋栋自己也会看到一些编号,他也会根据自己的面具编号把信息补充进去。由于并不是每个人都能记住自己所看到的全部编号,因此,栋栋收集的信 息不能保证其完整性。现在请你计算,按照栋栋目前得到的信息,至多和至少有多少类面具。由于主办方已经声明了k≥3,所以你必须将这条信息也考虑进去。
输入
第一行包含两个整数n, m,用一个空格分隔,n 表示主办方总共准备了多少个面具,m 表示栋栋收集了多少条信息。接下来m 行,每行为两个用空格分开的整数a, b,表示戴第a 号面具的人看到了第b 号面具的编号。相同的数对a, b 在输入文件中可能出现多次。
输出
包含两个数,第一个数为最大可能的面具类数,第二个数为最小可能的面具类数。如果无法将所有的面具分为至少3 类,使得这些信息都满足,则认为栋栋收集的信息有错误,输出两个-1。
样例输入
【输入样例一】
6 5
1 2
2 3
3 4
4 1
3 5
【输入样例二】
3 3
1 2
2 1
2 3
样例输出
【输出样例一】
4 4
【输出样例二】
-1 -1
提示
100%的数据,满足n ≤ 100000, m ≤ 1000000。
题解
今天的四道题除了第二题之外都是以点、边的图论形式给出的,但是并没有用图论的最短路、LCA之类的板子,所以大概算是伪图论。思维量比较大,所谓的一到集训必定做好题;上午灌了不少咖啡所以没有假期刚过的混沌,精神也很集中。考前莫名有点紧张?是到了该重视考试的时候了。ad爷讲题非常棒!比平常上课收获更多,虽然速度快但是理解得比平时深刻。有了强烈的想学学高深数学的想法,自己现在虽然纯代码的东西也不是很强,但是毕竟数学方面最弱,数据结构也不行。好好努力,这几天的集训大概会有所提高吧。ad爷居然说改这些题只需要两个小时,有点不可想象,果然我还是太弱了QAQ。
考试时打的是并查集,得了10分。从样例就可以看出来判环是一个很重要的步骤,想了想方法大概除了spfa判入队次数就是搜索了,明显不是个最短路的题所以非搜索莫属。题目中总是有一些很让人搞不明白的表述,比如说信息不完整,比如说自己也会看到,比如说不完全覆盖,这种时候就需要自己开动脑筋揣摩一下了。对于环的情况,当时以为搞出环来就算确定了,没有想到最大公约数,虽然偶然中画出了假环的情况但是也没有给出合理的解决办法;对于链的情况,刚开始以为最小只不过是总点数-1,后来想到了3总是可行的,但是到了改题改了很久之后才明白最大应该是链长之和而不是简单的n。总的来说,这道题无论是考还是改都是一步步拓宽思路的过程,开始的思维还是太简单、不够缜密,总是在偶然中擦过了正解的边却还是不能把它推广到真正正确。
真环 1->2->3->4->1 假环 1->2->4->7->6 + 1->3->5->6
直链 1->2->3->4->5 分支型支链 1->2->3->4->5 + 1->2->6
合并型支链 1->2->3->4->5 + 6->7->8->9->3(最后把我困住的一种。选主链,称某烷!)
正解是头插法加边,真边为1假边为-1,用并查集统计联通块,然后对每个联通块dfs,给每个点的标号是父节点标号加边权。如果dfs到一个已经走过的点,比较将要给它的标号和它已有的标号就会得出环的大小(要注意如果将要给它的标号和已有标号一样的话只是说明又回到了这一层,不用作环处理)。如果没有环,每一个链的长度就是出现过的最大标号与最小标号之差+1(这一点我居然一直没想到!各种局部变量各种传参打得乱七八糟还得不出正确答案,最后还是问了zzh才得知“还有这种操作?”)。
void dfs(int x,int fx)
{
vi[x]=1;
int jk;
for(int i=h[x];i!=-1;i=b[i].ne)
{
jk=b[i].v;
if(vi[jk]&&c[x]+b[i].w!=c[jk])
{
if(ans==-1) ans=abs(c[x]+b[i].w-c[jk]);
else ans=gcd(ans,abs(c[x]+b[i].w-c[jk]));
yh=1;
}
c[jk]=c[x]+b[i].w;
bj(zh,c[jk]);
xbj(fh,c[jk]);
if(!vi[jk])
dfs(jk,b[i].w);
}
}
结果:
有环:max=gcd(各环长)
max<3 无解
max>=3 mix=max大于等于3的最小因数
无环:max=各链长之和
max<3 无解
max>=3 mix=3
改题的过程旷日持久,主要是合并型支链处理得不对。但是在这个过程中坚持了下来,自己不断地发现问题解决问题,直到最后按ad爷说的给自己限定了一个时间,依然没有改过来就去找zzh问了最后一个问题;这样确实能保证效率,而且给了自己一个压力。奥赛课,集中专注、高效紧张是最难的。做了这道题无论是知识上还是心态上都收获很大,时间没有白费。不过剩下的题都没有改,做到一半又舍不得放下,看来像教练说的那样从易到难确实是有必要的(可是我为什么觉得这题除了情况多点其实没有其他题难呢= =)。
#include<iostream> #include<cstdio> #include<cstring> #include<cmath> using namespace std; const int nj=100010,mj=1000010; int m,n,e,h[nj],fa[nj],c[nj],ans; bool yh,vi[nj]; int zh,fh,a1,a2,jg,temp; struct B { int v,ne,w; }b[mj*2]; int gcd(int x,int y) { if(y==0) return x; return gcd(y,x%y); } void add(int x,int y,int z) { b[e].v=y; b[e].ne=h[x]; b[e].w=z; h[x]=e++; } void bj(int &x,int y) { x=x>y?x:y; } void xbj(int &x,int y) { x=x<y?x:y; } void dfs(int x,int fx) { vi[x]=1; int jk; for(int i=h[x];i!=-1;i=b[i].ne) { jk=b[i].v; if(vi[jk]&&c[x]+b[i].w!=c[jk]) { if(ans==-1) ans=abs(c[x]+b[i].w-c[jk]); else ans=gcd(ans,abs(c[x]+b[i].w-c[jk])); yh=1; } c[jk]=c[x]+b[i].w; bj(zh,c[jk]); xbj(fh,c[jk]); if(!vi[jk]) dfs(jk,b[i].w); } } int find(int x) { if(fa[x]==x) return x; return find(fa[x]); } void hb(int x,int y) { x=find(x); y=find(y); if(x!=y) fa[x]=y; } int main() { scanf("%d%d",&n,&m); memset(h,-1,sizeof(h)); ans=-1; for(int i=1;i<=n;i++) fa[i]=i; for(int i=1;i<=m;i++) { scanf("%d%d",&a1,&a2); add(a1,a2,1); add(a2,a1,-1); hb(a2,a1); } memset(c,0,sizeof(c)); for(int i=1;i<=n;i++) if(fa[i]==i) { zh=0; fh=0x7fffffff; temp=fh; dfs(i,-2); if(zh==0&&fh==temp) jg++; else jg+=zh-fh+1; } if(!yh) { if(jg<3) printf("-1 -1"); else printf("%d 3",jg); return 0; } if(ans<3) printf("-1 -1"); else { for(int i=3;i<=ans;i++) if((ans%i)==0) { jg=i; break; } printf("%d %d",ans,jg); } return 0; }