Date:2022.01.12
题意:长度为n的序列,每次可将当前序列分为两半,规则:mid=(a[max]+a[min])/2mid=(a[max]+a[min])/2mid=(a[max]+a[min])/2,所有<=mid的放到左边一类,>mid的放到右边一边,每次永久舍弃一边。m个询问,问能不能找到和为当前值的,能输出Yes,不能No。

思路:求所有可能的和,先处理前缀和。因为每次都最多选<=mid或>mid的一半,因此估算期望大概lognlognlogn层,自然考虑打表。每次二分查找出<=mid的最后一个位置,以此为界分左右两边,把两部分的和都加入set中,再由此递归约logn层即可求出所有和的组合加入set中。除此之外注意递归的弹出条件为a[max]==a[min]a[max]==a[min]a[max]==a[min],无论如何这组和都没法再分,(举个例子:当前3个元素为1、1、1,那么mid=1,二分查找总会找到最后一个1,导致死循环)否则会死循环。
代码如下:
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+10;
typedef long long LL;
LL n,m,t;
LL a[N],sum[N];
set<LL>s;
void init(LL l,LL r)
{
if(a[l]==a[r]) {s.insert(sum[r+1]-sum[l]);return;}
LL x=(a[l]+a[r])/2;
LL mid=upper_bound(a,a+n,x)-1-a;
s.insert(sum[mid+1]-sum[l]);init(l,mid);
s.insert(sum[r+1]-sum[mid+1]);init(mid+1,r);
}
int main()
{
ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
cin>>t;
while(t--)
{
s.clear();
memset(sum,0,sizeof sum);
cin>>n>>m;
for(int i=0;i<n;i++) {cin>>a[i];}
sort(a,a+n);
for(int i=1;i<=n;i++) {sum[i]=sum[i-1]+a[i-1];}
init(0,n-1);s.insert(sum[n]);
//for(auto it:s) cout<<it<<' ';
//cout<<endl;
while (m -- )
{
LL q;cin>>q;
if(s.count(q)) cout<<"Yes"<<endl;
else cout<<"No"<<endl;
}
}
return 0;
}

本文介绍了一种通过递归地将序列分成两半,并利用前缀和来计算所有可能的子序列之和的方法。该方法使用二分查找确定分割点,并通过递归遍历所有可能的和,最终解决询问是否能找到特定和的问题。
500

被折叠的 条评论
为什么被折叠?



