CSP模拟16
CSP模拟16
T1 糖果
题意:把 \(n\) 个数分成若干段,每段异或和相等,段数大于 \(1\) ,问能否分成。
我们从第一个数开始求数列的异或和,因为 \(n\) 大于 \(1\) ,两个相等的数异或和为 \(0\) ,数列异或和有两种情况:
-
数列异或和等于 \(0\) ,一定可以分成偶数段,符合题意。
-
数列异或和不为 \(0\)。
对于第二种情况,我们设数列异或和为 \(x\) ,异或和为 \(x\) 数列的数量为 \(tot\) ,当前异或和为 \(an\) ,我们从头开始遍历,出现一个 \(x\) 就把 \(tot\) 加一,把 \(an\) 设为零。这样就能统计出异或和为 \(x\) 的数列段数。
若段数大于 \(1\) 就符合题意。如图,空白处异或和一定为 \(0\) ,可以并入前一段数列。
code
#include<iostream>
#include<cstdio>
using namespace std;
int n,a[100010];
void work(){
scanf("%d",&n);
int ans=0;
for(int i=1;i<=n;i++){
scanf("%d",&a[i]);
ans^=a[i];
}
if(n==1){
printf("NO\n");
return;
}
if(ans==0){
printf("YES\n");
return;
}
int an=0,tot=0;
for(int i=1;i<=n;i++){
an^=a[i];
if(an==ans){
tot++;
an=0;
}
}
if(tot>1){
printf("YES\n");
return;
}
printf("NO\n");
return ;
}
int main(){
int t;
scanf("%d",&t);
while(t--){work();}
return 0;
}
T2 魔法仪式
题意:问一段数列中有多少个区间去掉区间最大值后的区间和为 \(k\) 的倍数。
我们可以二分中间值,分成两种情况考虑,一种是最大值在中间值左边,一种是最大值在中间值右边。统计非最大值的一边的区间和 % \(k\) 的值。左右两边对 \(k\) 的模的和模 \(k\)一定为 \(0\)。
另外我们需要注意两种情况中只有一种可以让非最大值的那一边也出现最大值,不然会重复计算。这就是你只有30分的原因
code
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
using namespace std;
#define int long long
int n,k,a[300010],ans,tmp[3000010];
void work(int l,int r){
if(l==r) return;
int mid=(l+r)>>1;
work(l,mid);
work(mid+1,r);
int ma=0,y=mid,num=0,nm2=0;
//memset(tmp,0,sizeof(tmp));
queue<int>q;
for(int i=mid;i>=l;i--){
ma=max(ma,a[i]);
num+=a[i];
while(a[y+1]<=ma&&y<r){
y++;
nm2+=a[y];
tmp[nm2%k]++;
q.push(nm2%k);
}
ans+=tmp[(k-((num-ma)%k))%k];
}
ma=0;int x=mid+1;num=0;nm2=0;
while(!q.empty()){
tmp[q.front()]=0;
q.pop();
}
//memset(tmp,0,sizeof(tmp));
for(int i=mid+1;i<=r;i++){
ma=max(ma,a[i]);
num+=a[i];
while(a[x-1]<ma&&x>l){
x--;
nm2+=a[x];
tmp[nm2%k]++;
q.push(nm2%k);
//cout<<nm2%k<<" ";
}
//cout<<num-ma<<"\n";
ans+=tmp[(k-((num-ma)%k))%k];
}
while(!q.empty()){
tmp[q.front()]=0;
q.pop();
}
}
signed main(){
scanf("%lld%lld",&n,&k);
for(int i=1;i<=n;i++){
scanf("%lld",&a[i]);
}
work(1,n);
printf("%lld",ans);
return 0;
}