2023NOIPA层联测13(ACCODER414)

2023NOIPA层联测13(ACCODER414)

2023年10月17日 / 信息学,比赛,2023NOIPA层联测

题目链接

异或帽子

注意到 n n n为偶数,考虑求 s s s表示 b b b的异或和,因为每个人看不到自己的 a a a,所以其的 a a a一定在在 s s s中被计算了奇数次,对于异或,其实就是一次。所以 s s s就是所有人 a a a的异或和,那么每个人的 a = s ⊕ b a=s \oplus b a=sb

#include
using namespace std;

const int maxn=2*1e5;

int n;
int a[maxn+5];
int sum;

signed main()
{
	freopen("hat.in","r",stdin);
	freopen("hat.out","w",stdout);
	
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]),sum^=a[i];
	for(int i=1;i<=n;i++)
		printf("%d ",sum^a[i]);
	
	return 0;
}

传话游戏

考虑动态规划,设 f [ i ] [ 0 / 1 ] f[i][0/1] f[i][0/1]表示第 i i i个单词与/不与前面的交换。

有转移:

  • f [ i ] [ 0 ] = f [ i − 1 ] [ 0 ] + f [ i − 1 ] [ 1 ] f[i][0]=f[i-1][0]+f[i-1][1] f[i][0]=f[i1][0]+f[i1][1]
  • f [ i ] [ 1 ] = f [ i − 2 ] [ 0 ] + f [ i − 2 ] [ 1 ] f[i][1]=f[i-2][0]+f[i-2][1] f[i][1]=f[i2][0]+f[i2][1]

初始值:

f [ 0 ] [ 0 ] = f [ 1 ] [ 0 ] = 1 f[0][0]=f[1][0]=1 f[0][0]=f[1][0]=1

#include
using namespace std;

#define int long long 
const int maxn=1e5;
const int maxm=2*1e6;
const int mod=1e9+7;

int n,m;
string s[maxn+5];
int f[maxn+5][2];

signed main()
{
	freopen("string.in","r",stdin);
	freopen("string.out","w",stdout);
	
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
		cin>>s[i];
	
	f[0][0]=f[1][0]=1;
	for(int i=2;i<=n;i++)
	{
		f[i][0]=(f[i-1][0]+f[i-1][1])%mod;
		if(s[i]!=s[i-1])
			f[i][1]=(f[i-2][0]+f[i-2][1])%mod;
	}
	
	printf("%lld",(f[n][0]+f[n][1])%mod);
	
	return 0;
}

全球覆盖

洛谷:P5987

首先显然易见的是横纵坐标完全可以分开考虑(因为横坐标对纵坐标没有影响),只需要分别对横纵求答案,然后相乘就是最后的答案。

接着问题转化成 [ L , R ] [L,R] [L,R] [ 0 , L ) , ( R , X ] [0,L),(R,X] [0,L),(R,X]选一个最后求交集最大的问题。

我们将所有的 L , R L,R L,R放在一起排个序,相邻的两个点会分割若干条线段(区间),其中每一条线段对于每一对 L , R L,R L,R选择(也就是选$ [ L , R ] [L,R] [L,R] [ 0 , L ) , ( R , X ] [0,L),(R,X] [0,L),(R,X])是固定的。所以我们可以得到每一条线段的需求。

那么需求相同的线段就可以同时取到,对答案的贡献也就是他们长度的和。

问题是如何记录需求。我们给 [ L i , R i ] [L_i,R_i] [Li,Ri]这一选择一个特殊数 p i ∈ [ 0 , 2 64 ) p_i \in [0,2^{64}) pi[0,264)(随机即可,注意获取随机数的方式,因为这会影响随机数的范围), [ 0 , L i ) , ( R i , X ] [0,L_i),(R_i,X] [0,Li),(Ri,X]这一个选择记为 0 0 0。接着我们发现对于 [ L i , R i ] [L_i,R_i] [Li,Ri]之间的线段,该区间对其的贡献都为 p i p_i pi而其他的都为 0 0 0,所以使用一个异或差分(实现参考代码)即可解决问题。再将一个线段对应的选择求异或和就可以代表他的需求。

为什么这是对的?因为我们做的是异或运算所以需求 q i ∈ [ 0 , 2 64 ) q_i \in [0,2^{64}) qi[0,264)并且是随机分布的。我们可以使用生日悖论来计算正确的概率。

可以将其转换成有 m = 2 n + 1 m=2n+1 m=2n+1个小朋友,在 2 64 2^{64} 264天一年的情况下,所有小朋友生日不同的概率(记 a = 2 64 a=2^{64} a=264)。

P = a ! a m ( a − m ) ! > a − m a P=\frac{a!}{a^m(a-m)!}>\frac{a-m}{a} P=am(am)!a!>aam

