题干:
B君在围观一群男生和一群女生玩游戏,具体来说游戏是这样的:
给出一棵n个节点的树,这棵树的每条边有一个权值,这个权值只可能是0或1。 在一局游戏开始时,会确定一个节点作为根。接下来从女生开始,双方轮流进行 操作。
当一方操作时,他们需要先选择一个不为根的点,满足该点到其父亲的边权为1; 然后找出这个点到根节点的简单路径,将路径上所有边的权值翻转(即0变成1,1 变成0 )。
当一方无法操作时(即所有边的边权均为0),另一方就获得了胜利。
如果在双方均采用最优策略的情况下,女生会获胜,则输出“Girls win!”,否则输 出“Boys win!”。
为了让游戏更有趣味性,在每局之间可能会有修改边权的操作,而且每局游戏指 定的根节点也可能是不同的。
具体来说,修改边权和进行游戏的操作一共有m个,具体如下:
∙∙“0 x”表示询问对于当前的树,如果以x为根节点开始游戏,哪方会获得胜利。
∙∙“1 x y z ”表示将x和y之间的边的边权修改为z。
B君当然知道怎么做啦!但是他想考考你。
Input
包含至多5组测试数据。
第一行有一个正整数,表示数据的组数。
接下来每组数据第一行,有二个空格隔开的正整数n,m,分别表示点的个数,操 作个数。保证n,m< 40000。
接下来n-1行,每行三个整数x,y,z,表示树的一条边。保证1<x<n, 1<y< n, 0 <= z <= 1。
接下来m行,每行一个操作,含义如前所述。保证一定只会出现前文中提到的两 种格式。
对于操作0,保证1 <= x <= n ;对于操作1,保证1 <= x <= n, 1 <= y <= n, 0 <= z <= 1,保证树上存在一条边连接x和y。
Output
对于每组数据的每一个询问操作,输出一行“Boys win!”或者“Girls win!”。
Sample Input
2
2 3
1 2 0
0 1
1 2 1 1
0 2
4 11
1 2 1
2 3 1
3 4 0
0 1
0 2
0 3
0 4
1 2 1 0
0 1
0 2
0 3
1 3 4 1
0 3
0 4
Sample Output
Boys win!
Girls win!
Girls win!
Boys win!
Girls win!
Boys win!
Boys win!
Girls win!
Girls win!
Boys win!
Girls win!
解题报告:
想想他需要不停的换根,数据量又如此庞大,以至于每次换根后,我dfs一遍的时间都没有,所以肯定是个找规律题,肯定不会给你遍历整棵树的机会的。从必胜态必败态的角度考虑,不难发现如果是一条链的情况,规律就是根节点相连的那条边的边权如果是1,那女生胜,反之男生胜。又因为如果是多条链的话,链之间互不影响,所以可以直接统计和根节点相邻的边的边权和,如果是奇数,那就是女生获胜,反之男生获胜。当然如果先一条链然后分成两条链的情况,那其实也问题不大,因为发现可以转化成一条链的情况上面。(其实可以大胆猜想,既然一条链的情况,只和根节点相邻的边的边权有关系,假设这条边是e,那也就是所有 想到达根节点的路径,只要需要经过边e,那就都和路径上的其他边没关系,之和边e相关,所以就这样?)
好吧,还是来一发正解:
正解是,经分析发现无论操作哪个节点,这个节点都会使其所在子树中与根直接相连的那条边翻转。那么再根据游戏的规则以及子树的性质,会发现若你面对当前这条与根相连的边的权值为1时,对方通过子树上任意点来翻转当前边,你都能再次进行翻转。如果你面对权值为0,那么要么你不能进行操作,要么不管你进行什么操作对方都还能进行操作。
AC代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define FF first
#define SS second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
vector<PII> vv[MAX];
int n,m;
int main()
{int T;cin>>T;while(T--) {scanf("%d%d",&n,&m);for(int i = 1; i<=n; i++) vv[i].clear(); for(int x,y,z,i = 1; i<=n-1; i++) {scanf("%d%d%d",&x,&y,&z);vv[x].pb(pm(y,z));vv[y].pb(pm(x,z));}for(int i = 1; i<=n; i++) sort(vv[i].begin(),vv[i].end());int op,u,v,z,ans;while(m--) {scanf("%d",&op);if(op == 0) {ans=0; scanf("%d",&u);for(auto x : vv[u]) {if(x.SS == 1) ans++;}if(ans & 1) puts("Girls win!");else puts("Boys win!");}else {scanf("%d%d%d",&u,&v,&z);int pos1 = lower_bound(vv[u].begin(),vv[u].end(),pm(v,-1)) - vv[u].begin();int pos2 = lower_bound(vv[v].begin(),vv[v].end(),pm(u,-1)) - vv[v].begin();vv[u][pos1] = pm(v,z); vv[v][pos2] = pm(u,z);}}} return 0 ;
}
关于实现细节:
发现对于0操作,如果是菊花图的话就炸了。
所以优化一下,发现不需要枚举:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<queue>
#include<stack>
#include<map>
#include<vector>
#include<set>
#include<string>
#include<cmath>
#include<cstring>
#define FF first
#define SS second
#define ll long long
#define pb push_back
#define pm make_pair
using namespace std;
typedef pair<int,int> PII;
const int MAX = 2e5 + 5;
vector<PII> vv[MAX];
int n,m,in[MAX];
int main()
{int T;cin>>T;while(T--) {scanf("%d%d",&n,&m);for(int i = 1; i<=n; i++) vv[i].clear(),in[i]=0; for(int x,y,z,i = 1; i<=n-1; i++) {scanf("%d%d%d",&x,&y,&z);vv[x].pb(pm(y,z));vv[y].pb(pm(x,z));if(z == 1) in[x]++,in[y]++; }for(int i = 1; i<=n; i++) sort(vv[i].begin(),vv[i].end());int op,u,v,z,ans;while(m--) {scanf("%d",&op);if(op == 0) {ans=0; scanf("%d",&u);if(in[u] & 1) puts("Girls win!");else puts("Boys win!");}else {scanf("%d%d%d",&u,&v,&z);int pos1 = lower_bound(vv[u].begin(),vv[u].end(),pm(v,-1)) - vv[u].begin();int pos2 = lower_bound(vv[v].begin(),vv[v].end(),pm(u,-1)) - vv[v].begin();if(vv[u][pos1].SS==0 && z == 1) {vv[u][pos1] = pm(v,z); vv[v][pos2] = pm(u,z);in[u]++,in[v]++;}else if(vv[u][pos1].SS==1 && z == 0) {vv[u][pos1] = pm(v,z); vv[v][pos2] = pm(u,z);in[u]--,in[v]--;} }}} return 0 ;
}