传送门

题目描述

输入

输出

样例输入


Sample Input

样例输出

Boys win!
Girls win!
Girls win!
Boys win!
Girls win!
Boys win!
Boys win!
Girls win!
Girls win!
Boys win!
Girls win!

Sample Output

分析

这道题我们首先想到的就是模拟,但是40000的数据显然是太大了,肯定会超时

那么我们来模拟一下第一个样例

这是刚开始建好的边,建完边后我们发现这棵树没有能够修改的节点

所以我们对于第一个询问0 1显然要输出 Boys win!

接下来是一个修改边的的操作 1 2 1 1

修改完后就变成了下面这样

接下来又是一个询问操作0 2

我们发现在girls把(1,2)的边权修改为0后,boys不能再进行操作

所以很显然 Girls win!

第一个样例我们的模拟就结束了

是不是什么规律也没有看出来的,没有关系,我们再来第二组

(提示:注意观察与根节点相邻的边)

首先上来的就是四个询问,分别是1、2、3、4节点作为根节点

当1作为根节点时,操作如下图

我们发现,与根节点相邻的边的权值一开始为1,经过一次操作后变成了0,这时操作结束 Girls win! 

当2为根节点时

我们发现,与根节点相邻的边有两个,权值一开始都为1,经过两次次操作后变成了0,这时操作结束 Boys win!

当3为根节点时

我们发现,与根节点相邻的边有两个,一个为1,一个为0,经过一次操作后1的那个变成了0,这时操作结束 Girls win!

当4为根节点时(画图好难用)

我们发现,与根节点相邻的边的权值一开始为0,经过两次操作后从0变为1又变为0,这时操作结束 Boys win!

下面是一个修改边权的操作 1 2 1 0

修改完后,就成了这样

当1为根节点时

我们发现,与根节点相邻的边的权值一开始为0,经过两次操作后从0变为1又变为0,这时操作结束 Boys win!

当2为根节点时

我们发现,与根节点相邻的边有两个,一个为1,一个为0,经过一次操作后1的那个变成了0,这时操作结束 Girls win!

当3为根节点时

我们发现,与根节点相邻的边有两个,一个为1,一个为0,经过一次操作后1的那个变成了0,这时操作结束 Girls win!

最后又是一个修改边权的操作,我们就不再模拟

通过以上的模拟,我们可以发现什么呢?

1、操作奇数次,girls win,操作偶数次boys win(是不是很显然

2、如果根节点只有一条边相连,那么如果这条边的边权为1,需要操作奇数次才能把它变成0,因为你的每一次操作都会对它产生影响,而且你无论后面操作多少次,最终还是要把它变为0,根据第一条性质,girls win

如果边权是0呢,就和上面相反,boys win

3、如果有多条边呢,我们就把每一条边上的操作次数累加,再根据性质1判断

方法一

听到这里,你是不是很激动呢,当给出一个根节点时,我们只需要把与它相邻的边的边权加和,再判断奇偶性就可以了

这里要注意的是,修改边的操作不一定修改成与原来相反的价值,有可能原来价值为1,修改后还为1

代码

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<ctime>
using namespace std;
const int maxn=;
struct asd{
int from,to,next,val;
}b[maxn];
int head[maxn],tot=;
void ad(int aa,int bb,int cc){
b[tot].from=aa;
b[tot].to=bb;
b[tot].next=head[aa];
b[tot].val=cc;
head[aa]=tot++;
}
int du[maxn];
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(head,-,sizeof(head));
memset(&b,,sizeof(struct asd));
memset(du,,sizeof(du));
tot=;
int n,m;
scanf("%d%d",&n,&m);
for(int i=;i<n;i++){
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
ad(aa,bb,cc);
ad(bb,aa,cc);
du[aa]+=cc;
du[bb]+=cc;
}
while(m--){
int cc;
scanf("%d",&cc);
if(cc==){
int aa;
scanf("%d",&aa);
int ans=du[aa];
if(ans%==) printf("Boys win!\n");
else printf("Girls win!\n");
} else {
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
for(int i=head[aa];i!=-;i=b[i].next){
int u=b[i].to;
if(bb==u && b[i].val!=cc){
b[i].val=cc;
b[i^].val=cc;
if(cc==){
du[aa]++;
du[bb]++;
} else {
du[aa]--;
du[bb]--;
}
break;
}
if(bb==u) break;
}
}
}
}
return ;
}