极限数据下后者约等于 0.9999999999999457898913757247783 0.9999999999999457898913757247783 0.9999999999999457898913757247783,而 P P P更要大于此,可以说是非常优秀了。

#include
using namespace std;

#define ll long long 
#define ull unsigned long long
#define pii pair<int,int>
#define piu pair<int,ull>
#define fi first
#define se second
const int maxn=5*1e5;

int n,X,Y;
pii a[maxn+5],b[maxn+5];

vector<piu> E,S;
mt19937_64 rnd(random_device{}());

inline bool CmpE(piu x,piu y){return x.fi<y.fi;}
inline bool CmpS(piu x,piu y){return x.se<y.se;}

inline int Solve(pii f[],int val)
{
	ull tmp;
	E.clear();
	for(int i=1;i<=n;i++)
	{
		tmp=rnd();
		E.push_back(piu(f[i].fi,tmp));
		E.push_back(piu(f[i].se,tmp));
	}
	E.push_back(piu(0,0));
	E.push_back(piu(val,0));
	sort(E.begin(),E.end(),CmpE);
	
	tmp=0;
	S.clear();
	for(int i=0,vtp=E.size();i<vtp-1;i++)
	{
		tmp^=E[i].se;
		if(E[i].fi<E[i+1].fi)
			S.push_back(piu(E[i+1].fi-E[i].fi,tmp));
	}
	sort(S.begin(),S.end(),CmpS);
	
	tmp=0;
	int sum=0,res=0;
	for(int i=0,vtp=S.size();i<vtp;i++)
	{
		if(tmp==S[i].se)
			sum+=S[i].fi;
		else
			tmp=S[i].se,sum=S[i].fi;
		res=max(res,sum);
	}
	return res;
}

signed main()
{
	freopen("globe.in","r",stdin);
	freopen("globe.out","w",stdout);
	
	int x,xx,y,yy;
	scanf("%d%d%d",&n,&X,&Y);
	for(int i=1;i<=n;i++)
	{
		scanf("%d%d%d%d",&x,&y,&xx,&yy);
		if(x>xx) swap(x,xx);
		if(y>yy) swap(y,yy);
		a[i]=pii(x,xx);
		b[i]=pii(y,yy);
	}
	
	printf("%lld",1ll*Solve(a,X)*Solve(b,Y));
	
	return 0;
}

幂次序列

正解需要笛卡尔树(正在学),这里提供一个非正解。

考虑一个拼盘,分极差(最大值减最小值)小于 30 30 30和大于两种情况。

小于

因为极差小于 30 30 30,所以我们把每一个 a a a减去最小值,然后暴力枚举 i , j i,j i,j表示考虑到第 i i i位,并维护一个前缀和,最终答案为 2 j 2^j 2j,我们直接在set里搜索 s u m − 2 j sum-2^j sum2j即可。

大于

我们同样先枚举 i i i,然后枚举一个 j j j i i i往前搜索。然后我们像 50 p t s 50pts 50pts暴力一样做,每次暴力插入,并使用set维护幂集合(参见代码)。现在,考虑减枝。

首先,如果集合内的极差减去大小已经大于 j j j那么接下来的枚举就没有必要了,因为无论怎么插入,都不可能归成一个数。

时间复杂度无法分析,对于没有特殊构造的数据能过。

#include
using namespace std;

#define int long long 
const int maxn=2*1e5;
const int maxm=100;

int n;
int a[maxn+5];
int ans;

set<int> se;
set<int>::iterator it;
int x;

int mx=INT_MIN,mn=INT_MAX;

inline void add(int x)
{
	while(true)
	{
		it=se.find(x);
		if(it==se.end())
		{
			se.insert(x);
			break;
		}
		else
			se.erase(it),x++;
	}
}
inline void Work1()
{
	se.insert(0);
	for(int i=1;i<=n;i++) a[i]-=mn;
	int sum=0;
	for(int i=1;i<=n;i++)
	{
		sum+=(1<<a[i]);
		for(int j=1;j<=sum;j<<=1)
			ans+=se.count(sum-j);
		se.insert(sum);
	}
}
inline void Work2()
{
	for(int i=n;i>=1;i--)
	{
		se.clear();
		for(int j=i;j>=1;j--)
		{
			add(a[j]);
			if(se.size()==1)
				ans++;
			if(*se.rbegin()-*se.begin()-(int)se.size()>j)
				break;
		}
	}
}

signed main()
{
	freopen("sequence.in","r",stdin);
	freopen("sequence.out","w",stdout);
	
	scanf("%lld",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%lld",&a[i]);
		mx=max(mx,a[i]);
		mn=min(mn,a[i]);
	}
	
	if(mx-mn<=30) Work1();
	else Work2();
	
	printf("%lld",ans);
	
	return 0;
}

你可能感兴趣的:(信息学,比赛,算法,c++)