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;
}