Codeforces Round #673 (Div. 1)

Posted C202044zxy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Codeforces Round #673 (Div. 1)相关的知识,希望对你有一定的参考价值。

B. Make Them Equal

题目描述

点此看题

解法

这道题需要发掘题目的特殊性质,你发现移动的石子必须是 \\(i\\) 的倍数,那么我们可以把所有石子移动到 \\(1\\),然后再分配,这整个过程的操作数不能超过 \\(3n\\)

我们是知道最后每个位置的石子有多少的,如果 \\(\\sum a_i\\%n\\not=0\\) 就直接无解。

现在的问题是把所有的石子移动到 \\(1\\),需要 \\(i\\) 位置上的石子数是 \\(i\\) 的倍数才可以全部移动,所以我们可能会先把 \\(1\\) 的石子移给他然后再全部拿回来。注意到一个重要条件:\\(a_i\\geq 1\\),并且模数是 \\(i\\),所以可以从左往右扫,扫到 \\(i\\) 的时候 \\(1\\)\\(i\\) 石子总和一定是够 \\(i\\) 的,所以就可以把所有石子移到 \\(1\\),步数消耗 \\(2n\\)

最后消耗 \\(n\\) 步自由分配即可。

#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int M = 300005;
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<\'0\' || c>\'9\') {if(c==\'-\') f=-1;}
	while(c>=\'0\' && c<=\'9\') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,m,a[M];
struct node
{
	int x,y,z;
};vector<node> b;
void work()
{
	n=read();m=0;b.clear();
	for(int i=1;i<=n;i++)
	{
		a[i]=read();
		m+=a[i];
	}
	if(m%n) {puts("-1");return ;}
	m/=n;
	for(int i=2;i<=n;i++)
	{
		if(a[i]%i)
			b.push_back(node{1,i,i-a[i]%i});
		b.push_back(node{i,1,(i+a[i]-1)/i});
	}
	for(int i=2;i<=n;i++)
		b.push_back(node{1,i,m});
	printf("%d\\n",b.size());
	for(int i=0;i<b.size();i++)
		printf("%d %d %d\\n",b[i].x,b[i].y,b[i].z);
}
signed main()
{
	T=read();
	while(T--) work();
}

C. XOR Inverse

题目描述

点此看题

解法

这道题又是异或又是逆序对的直接把我整傻了,但是不要被吓到,我们考虑两个数什么时候会成为逆序对

其实就是看是否满足 \\(a_i\\oplus x>a_j\\oplus x\\and i<j\\),既然是位运算我们用二进制的思维去考虑:最高位决定大小关系。设 \\(a_i,a_j\\) 第一个不同的数位是 \\(t\\),那么它们一定就会在 \\(x\\)\\(t\\) 这一位决出胜负,如果 \\(a_i\\) 这一位为 \\(1\\)\\(a_j\\) 这一位为 \\(0\\),那么 \\(x\\) 这一位为 \\(0\\) 的时候一定有逆序对否则一定没有逆序对。

那么每一对数是否产生逆序对就是在某一位上决定的,对于每一位是独立的,所以可以只要统计出这些数对第一个不同的数位,就可以逐位确定 \\(x\\) 了,统计的过程可以用 \\(\\tt trie\\) 树实现,时间复杂度 \\(O(n\\log n)\\)

#include <cstdio>
const int M = 300005;
const int N = 40*M;
#define ll long long
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<\'0\' || c>\'9\') {if(c==\'-\') f=-1;}
	while(c>=\'0\' && c<=\'9\') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int n,cnt,rt,jzm,a[M],ls[N],rs[N];ll zxy,s[N],b[31][2];
void ins(int d,int &x,int v)
{
	if(!x) x=++cnt;
	if(d==0)
	{
		s[x]++;
		return ;
	}
	if(v&(1<<(d-1)))
	{
		b[d-1][1]+=s[ls[x]];
		ins(d-1,rs[x],v);
	}
	else
	{
		b[d-1][0]+=s[rs[x]];
		ins(d-1,ls[x],v);
	}
	s[x]=s[ls[x]]+s[rs[x]];
}
signed main()
{
	n=read();
	for(int i=1;i<=n;i++)
	{
		int x=read();
		ins(30,rt,x);
	}
	for(int i=30;i>=0;i--)
	{
		if(b[i][0]<=b[i][1]) zxy+=b[i][0];
		else zxy+=b[i][1],jzm+=(1<<i);
	}
	printf("%lld %d\\n",zxy,jzm);
}

E. Split

题目描述

点此看题

解法

\\(\\tt div1\\) 的神题,让我重新认识了什么叫 \\(dp\\) 优化。

首先设计暴力 \\(dp\\),设 \\(dp[i][j]\\) 表示考虑 \\(a_1\\sim a_i\\),结尾的 \\(b_{2i}\\)\\(j\\),最大相邻相同的数对有多少个(最后减一下得到答案),这样做复杂度直接上天。这时候普通的竞赛选手(比如我)可能就去想别的状态定义了,好像这东西也优化不动啊。但现在不妨观察一下这个状态转移的特点

  • \\(dp\\) 的过程是分层的,我们把 \\(dp[i]\\) 这一层称为 \\(cur\\)\\(dp[i+1]\\) 这一层称为 \\(nxt\\),令 \\(x=a_{i+1}\\)
  • \\(nxt[i]\\geq \\max(cur)\\),因为总可以找到一种方法从上一层的最大值转移过来。
  • \\(\\max(nxt)-\\min(nxt)\\leq 2\\),因为每次转移最多增加两对相邻相同数,这是本题的关键,能简化情况。
  • \\(\\max(nxt)-\\min(nxt)=2\\) 要求 \\(a_i\\) 为偶,并且 \\(nxt[a_i/2]\\) 是这层 \\(dp\\) 值中唯一的最大值。
  • 对于某些 \\(i\\) 我们能找到 \\(cur[x-i]+1\\) 转移到 \\(nxt[i]\\) 的方法。
  • 如果 \\(x\\) 是偶数,与其在转移是分开讨论他,不如在转移结束之后给 \\(nxt[x/2]\\)\\(1\\)
  • 虽然每一层的转移 \\(dp\\) 值会有差异,但是经过一次转移之后所有 \\(dp\\) 值都会到一个新的基准值

根据上面的观察,我们可以维护下面的东西来代替 \\(dp\\) 过程:

  • \\(zero\\) 表示这一层所有 \\(dp\\) 值的基准线,也就是这一层的最小值。
  • \\(one\\) 表示一个集合维护满足下列条件的下标 \\(i\\)\\(cur[i]=zero+1\\)
  • \\(two\\) 表示这一层值为 \\(zero+2\\) 的下标,如果没有则为 \\(-1\\),有则为 \\(a_i/2\\)

现在考虑如何维护。第一种情况,上一层存在 \\(two\\),那么这一层的基准一定选他了(注意这里是不考虑 \\(x/2\\) 多出来的贡献 \\(1\\) 才能这么贪心),直接清空上一层维护的 \\(one\\),看 \\(x-two\\) 能不能加入 \\(one\\) 里面。

第三种情况,\\(two,one\\) 都不存在,那么可以把 \\([1,\\min(a_i,x)-1]\\) 加入 \\(one\\) 中,因为这里我们是加入的一整段所以我们选择以区间的形式来维护 \\(one\\),看能不能保证复杂度。

第二种情况,上一层 \\(one\\) 非空,那么这一层的基准线一定选他了。然后要考虑这一层的 \\(one\\) 是怎么样的,上一层的元素 \\(A\\) 到这一层就变成了 \\(x-A\\),所以我们要求 \\(A\\leq x\\),也就是我们要删除一段后缀。但是 \\(A\\rightarrow x-A\\) 这个类似以 \\(x\\) 为轴的旋转操作有点难搞,可以考虑懒惰的旋转,手玩一下发现规律是 \\(A,x-A,y-x+A,z-y+x-A...\\),可以维护旋转值 \\(shift\\)\\(A\\) 前面的符号 \\(sign\\),每次 \\(shift=shift-x,sign\\times-1\\),那么每次就要根据 \\(sign\\) 的正负判断删除的是前缀还是后缀。

然后特殊考虑 \\(x/2\\) 这一位,首先从 \\(one\\) 里面去找他,如果找到了就是 \\(two\\),如果找不到就把他加入 \\(one\\) 中。

#include <cstdio>
#include <iostream>
#include <set>
using namespace std;
const int M = 500005;
#define make make_pair
#define int long long
#define pii pair<int,int>
#define fi first
#define se second
int read()
{
	int x=0,f=1;char c;
	while((c=getchar())<\'0\' || c>\'9\') {if(c==\'-\') f=-1;}
	while(c>=\'0\' && c<=\'9\') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
	return x*f;
}
int T,n,a[M];
void work()
{
	n=read();
	set<pii> one;set<pii>::iterator it;
	int ze=0,two=-1,sh=0,f=1,ans=2*n;
	for(int i=1;i<=n;i++)
	{
		int x=read();a[i]=x;
		if(two!=-1)
		{
			one.clear();sh=0;f=1;ze+=2;//based on TWO
			if(x>two) one.insert(make(x-two,x-two));
		}
		else if(!one.empty())
		{
			ze++;//based on ONE
			if(f==-1)
			{
				//sh-idx>=x -> idx<=sh-x is illegal
				int lim=sh-x;
				while(!one.empty() && one.begin()->se<=lim)
					one.erase(one.begin());//delete the whole segment
				if(!one.empty() && one.begin()->fi<=lim)//delete the part of the segment
				{
					it=one.begin();int tmp=it->se;
					one.erase(it);
					one.insert(make(lim+1,tmp));
				} 
			}
			else
			{
				//sh+idx>=x -> idx>=x-sh is illegal
				int lim=x-sh;
				while(!one.empty() && one.rbegin()->fi>=lim)
					one.erase(--one.end());
				if(!one.empty() && one.rbegin()->se>=lim)
				{
					int tmp=one.rbegin()->fi;
					one.erase(--one.end());
					one.insert(make(tmp,lim-1));
				}
			}
			sh=x-sh;
			f*=-1;//we rotate lazily
		}
		else//every element is the same
		{
			f=-1;sh=x;
			int lim=min(a[i-1]-1,x-1);
			if(1<=lim) one.insert(make(1,lim));
		}
		//here consider x/2
		two=-1;
		if(x%2==0)
		{
			int y=x/2,val=(y-sh)/f,pd=0;//we reflect the value in ONE
			it=one.lower_bound(make(val+1,(int)-1e18));//attention
			if(it!=one.begin())
			{
				it--;
				if(it->fi<=val && val<=it->se) pd=1;
			}
			if(pd) two=x/2;//we find TWO
			else one.insert(make(val,val));//otherwise add it into ONE
		}
	}
	if(two!=-1) ans-=ze+2;
	else if(!one.empty()) ans-=ze+1;
	else ans-=ze;
	printf("%lld\\n",ans);
}
signed main()
{
	T=read();
	while(T--) work();
}

以上是关于Codeforces Round #673 (Div. 1)的主要内容,如果未能解决你的问题,请参考以下文章

Namomo Spring Camp 2022 Div1 XOR Inverse Codeforces Round #673 (Div. 1) C. XOR Inverse 按位贪心模拟/字典树分治

Namomo Spring Camp 2022 Div1 XOR Inverse Codeforces Round #673 (Div. 1) C. XOR Inverse 按位贪心模拟/字典树分治

Codeforces Round #436 E. Fire(背包dp+输出路径)

[ACM]Codeforces Round #534 (Div. 2)

Codeforces Round #726 (Div. 2) B. Bad Boy(贪心)

Codeforces Global Round 19