开灯
题目描述
在一条无限长的路上,有一排无限长的路灯,编号为1,2,3,4,…。
每一盏灯只有两种可能的状态,开或者关。如果按一下某一盏灯的开关,那么这盏灯的状态将发生改变。如果原来是开,将变成关。如果原来是关,将变成开。
在刚开始的时候,所有的灯都是关的。小明每次可以进行如下的操作:
指定两个数,a,t(a为实数,t为正整数)。将编号为[a],[2 × a],[3 × a],…,[t × a]的灯的开关各按一次。其中[k]表示实数k的整数部分。
在小明进行了n次操作后,小明突然发现,这个时候只有一盏灯是开的,小明很想知道这盏灯的编号,可是这盏灯离小明太远了,小明看不清编号是多少。
幸好,小明还记得之前的n次操作。于是小明找到了你,你能帮他计算出这盏开着的灯的编号吗?
题目链接:https://www.luogu.com.cn/problem/P1161
输入格式
第一行一个正整数n,表示n次操作。
接下来有n行,每行两个数,ai, ti 。其中ai是实数,小数点后一定有6位,ti 是正整数。
输出格式
仅一个正整数,那盏开着的灯的编号。
输入输出样例
输入
3
1.618034 13
2.618034 7
1.000000 21
输出
20
题解
刚开始看到这个题的时候,第一时间想到的就是定义一个bool类型的数组vis[],刚开始所有元素初始化为false,表示灯是关着的,每次将若干盏灯改变状态,就将vis[i]变反,最后输出数值为true的那盏灯即可,但这样一来,时间复杂度就太高了,为O(m*n)。
后来换了一种思路,由于最近在尝试着使用STL的容器,就想着用set来试一下,定义:存在set中的元素i表示序号为i的灯是开着的,对于下面的n次操作,如果要将一盏灯改变状态,若它原先处于关闭的状态,即不在set容器中,那么改变状态后就处于打开的状态,就要将它插入到set中;反之,如果一盏灯原先处于打开的状态,那么改变状态之后他就是处于关闭的状态,就要从原先的set中删除,由于题目已经保证最终只有一盏灯处于打开的状态,那么就可以确认最终set中只有一个元素,输出即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
cin>>n;
set<int>ans;
for(int i=0;i<n;i++){
double a;
int t;
cin>>a>>t;
for(int j=1;j<=t;j++){
if(ans.find((int)(j*a))!=ans.end()){//有这个元素
ans.erase((int)(j*a));
}
else{
ans.insert((int)(j*a));
}
}
}
set<int>::iterator it=ans.begin();
cout<<(*it)<<endl;
return 0;
}
但是这个方法需要频繁的插入和删除,最终提交的时候也是最后一个点TLE了。
这时候想起之前看到的一篇博客,可以使用异或操作,用到的性质主要有以下两条:
1.两个相同的元素异或操作后结果为0。
2.0和任何元素异或之后还是这个元素本身。
那么下面讲一下如何利用这两条性质。
所有的灯刚开始处于关闭的状态,假设一共有n盏灯,最终仍然有n-1盏灯关闭,它们状态改变的次数一定是偶数次,最终打开的那盏灯状态改变的次数一定是奇数次。因此,如果对每次给定的灯的序号进行异或操作,所有的经过偶数次改变的灯的序号的结果为0,奇数又可以等于一个偶数加一,那么最终的结果就是0异或还亮着的那盏灯的序号,输出即可。
代码如下:
#include<bits/stdc++.h>
using namespace std;
int main(){
int ans;
int n;
cin>>n;
for(int i=0;i<n;i++){
double a;
int t;
cin>>a>>t;
for(int j=1;j<=t;j++){
ans=ans^(int)(j*a);
}
}
cout<<ans<<endl;
return 0;
}
这个方法最终也是成功AC了,感谢各位的观看。