2014携程第一题总结
灵感来自2014携程赛的第一题。
这个题目的关键在于如何建立合适的模型对三类状态进行刻画。
这道题目巧妙地在并查集中运用了带权向量来表示(推算)同根元素之间的关系。把并查集中的元素属性从原先的属于根拓展到属于根和相互关系。
建模思想:因为总共有三类状态,相互关系有三种,用rank【k】表示k与其最祖先节点的关系,0表示同类,1表示自己能吃最祖先,2表示最祖先吃自己。用rank【k】表示向量f(x,father【x】),(d - 1)可以表示f(x,y),用向量来表示互相关系~!
先看向量建模,基本图:
(由B -> A 和 A -> C 的关系可以推出 B -> C的关系:B被A吃,A被C吃,或者说:C吃A,A吃B,所以B吃C,符合三角关系 )
为什么可行呢?首先因为合适的建模方式和向量的可加性,其次因为只有3种关系的状态,所以运用%3可以表达这样的关系。
这个题目的关键在于如何建立合适的模型对三类状态进行刻画。
这道题目巧妙地在并查集中运用了带权向量来表示(推算)同根元素之间的关系。把并查集中的元素属性从原先的属于根拓展到属于根和相互关系。
建模思想:因为总共有三类状态,相互关系有三种,用rank【k】表示k与其最祖先节点的关系,0表示同类,1表示自己能吃最祖先,2表示最祖先吃自己。用rank【k】表示向量f(x,father【x】),(d - 1)可以表示f(x,y),用向量来表示互相关系~!
先看向量建模,基本图:
(由B -> A 和 A -> C 的关系可以推出 B -> C的关系:B被A吃,A被C吃,或者说:C吃A,A吃B,所以B吃C,符合三角关系 )
为什么可行呢?首先因为合适的建模方式和向量的可加性,其次因为只有3种关系的状态,所以运用%3可以表达这样的关系。
如图,在可加性的基础上用%3即可达到神奇的效果。其实仔细想想:由向量的可加性,我们按照题目给出的数据对各个节点的rank值
进行增加,如果只是单纯的增加,最后会形成一个分优先前后线性表,而现在对数据%3,就是将线性表变成环装表,或者更准确的说:
变成三角表(因为只有三种状态)。
所以现在我们就可以理解Judge中if(((d - 1 + rank[y]) % 3) == rank[x])这句话的意思了。
如果向量f(x,y)+ f(x,fa【x】) == f(y,fa【y】) (x,y同根,所以fa【x】== fa【y】)
则说明这句话是真话,否则是假话。
在find函数中,有回溯更新儿子节点的过程
fa[y] -> fa[x]的为union中干的事情,它只把fa[y]牵了过去,而黑色箭头为后来find中更新儿子节点时的操作(rank1先,rank2后)
下面为代码(同p1182)
#include <stdio.h> #define MAXN 50000 int fa[MAXN + 5],rank[MAXN + 5]; int n,k,d,x,y,count; void Init(){ for(int i = 1;i <= n;i ++){ fa[i] = i; rank[i] = 0; } return; } int find(int x){ if(x == fa[x]) return x; //如果第一步判断是错误,那么说明x是某个节点的儿子,需要更新儿子 //下面是回溯更新儿子 int prefa = fa[x]; fa[x] = find(fa[x]);//直到找到最祖先节,找到以后更新最祖先与儿子们的关系 rank[x] = (rank[x] + rank[prefa]) % 3; return fa[x];//返回给底下一层的是所找到的最祖先节点 } bool Judge(int x,int y,int d){ if(x > n || y > n || (x == y) && (d == 2)) return true;//假话 int fx = find(x),fy = find(y); if(fx == fy){//同根,判断描述是否为真if(((d - 1 + rank[y]) % 3) == rank[x]) return false;//真话 else return true;//假话 } else return false;//异根,需要先合并 } void Union(int x,int y,int d){ int fx = find(x),fy = find(y); if(fx == fy) return; fa[fx] = fy; rank[fx] = (d - 1 + rank[y] - rank[x] + 3) % 3; //出现了减号,要+3防止出现负数 return; } int main(){ //freopen("in.txt","r",stdin); scanf("%d %d",&n,&k); Init(); count = 0; for(int i = 1;i <= k;i ++){ scanf("%d %d %d",&d,&x,&y); if( Judge(x,y,d) ){//判断是否假话或是否同根 count ++; } else{ Union(x,y,d); } } printf("%d\n",count); return 0; }
剪刀石头布
Problem Description
现有M个人一起玩剪刀石头布,以1-M编号,每人出一种,出过不再改变,但是我们并不知道它到底是哪一种。 (其中石头赢剪刀,剪刀赢布,布赢石头,一样则平)
裁判用两种说法对这M个人所构成的输赢关系进行描述:
一:"1 A B",表示第A个人和第B个人出的一样。
二:"2 A B",表示第A个人赢第B个人。
裁判对M个人,用以上两种说法,连说N句话,其中有真的、也有假的。
一句话出现以下情况,就是假话,否则就是真话。
1) 该句话与之前的某些真话冲突;
2) 该句话中A或B比M大;
3) 该句话表示A赢A。
请根据给定的M和N,输出假话数。
其中(1 <= M <= 10,000),(0 <= N <= 10,000)
一:"1 A B",表示第A个人和第B个人出的一样。
二:"2 A B",表示第A个人赢第B个人。
裁判对M个人,用以上两种说法,连说N句话,其中有真的、也有假的。
一句话出现以下情况,就是假话,否则就是真话。
1) 该句话与之前的某些真话冲突;
2) 该句话中A或B比M大;
3) 该句话表示A赢A。
请根据给定的M和N,输出假话数。
其中(1 <= M <= 10,000),(0 <= N <= 10,000)
Input
第1行是一个自然数K,代表有K组数据。
每组数据以一个空行分隔,其中每组数据的第1行是两个自然数M、N,以空格分开。
每组数据的第2行至N+1行,每行是三个自然数X,A,B,三个数之间用空格分开,X(1或2)表示说法的种类。
每组数据以一个空行分隔,其中每组数据的第1行是两个自然数M、N,以空格分开。
每组数据的第2行至N+1行,每行是三个自然数X,A,B,三个数之间用空格分开,X(1或2)表示说法的种类。
Output
每组数据对应一行,每行有一个整数,代表假话数。
Sample Input
3
43 11
1 4 3
2 3 3
1 4 1
1 4 4
2 3 3
1 2 2
2 1 4
1 1 1
2 1 4
2 3 4
2 3 2
66 9
2 3 1
2 4 4
2 1 2
2 4 3
2 4 2
2 2 3
1 3 2
1 2 1
1 1 1
6 7
2 3 7
2 1 2
2 4 4
1 2 1
1 3 2
1 2 3
2 1 3
Sample Output
5
4
3