2022“杭电杯”中国大学生算法设计超级联赛(2)部分题题解

ShuanQ

这个题告诉我们没事别瞎用PR,还是可能会被卡掉的...
考虑M一定是P*Q-1的因子,并且是质数,考虑分解质因子。然后这个题就没了...
(没啥事别枚举因子,判断是不是质数....)

Luxury cruise ship

考虑一个数能不能用7,31,365拼成。可以发现365可以被7,31构成.(365=1131+27).所以我们考虑底层都是由7和31构成,之后我们尝试将他们成对的拿出,每次拿出11个31和2个7,这样就构成了365.那考虑拿到最后,只会剩下31和7凑不成一对的情况。可以发现这种情况并不多,并且凑不成一对的情况表示的数字很有限。所以我们可以直接预处理出来小数据的情况。大数据先用365去凑,之后再用我们处理的小数据即可。

Static Query on Tree

树剖,A,B,C分开考虑,打三个标记。不多说了(打了180多行,真NM...)

DOS Card

这个题又拓展了我对线段树区间可加性的认知。
考虑区间是否可加,我们发现答案只能是两种形式,+-+-和++--.
首先考虑第一种情况,我们+-+-划分,可以得知区间内需要维护的值有,两对的最大值,最大值,一对减去剩下的最小值,一对的最大值,一对的值加上剩下的最大值。
考虑++--的划分,有最大值,次大值,最小值,次小值,一对加上剩下的最大值,一对减上剩下的最小值。
综上所述:我们需要:
最大值,次大值,最小值,次小值,一对的最大值,二对的最大值,一对的值加上剩下最大值,一对的值减去剩下最小值。依次去更新即可。
友情提示:细节真NM的多。。。

点击查看代码
#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
using namespace std;
typedef long long ll;
const int N=2e5+10;
const ll INF=2e17;
int n,m,a[N];
struct Tree
{
	int l,r;
	ll mx,mxx,mn,mnn,yi,er,yia,yin;
	#define l(p) t[p].l
	#define r(p) t[p].r
	#define mx(p) t[p].mx
	#define mn(p) t[p].mn
	#define mxx(p) t[p].mxx
	#define mnn(p) t[p].mnn
	#define yi(p) t[p].yi
	#define er(p) t[p].er
	#define yia(p) t[p].yia
	#define yin(p) t[p].yin
	Tree friend operator +(Tree a,Tree b)
	{
		Tree c;
		c.l=a.l;c.r=b.r;
		ll A[4]={a.mx,a.mxx,b.mx,b.mxx};
		sort(A,A+4,[&](ll x,ll y){return x>y;});
		c.mx=A[0];c.mxx=A[1];
		ll B[4]={a.mn,a.mnn,b.mn,b.mnn};
		sort(B,B+4);
		c.mn=B[0];c.mnn=B[1];
		c.yi=max(max(a.yi,b.yi),a.mx-b.mn);
		c.er=max(max(a.er,b.er),a.mx+b.yin);	
		c.er=max(c.er,a.yia-b.mn);
		if(a.r-a.l>=1&&b.r-b.l>=1) c.er=max(c.er,a.mx+a.mxx-b.mn-b.mnn);
		c.er=max(c.er,a.yi+b.yi);
		c.yia=max(max(a.yia,b.yia),a.yi+b.mx);
		if(b.r-b.l>=1)c.yia=max(c.yia,a.mx+b.mx-b.mn);
		if(a.r-a.l>=1)c.yia=max(c.yia,a.mx+a.mxx-b.mn);
		c.yin=max(max(a.yin,b.yin),b.yi-a.mn);
		if(b.r-b.l>=1)c.yin=max(c.yin,a.mx-b.mn-b.mnn);
		if(a.r-a.l>=1)c.yin=max(c.yin,a.mx-a.mn-b.mn);
		return c; 
	}
}t[N<<2];
inline void build(int p,int l,int r)
{
	if(l==r)
	{
		l(p)=r(p)=l;
		mx(p)=mn(p)=(ll)a[l]*a[l];
		mxx(p)=-INF;
		mnn(p)=INF;
		yi(p)=-INF;
		er(p)=-INF;
		yia(p)=-INF;
		yin(p)=-INF;
		return;
	}
	int mid=l+r>>1;
	build(ls,l,mid);
	build(rs,mid+1,r);
	t[p]=t[ls]+t[rs];
}
inline Tree ask(int p,int l,int r)
{
	if(l<=l(p)&&r>=r(p)) return t[p];
	int mid=l(p)+r(p)>>1;
	if(r<=mid) return ask(ls,l,r);
	if(l>mid) return ask(rs,l,r);
	return ask(ls,l,r)+ask(rs,l,r);
}
int main()
{
//	freopen("1.in","r",stdin);
	int T;scanf("%d",&T);
	while(T--)
	{
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i) scanf("%d",&a[i]);
		build(1,1,n);
		for(int i=1;i<=m;++i)
		{
			int l,r;scanf("%d%d",&l,&r);
			printf("%lld\n",ask(1,l,r).er);
		} 	
	}
	return 0;
}

Copy

怎么说呢,这个题感觉思维性还是很有强度的。
首先我们特别要注意的是这道题的数据范围,可以发现每次操作的区间都是在n范围内。这提醒我们操作超过n时,可以直接舍弃。其次我们考虑异或的性质,两个相同的数异或答案是0,会抵消。说明我们只需要知道在原数列,每个数是否参与了异或即可。考虑操作1的过程,是将某个区间给分开了,这对我们统计某个数的奇偶有很大问题.我们尝试倒着看这个过程。正着看是分开,倒着看是合并。(对于一道题目而言,合并比分开简单).并且这个合并也简单,只需要将l+1到n的数向左移区间长度即可.我们用bitset去表示每一位参与异或的结果。倒着做这个过程。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e5+10;
int n,q,a[N];
bitset<N>ans;
struct wy{int l,r,op;}b[N];
inline void solve()
{
	ans.reset();
	scanf("%d%d",&n,&q);
	for(int i=1;i<=n;++i) scanf("%d",&a[i]);
	for(int i=1;i<=q;++i)
	{
		scanf("%d%d",&b[i].op,&b[i].l);
		if(b[i].op==1) scanf("%d",&b[i].r);
	} 
	for(int i=q;i>=1;--i)
	{
		int l=b[i].l,r=b[i].r;
		if(b[i].op==1)
		{
			auto a=ans&((~bitset<N>(0))>>(N-r-1));
			auto b=ans&((~bitset<N>(0))<<(r+1));
			ans=a^(b>>(r-l+1));
		}
		else ans[l]=ans[l]^1;
	}
	int res=0;
	for(int i=1;i<=n;++i) if(ans[i]) res^=a[i];
	printf("%d\n",res);
}
int main()
{
//	freopen("1.in","r",stdin);
	int T;scanf("%d",&T);
	while(T--) solve();
	return 0;
} 
posted @ 2022-07-21 20:00  逆天峰  阅读(164)  评论(0编辑  收藏  举报
作者:逆天峰
出处:https://www.cnblogs.com/gcfer//