Codeforces Round 880 (Div. 2)

Preface

补题

最近被太阳晒得全身发痒,而且手臂还红的要死,不过每天有大把时间可以用来想题写题还是很爽的

这场的题目就突出一个诡异,DE都是只可意会不可言传的做法,给闪总人干麻了


A. Destroyer

\(num_i\)表述数\(i\)出现的次数,则必须对所有的\(i\)都要满足\(num_i\ge num_{i+1}\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=105;
int t,n,x,num[N];
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (i=0;i<100;++i) num[i]=0;
		for (scanf("%d",&n),i=1;i<=n;++i) scanf("%d",&x),++num[x];
		bool flag=1; for (i=0;i<100&&flag;++i) if (num[i]<num[i+1]) flag=0;
		puts(flag?"YES":"NO");
	}
	return 0;
}

B. Astrophysicists

很显然的贪心,我们先令\(t=\lceil\frac{g}{2}\rceil-1\),如果\(k\times g\le t\times n\)则显然可以把所有钱都赖掉

否则给前\(n-1\)个人都发\(t\)的钱,然后剩下的都给最后一个人即可,不难证明这样一定是最优的

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,n,k,g;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld%lld",&n,&k,&g); int r=(g+1)/2-1;
		if (k*g<=r*n) { printf("%lld\n",k*g); continue; }
		int ret=r*(n-1),left=k*g-(n-1)*r; left%=g;
		printf("%lld\n",(n-1)*r+(left<=r?left:left-g));
	}
	return 0;
}

C. k-th equality

\([L_x,R_x]\)为位数为\(x\)的数的范围,则显然根据字典序我们从小到大枚举\(a\in[L_A,L_B]\)

然后考虑合法的\(b,c\)取值有多少种,根据\(C\)的取值范围\([L_C,R_C]\)我们可以推出\(b\)的取值要在\([L_C-a,R_C-a]\)

同时显然\(b\)也必须在\([L_B,R_B]\)内,则我们把两个区间做交集就可以得出\(b\)合法的取值方案数了,此时\(c\)也唯一确定且一定合法

复杂度\(O(10^A)\)

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int L[7]={0,1,10,100,1000,10000,100000},R[7]={0,9,99,999,9999,99999,999999};
int t,k,A,B,C;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%lld",&t);t;--t)
	{
		scanf("%lld%lld%lld%lld",&A,&B,&C,&k); bool flag=0;
		for (RI a=L[A];a<=R[A];++a)
		{
			int lb=max(L[C]-a,L[B]),rb=min(R[C]-a,R[B]);
			if (lb<=rb)
			{
				if (k<=rb-lb+1)
				{
					flag=1; int b=lb+k-1,c=a+b;
					printf("%lld + %lld = %lld\n",a,b,c); break;
				} else k-=rb-lb+1;
			}
		}
		if (!flag) puts("-1");
	}
	return 0;
}

D. Lottery

好玄妙的题,想了半天没啥思路后面去看Tutorial感觉也没咋看懂,但大概可以意会一下结论

这题的结论就是所有可能的答案取值一定在\(\{a_i+j\}\),其中\(j\in[-2,2]\)

具体证明可以看上面的官方题解,里面配了张图很适合感性理解

然后具体实现的话我刚开始写了个枚举答案\(pos\),则不难发现答案一定是包含\(pos\)的某个区间

那么可以二分向左和向右的量来得到最后的区间,总复杂度是\(O(5\times n\times (\log m+\log n))\)

但写了之后发现根本跑不过去,因为\(m\)的范围很大并且二分还要跑两次,直接GG

后面一想其实可以直接双指针检验,区间的移动是跟着枚举的答案一起移动的,就可以把后面这部分做到线性了(前面排序的一个\(\log\)还是得带)

具体实现有挺多细节的,需要注意的说