普通枚举

写完后,我们把它交上去,发现过了,时间消耗还不多

但是我们细细一想会发现,这种做法的时间效率不能保证,我们完全可以造一组数据将它卡成n^2

比如下面这样

m,n小于40000,我们完全可以按照上面那样建边,然后来39999次修改操作

最后再来一次询问

而且题目中最多会给出5组数据

那么耗时就是5*40000*40000,显然会T(后面会有样例,大家可以试一下)

方法二

既然如此,那我们就要考虑怎么省去遍历边的操作

题目中只给出了0,1两种值

所以,联系我们最近学过的内容

没错,就是bitset

代码

 #include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<cmath>
#include<bitset>
#include<ctime>
using namespace std;
bitset<> bit[];
int du[];
int main(){
int t;
scanf("%d",&t);
while(t--){
memset(du,,sizeof(du));
for(int i=;i<;i++){
bit[i].reset();
}
int n,m;
scanf("%d%d",&n,&m);
for(int i=;i<n;i++){
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
if(cc==) bit[aa][bb]=bit[bb][aa]=;
du[aa]+=cc,du[bb]+=cc;
}
while(m--){
int cc;
scanf("%d",&cc);
if(cc==){
int aa;
scanf("%d",&aa);
int ans=du[aa];
if(ans%==) printf("Boys win!\n");
else printf("Girls win!\n");
} else {
int aa,bb,cc;
scanf("%d%d%d",&aa,&bb,&cc);
if(bit[aa][bb]!=cc){
if(cc==){
bit[aa][bb]=bit[bb][aa]=;
du[aa]++,du[bb]++;
} else {
bit[aa][bb]=bit[bb][aa]=;
du[aa]--,du[bb]--;
}
}
}
}
}
return ;
}

bitset

但是很遗憾内存开不下

虽然bitset很优秀,只占一个二进制位,但是题目中的内存限制为65536 kB

最多可以开一维的bitset数组65536*1024*8=536870912(5亿多,是不是很强大)

但因为是二维数组,我们开方后就只有23000了,只能达到原题数据的一半左右

如果我们开40000*40000显然会M掉

如果开23000*23000呢,会RE,因为下标访问bitset数组并不会检查越界

而且因为数组过大,你不可能每组数据都重新开一个bitset数组,所以你要初始化,但初始化就要花费几百毫秒

方法三

这时,优秀的解法该出现了

是什么呢?

答案就是map+pair

map的用法大家应该都很熟悉了,我们就简单讲一下pair吧

摘自百度百科:

 定义:c++中的结构模板,定义在头文件<utility>中,提供一个包含2个数据成员的结构体模板。继承与_Pair_base结构体模板。通过first,second访问2个成员,有 operator= 和 swap 方法。

以下内容摘自:https://blog.csdn.net/qq_42232118/article/details/82078854

其实,这里pair的作用就是把两个元素整合在一起

那么这个算法优秀在哪里呢?

map查询元素的复杂度为O(logn),而枚举的话复杂度是随机的,幸运的话,你一次就可以查询完,但是遇上特殊情况的话,你会被卡掉

代码

 #include<cstdio>
