AT-abc347(C-E)

AtCoder Beginner Contest 347

C - Ideal Holidays

这场做得最头疼的题

分析

容易想到先用(di+a+b-1)%(a+b)+1把di映射到[1,a+b]的区间再排序,但由于未知星期一是哪天,我们也无法确定映射后的di是星期几

关于这个映射可以自己推一下

我们取a+b=7看几个例子

对于
3 2 5
1 2 9
经映射后得
1 2 2
取第一天是星期一,所有di都在[1,2]内
而对于
4 2 5
1 2 7 9
经映射再排序后得
1 2 2 7
这时取第一天为星期一,d[4]为星期日,取第七天为星期一,第二天为星期三,我们发现此时不能使所有di都在[1,2]内
但若是b=4,a=3
4 3 4
1 2 7 9
经映射再排序后得
1 2 2 7
此时取第七天为星期一,第二天为星期三,我们发现此时能使所有di都在[1,3]内
而
4 3 4
1 2 6 9
显然又无法实现

所以能得到:映射再排序后存在d[i]-d[i-1]>b就能够实现,但若是所有d[i]原本就在[1,a]的范围内呢,要处理这种情况,我们可以使d[n+1]=d[0]+a+b,或者做一个特判

代码

void solve(){
	int n,a,b,x;cin>>n>>a>>b;
	vector<int>d;
	rep(i,1,n){
		cin>>x;
		d.push_back((x+a+b-1)%(a+b)+1);//映射处理
	}
	sort(d.begin(),d.end());
	d.push_back(d[0]+a+b);//相当于加上边界,处理一周七天时1 2 9的情况
	rep(i,1,d.size()-1){
		if(d[i]-d[i-1]>b){
			cout<<"Yes";
			return;
		}
	}
	cout<<"No";
}

D - Popcount and XOR

贪心

分析

我们知道只有两数在二进制形式下当前位不同时,异或后的当前位才为1,反推过来,对于x^y=c ,c的二进制下当前位为1的话说明x或y的当前位一定是一1一0,而又由于要保证a与b个1都要刚好用尽,因此我们的贪心策略就是,对于c的二进制下为1的位,若a>b,则用a,此时x加上当前位的贡献,反之用b,y加上当前位的贡献,这样以后再判断是否满足ab相等且大于等于0

代码

void solve(){
	int a,b,x=0,y=0,c;cin>>a>>b>>c;
	rep(i,0,60){
		if(c>>i&1){
			if(a>b){
				x+=(1ll<<i);//赛时甚至是用的快速幂(っ °Д °;)っ 
				a--;
			} 
			else{
				y+=(1ll<<i);
				b--;
			} 
		} 
	}
	if(a!=b||a<0||b<0){
		cout<<"-1";
		return;
	}
	rep(i,0,60){
		if(!(c>>i&1)&&a){
			x+=(1ll<<i);
			y+=(1ll<<i);
			a--;
		}
	} 
	if(a){//还要特判a或者b是否有剩 
		cout<<"-1";
		return;
	}
	cout<<x<<" "<<y;
}

E - Set Add Query

分析

先贴一个会t的纯模拟做法是为了更好地理解题意

void solve(){
	int n,q,x;cin>>n>>q;
	set<int>s;
	rep(i,1,q){
		cin>>x;
		if(!s.count(x)){
			s.insert(x);
		}
		else s.erase(x);
		for(auto it:s){
			if(it<=n) a[it]+=s.size();
		}
	}
	rep(i,1,n) cout<<a[i]<<" ";
}

我们思考如何优化这个O(n*n)的做法——也就是不需要每次更新set时遍历更新a[i]

很显然对于元素x,只有在它被插入set期间时才对答案有这期间s.size()和的贡献,而s.size()的和可以用前缀和p[i]做预处理,于是我们可以记录x在set里存在的下标,在循环外对于1~n的每个i成对地遍历它的下标l,r,答案a[i]=p[r-1]-p[l-1](遍历到r对应的下标时x已经被弹出set,对答案无贡献)

同时注意这些下标不一定成对,也就是有些数最后还存在于set里,对于这些数,它们最后一次加入集合的下标为l,此后对答案的贡献为最终的s.size()-p[l-1],而这个s.size()又等于p[q],所以我们可以给这些数的下标数组加上q+1,使得对于最后的l,也有成对的r=p[q]

对于4 6
1 2 3 2 4 2
每个x对应的s.size()为
1 2 3 2 3 2
前缀和为
1 3 6 8 11 13
则对于2这个数,它的下标为{2,4,6}
ans=p[4-1]-p[2-1]+p[7-1]-p[6-1]=8-1+13-13=7

代码

const int N=2e5+5;
int p[N],a[N];
vector<int>v[N];
void solve(){
	int n,q,x;cin>>n>>q;
	set<int>s;
	//vector<vector<int>>v;会re 
	rep(i,1,q){
		cin>>x;
		if(!s.count(x)){
			s.insert(x);
		}
		else s.erase(x);
		if(x<=n) v[x].push_back(i);//记录加入/弹出set时的下标
		p[i]=p[i-1]+s.size();//前缀和处理
	}
	for(auto i:s){//最后还存在set里的数
		if(i<=n) v[i].push_back(q+1);
	}
	rep(i,1,n){
		int sz=v[i].size();
		for(int j=0;j<sz;j+=2){//成对遍历下标
			int l=v[i][j],r=v[i][j+1];
			a[i]+=p[r-1]-p[l-1];
		}
		cout<<a[i]<<" ";
	}
}
posted @ 2024-04-05 17:32  mono_4  阅读(32)  评论(0编辑  收藏  举报