#include<cstdio>
#include<iostream>
#include<vector>
#include<utility>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=1000005;
int n,m,k,a[N],mx,pos; vector <int> v;
signed main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	if (scanf("%lld%lld%lld",&n,&m,&k),k>n) return printf("%lld 0",m+1,0),0;
	RI i,j; for (v.push_back(0),v.push_back(m),i=1;i<=n;++i)
	for (scanf("%lld",&a[i]),j=-2;j<=2;++j) if (0<=a[i]+j&&a[i]+j<=m) v.push_back(a[i]+j);
	sort(v.begin(),v.end()); v.erase(unique(v.begin(),v.end()),v.end());
	sort(a+1,a+n+1); mx=0; j=1;
	for (int x:v)
	{
		while (j<=n&&a[j]<x) ++j;
		int l=0,r=m; int lst=j,kk=k;
		while (j<=n&&a[j]==x) ++j,--kk;
		if (kk<=0) continue;
		if (lst-kk>=1)
		{
			l=(a[lst-kk]+x)/2; while (l-a[lst-kk]<=x-l) ++l;
		}
		if (j+kk-1<=n)
		{
			r=(a[j+kk-1]+x+1)/2; while (a[j+kk-1]-r<=r-x) --r;
		}
		if (r-l+1>mx) mx=r-l+1,pos=x;
	}
	return printf("%lld %lld",mx,pos),0;
}

E. Twin Clusters

首先我们要发现一个重要结论,所谓的区间不交其实是假的

因为假设我们找到了两个区间\([l_1,r_1],[l_2,r_2]\)满足它们的异或和相同,则分情况考虑:

  • 两个区间部分相交,此时把两个区间都减去其公共部分,剩下两个区间的异或和也一定相同
  • 一个区间包含另一个区间,则把大的区间减去小的区间,剩下的两个区间(如果存在的话)的异或和也一定相同

注意当出现例如\(l_1=l_2,r1<r2\)这种情况是没法处理的,不过这种情况出现的概率很低可以忽略

然后回到这题,刚开始想这题的本质其实就是在异或前缀和数组\(pfx\)中找出四个数使得它们异或和为\(0\)

然后和徐神讨论了下感觉连选三个数的情况都不太好做,四个数的情况更是难搞

那这道题该怎么处理呢,考虑这道题给出数据的方式,它的长度和数的值域是有关联的

我们发现区间的总数大概是\(\frac{n(n-1)}{2}=2^{2k+1}\),而值域只有\(2^{2k}\),由抽屉原理此时一定有解

并且根据生日悖论我们可以大胆猜测,我们每次随机地取一个区间并记录下异或和,如果出现和之前重复的就直接输出(注意上面讲的一种特殊情况)

这个做法的正确性是有保障的,具体可以自己写个代码跑一下当值域为\(4^k\)时,只需要不多的操作撞车的概率就很高了

最后也是700ms跑过,这题应该是有确定性做法的但还没想出来,先放着再说吧

#include<cstdio>
#include<iostream>
#include<random>
#include<unordered_map>
#include<utility>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=(1<<18)+5;
int t,k,n; long long a[N],pfx[N]; unordered_map <long long,pi> rst;
int main()
{
	//freopen("CODE.in","r",stdin); freopen("CODE.out","w",stdout);
	for (scanf("%d",&t);t;--t)
	{
		RI i; for (scanf("%d",&k),n=1<<k+1,i=1;i<=n;++i)
		scanf("%lld",&a[i]),pfx[i]=pfx[i-1]^a[i];
		mt19937_64 rnd(random_device{}());
		uniform_int_distribution<int> dist(1,n);
		for (rst.clear();;)
		{
			int l=dist(rnd),r=dist(rnd);
			if (l>r) swap(l,r); long long x=pfx[r]^pfx[l-1];
			if (rst.count(x))
			{
				int L=rst[x].first,R=rst[x].second,ll=max(l,L),rr=min(r,R);
				if (ll>rr)
				{
					printf("%d %d %d %d\n",l,r,L,R); break;
				} else
				if (ll==l&&rr==r)
				{
					if (L<=ll-1&&rr+1<=R)
					{
						printf("%d %d %d %d\n",L,ll-1,rr+1,R); break;
					}
				} else
				if (ll==L&&rr==R)
				{
					if (l<=ll-1&&rr+1<=r)
					{
						printf("%d %d %d %d\n",l,ll-1,rr+1,r); break;
					}
				} else
				{
					if (ll==l) l=rr+1; else r=ll-1;
					if (ll==L) L=rr+1; else R=ll-1;
					if (l<=r&&L<=R)
					{
						printf("%d %d %d %d\n",l,r,L,R); break;
					}
				}
			}
			rst[x]=pi(l,r);
		}
	}
	return 0;
}

Postscript

这样看来之前期末复习欠下的CF马上就可以补完了,这样就不用拖着尾巴进行集训了,舒服的捏

posted @ 2023-07-05 22:57  空気力学の詩  阅读(75)  评论(0编辑  收藏  举报