#include<cstring>
#include<map>
#include<utility>
#include<ctime>
using namespace std;
int deg[];
map<pair<int,int>,int> amap;
int main(){
int t,n,m,x,y,z,id,j;
scanf("%d",&t);
while(t--){
memset(deg,,sizeof(deg));
amap.clear();
scanf("%d%d",&n,&m);
for(int i=;i<n;i++){
scanf("%d%d%d",&x,&y,&z);
if(x>y){int te=x;x=y;y=te;} //统一顺序这样方便后期查找
amap[make_pair(x,y)]=z;
if(z==){
deg[x]++;
deg[y]++;
}
}
for(int i=;i<m;i++){
scanf("%d",&id);
if(id==){
scanf("%d",&x);
if(deg[x]%) printf("Girls win!\n");
else printf("Boys win!\n");
}
else{
scanf("%d%d%d",&x,&y,&z);
if(x>y){int te=x;x=y;y=te;}
if(amap[make_pair(x,y)]!=z){
if(z==){
amap[make_pair(x,y)]=;
deg[x]--;
deg[y]--;
}
else{
amap[make_pair(x,y)]=;
deg[x]++;
deg[y]++;
}
}
}
}
}
return ;
}

map优化

比较

这是一组符合题目要求的极端样例

样例太大,插不上,就放一个生成数据的代码吧

 #include<bits/stdc++.h>
using namespace std;
int main(){
freopen("data.in","w",stdout);
srand(time(NULL));
printf("5\n");
for(int i=;i<=;i++){
printf("39999\n39999\n");
for(int i=;i<=;i++){
printf("1 %d %d\n",i,i%);
}
for(int i=;i<=;i++){
printf("1 1 2 0\n");
}
printf("0 1\n");
}
return ;
}

在自己电脑上测的话毕竟不太准,那么我们可以借助一个很好的平台——洛谷

我在洛谷创建了一个题目,数据用的是极端数据(就是用上面的代码生成的数据)

链接

(这里为了关照一下bitset,我把内存限制开大了,为了更清晰的比较,我把时间限制调到了10s,而且都没有开O2优化)

大家可以拿自己的代码试一下,看一下会不会T掉

这是我的测试结果

map

第1组数据是最极限的那一种,2到5组数据也会卡枚举的方法,但没那么严重,6到10是小数据

我们发现map的效率比较稳定,取决于n的大小,是一种不错的方法

枚举

枚举的话,卡枚举的前5组都超过了2秒,即使时间开到了10s,最极限的第一组也会T掉,但是随机数据还是很快的

bitset

biset初始化会占用大量时间,不划算,小数据也会跑到几百毫秒

而且内存开到336.82MB也不现实,所以还是用map吧

总结:如果数据随机的话我觉得差别不大,map每次查询是log(n),不管数据如何都比较稳定,直接爆搜随机数据还可以,但极限数据或特别的构图可能会超时

biset的话内存是个短板

朋友HDU - 5963 (思维题) 三种方法的更多相关文章

  1. Linux系统下修改环境变量PATH路径的三种方法

    这里介绍Linux的知识,比如把/etc/apache/bin目录添加到PATH中有三种方法,看完之后你将学会Linux系统下如何修改环境变量PATH路径,需要的朋友可以参考下 电脑中必不可少的就是操 ...

  2. asp.net跳转页面的三种方法比较

    目前,对于学习asp.net的很多朋友来讲,实现跳转页面的方法还不是很了解.本文将为朋友们介绍利用asp.net跳转页面的三种方法,并对其之间的形式进行比较,希望能够对朋友们有所帮助. ASP.NET ...

  3. 两个Map的对比,三种方法,将对比结果写入文件。

    三种方法的思维都是遍历一个map的Key,然后2个Map分别取这2个Key值所得到的Value. #第一种用entry private void compareMap(Map<String, S ...

  4. 斐波那契数列-java编程:三种方法实现斐波那契数列

    题目要求:编写程序在控制台输出斐波那契数列前20项,每输出5个数换行 斐波那契数列指的是这样一个数列:1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, … 这个数列 ...

  5. CentOS7创建本地YUM源的三种方法

    这篇文章主要介绍了CentOS7创建本地YUM源的三种方法,本文讲解了使用CentOS光盘作为本地yum源.如何为CentOS创建公共镜像.创建完全自定义的本地源等内容,需要的朋友可以参考下     ...

  6. Postgresql 创建主键并设置自动递增的三种方法

    Postgresql 有以下三种方法设置主键递增的方式,下面来看下相同点和不同点. --方法一create table test_a (  id serial,  name character var ...

  7. (OPC Client .NET 开发类库)网上很多网友都有提过,.NET开发OPC Client不外乎下面三种方法

    1. 背景 OPC Data Access 规范是基于COM/DCOM定义的,因此大多数的OPC DA Server和client都是基于C++开发的,因为C++对COM/DCOM有最好的支持.现在, ...

  8. c#封装DBHelper类 c# 图片加水印 (摘)C#生成随机数的三种方法 使用LINQ、Lambda 表达式 、委托快速比较两个集合,找出需要新增、修改、删除的对象 c# 制作正方形图片 JavaScript 事件循环及异步原理(完全指北)

    c#封装DBHelper类   public enum EffentNextType { /// <summary> /// 对其他语句无任何影响 /// </summary> ...

  9. php将数组写入到文件的三种方法

    php将数组原样写入或保存到文件有三种方法可以实现, 第一种方法是使用serialize, 第二种方法是使用print_r, 第三种方法是使用var_export, 本文章向大家介绍这三种方法是如何将 ...

随机推荐

  1. maven配置阿里云仓库进行下载

    maven阿里云仓库下载 为了解决maven在下载jar包的时候,速度比较慢的问题,可以配置阿里云仓库配置方式的进行下载,首先找到您安装的maven路径. 在conf文件夹下面有个settings.x ...

  2. 3.keras-简单实现Mnist数据集分类

    keras-简单实现Mnist数据集分类 1.载入数据以及预处理 import numpy as np from keras.datasets import mnist from keras.util ...

  3. 面试官:换人!他连 TCP 这几个参数都不懂

    每日一句英语学习,每天进步一点点: 前言 TCP 性能的提升不仅考察 TCP 的理论知识,还考察了对于操心系统提供的内核参数的理解与应用. TCP 协议是由操作系统实现,所以操作系统提供了不少调节 T ...

  4. 2020阿里最新出品的泰山版Java开发手册,告别垃圾代码

    说起华山,我就想起岳不群,不,令狐冲:说起泰山,我就想起司马迁,他的那句名言"人总有一死,或重于泰山,或轻于鸿毛",真的发人深省啊.这就意味着,阿里出品的泰山版 Java 开发手册 ...

  5. 设计一个简单的多线程(Fecit)_1

    D6高级编程,Fecit ,学习里面关于线程创建的一个例子.,按照那个例子做的,不过本人喜欢将线程实现部分作为单独的单元,主线程再调用它. unit Unit1; interface uses Win ...

  6. LR字符串处理函数-lr_save_datetime

    void lr_save_datetime(const char *format, int offset, const char *name); 中文解释: lr_save_datetime将当前日期 ...

  7. C#数据结构与算法系列(六):链表——双链表(Double-LinkedList)

    1.对比单向链表 单向链表查找的方向只能是一个方向,而双向链表可以向前或者向后查找 单向链表不能自我删除,需要靠辅助节点,而双向链表可以自我删除 对于单向链表的删除,我们首先要找到单向链表待删除节点的 ...

  8. 深入理解Java虚拟机学习笔记(三)-----类文件结构/虚拟机类加载机制

    第6章 类文件结构 1. 无关性 各种不同平台的虚拟机与所有平台都统一使用的程序存储格式——字节码(即扩展名为 .class 的文件) 是构成平台无关性的基石. 字节码(即扩展名为 .class 的文 ...

  9. 提交代码到gitbub.com

    提交代码到gitbub.com touch README.md //新建说明文件 git init //在当前项目目录中生成本地git管理,并建立一个隐藏.git目录 git add . //添加当前 ...

  10. 第一章:开始启程-你的第一行Android代码

    Android 系统为开发者提供了什么? 四大组件 活动(Activity):界面 服务(Service):后台默默运行 广播接收器(Broadcast Receiver):接收.发送广播消息 内容提 ...