2025牛客寒假算法基础集训营补题
2025牛客寒假算法基础集训营1
H 井然有序之窗
做法:贪心
先把区间按左端点排序,对于数 \(i\) 该放在哪个位置,则将所有左端点小于 \(i\) 的区间放入优先队列,取出右端点最小的区间为 \(i\) 放的位置。
无解情况:当该右端点最小的区间的右端点小于 \(i\) 时无解,当区间未放满时无解。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005;
LL n,m,t,cnt=1,l=1,ans[N];
struct node{
LL l,r,i;
bool operator < (const node& a) const {
return r>a.r;
}
}a[N],b;
priority_queue<node> q;
inline LL read(){
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
bool cmp(node a, node b){
if(a.l==b.l) return a.r-a.l<b.r-b.l;
else return a.l<b.l;
}
int main(){
n=read();
for(int i=1;i<=n;i++){
a[i].l=read(),a[i].r=read(),a[i].i=i;
}
sort(a+1,a+1+n,cmp);
while(a[cnt].l<=l&&cnt<=n){
q.push(a[cnt++]);
}
while(!q.empty()){
b=q.top();q.pop();
if(b.r<l){
printf("-1\n");return 0;
}
ans[b.i]=l;
l++;
while(a[cnt].l<=l&&cnt<=n){
q.push(a[cnt++]);
}
}
if(cnt<=n){
printf("-1\n");
}else{
for(int i=1;i<=n;i++){
printf("%lld ",ans[i]);
}
}
return 0;
}
F 双生双宿之探
做法:双指针、前缀和
因为一个合法区间只含有两个数,故可将其变为 1 与 -1
| 1 | 4 | 1 | 1 | 4 | 3 | 3 | 4 | 2 | 2 |
|---|---|---|---|---|---|---|---|---|---|
| 1 | -1 | 1 | 1 | -1 | 1 | 1 | -1 | 1 | 1 |
之后使用前缀和 \(pre\) ,再用双指针移动,对于此时区间数种类记为 \(dist\) ,因为当且仅当 \(pre[r]-pre[l-1]=0\) 时该区间为双生数组,故统计 \(cnt[pre[l]]\) 的数量,在确定一个只含有2个数的长区间时,\(ans+=cnt[pre[r]]\) 。
#include<bits/stdc++.h>
#define LL long long
using namespace std;
const int N=100005;
LL n,m,t;
inline LL read(){
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
void solve(){
n=read();
vector<LL> a(n),v(n),f(n+1),k(n);
for(int i=0;i<n;i++){
v[i]=a[i]=read();
}
sort(v.begin(),v.end());
unique(v.begin(),v.end());
for(int i=0;i<n;i++){
a[i]=lower_bound(v.begin(),v.end(),a[i])-v.begin();
}//离散化,用桶统计元素种类
f[0]=1;
for(int i=1;i<n;i++){
if(a[i]==a[i-1]){
f[i]=f[i-1];
}else{
f[i]=-f[i-1];
}
}
LL dist=0,ans=0;
vector<LL> pre(n+1),cnt(2*n+1);
pre[0]=n;
for(int i=1;i<n;i++){
pre[i+1]=pre[i]+f[i];
}//前缀和加n,方便计数
for(int l=0,r=1;r<=n;r++){
cnt[pre[r-1]]++;
dist+=(k[a[r-1]]++==0);
while(dist>2){
cnt[pre[l]]--;
dist-=(k[a[l]]--==0);
l++;
}
ans+=cnt[pre[r]];
}
printf("%lld\n",ans);
return ;
}
int main(){
t=read();
while(t--){
solve();
}
return 0;
}
K 硝基甲苯之魇
做法:数论,线段树,ST表,二分,map
对于区间 gcd 有这样一个性质,则当左端点 \(l\) 确定时,右端点 \(r\) 从左向右移动时 gcd 大小是递减的。
| 20 | 8 | 4 | 6 | 2 | 3 | 5 | 6 | 7 | 5 | a[] |
|---|---|---|---|---|---|---|---|---|---|---|
| 20 | 4 | 4 | 2 | 2 | 1 | 1 | 1 | 1 | 1 | gcd |
所以当 \(l\) 固定时我们可以二分找到 gcd 的分界点,可以使用线段树或ST表来算出区间 gcd ,这样再找与该区间 gcd 相等的区间异或和。
对于区间异或和,我们可以通过异或前缀和来计算。
\(f_{l,r}=pre[r] \otimes pre[l-1]\)
在查找这一段区间 gcd 有没有相等的区间异或和时。
\(pre[r] \otimes pre[l-1]=gcd_{l,r}\)
$\downarrow $
\(gcd_{l,r} \otimes pre[l-1]=pre[r]\)
因为 \(l\) 是固定的,所以我们只需要找到这段区间的异或前缀和等于 \(pre[r]\) 的个数。
我们可以通过 \(map\) 套 \(vector\) 来计算个数,将异或前缀和作为 \(key\) , 将位置 \(i\) 作为值放入 \(vector\) 中,之后通过二分查找位置再相减。
#include<bits/stdc++.h>
#define LL int
#define l(p) tree[p].l
#define r(p) tree[p].r
#define gd(p) tree[p].gd
using namespace std;
const int N=200005;
LL gcd(LL a,LL b);
LL n,m,t,k,a[N];
struct Segment_tree{
LL l,r,gd;
}tree[N*4];
inline LL read(){
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
LL gcd(LL a,LL b){
return b?gcd(b,a%b):a;
}
void pushup(LL p){
gd(p)=gcd(gd(p<<1),gd(p<<1|1));
return ;
}
void build(LL p,LL l,LL r){
l(p)=l,r(p)=r;
if(l==r){
gd(p)=a[l];
return ;
}
LL mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
LL query(LL p,LL l,LL r){
if(l(p)>=l&&r(p)<=r){
return gd(p);
}
LL mid=(l(p)+r(p))>>1;
if(r<=mid) return query(p<<1,l,r);
else{
if(l>mid) return query(p<<1|1,l,r);
else{
LL val=gcd(query(p<<1,l,r),query(p<<1|1,l,r));
return val;
}
}
return 0;
}
void solve(){
n=read();
vector<LL> pre(n+1);
unordered_map<LL,vector<LL>> mp;//注意这里因为常数过大,使用map时会超时,而使用unordered_map则不会
for(int i=1;i<=n;i++){
a[i]=read();
pre[i]=pre[i-1]^a[i];
mp[pre[i]].push_back(i);
}
build(1,1,n);
LL ans=0;
for(int i=1;i<=n;i++){
LL L=i,g=a[i];
while(L<=n){
LL l=L,r=n;
while(l<r){
LL mid=(l+r+1)>>1;
if(query(1,i,mid)==g) l=mid;
else r=mid-1;
}
LL d=g^pre[i-1];
if(L!=i)
ans+=upper_bound(mp[d].begin(),mp[d].end(),r)-lower_bound(mp[d].begin(),mp[d].end(),L);
else ans+=upper_bound(mp[d].begin(),mp[d].end(),r)-upper_bound(mp[d].begin(),mp[d].end(),L);
L=r+1;
g=query(1,i,r+1);
}
}
printf("%d\n",ans);
return ;
}
int main(){
t=read();
while(t--){
solve();
}
return 0;
}
2025牛客寒假算法基础集训营2
E 一起走很长的路!
做法:前缀和,线段树
分析可知每次操作只增加第 \(l\) 块牌的重量为最优操作。
对于需要操作多少次,则通过预处理先求出。
\(b[i]=a[i]-pre[i-1]\)
再通过线段树维护数组 \(b\) 的区间最大值。
对于每次询问则找到数组 \(b\)在\([l+1,r]\) 之中的最大值加上 \(pre[l-1]\) 则为需要操作的次数。
#include<bits/stdc++.h>
#define LL long long
#define l(p) tree[p].l
#define r(p) tree[p].r
#define ma(p) tree[p].ma
using namespace std;
const int N=200005;
LL n,m,t,k,a[N],pre[N],q,b[N];
struct Segment_tree{
LL l,r,ma;
}tree[N<<2];
inline LL read(){
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
void pushup(LL p){
ma(p)=max(ma(p<<1),ma(p<<1|1));
return ;
}
void build(LL p,LL l,LL r){
l(p)=l,r(p)=r,ma(p)=-9223372036854775808;
if(l==r){
ma(p)=b[l];
return ;
}
LL mid=(l+r)>>1;
build(p<<1,l,mid);
build(p<<1|1,mid+1,r);
pushup(p);
return ;
}
LL query(LL p,LL l,LL r){
if(l(p)>=l&&r(p)<=r){
return ma(p);
}
LL mid=(l(p)+r(p))>>1;
if(r<=mid) return query(p<<1,l,r);
else
if(l>mid) return query(p<<1|1,l,r);
else
return max(query(p<<1,l,r),query(p<<1|1,l,r));
}
void solve(){
n=read(),q=read();
for(int i=1;i<=n;i++){
a[i]=read();
pre[i]=pre[i-1]+a[i];
b[i]=a[i]-pre[i-1];
}
build(1,1,n);
for(int i=1;i<=q;i++){
LL l=read(),r=read();
if(l==r){
printf("0\n");
continue;
}
LL x=query(1,l+1,r);
x+=pre[l-1];
printf("%lld\n",max(x,0ll));
}
return ;
}
int main(){
t=1;//t=read();
while(t--){
solve();
}
return 0;
}
2025牛客寒假算法基础集训营3
G 智乃与模数
做法:整除分块,二分
整除分块结论
对于常数 \(n\) ,使得式子
\(\left\lfloor\dfrac{n}{i}\right\rfloor=\left\lfloor\dfrac{n}{j}\right\rfloor\)
成立且满足 \(i \leqslant j \leqslant n\) 的 \(j\) 值最大为 \(\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right\rfloor\) ,即值\(\left\lfloor\dfrac{n}{i}\right\rfloor\) 所在块的右端点为 \(\left\lfloor\dfrac{n}{\left\lfloor\dfrac{n}{i}\right\rfloor}\right\rfloor\) 。
而后对于每一个块内,\(N\mod i\) 呈现等差数列 \(a_i=a_1+(i-1)*b\) 其中 \(a_1=N \mod i,b=- N / i\) 。
我们可以二分第 \(k\) 大的余数 \(mid\) ,每次统计所有大于 \(mid\) 的数量,并通过等差求和算出答案。
#include<bits/stdc++.h>
#define LL long long
#define l(p) tree[p].l
#define r(p) tree[p].r
#define ma(p) tree[p].ma
using namespace std;
const int N=200005;
LL n,m,t,k;
inline LL read(){
LL s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9'){s=(s<<1)+(s<<3)+ch-'0';ch=getchar();}
return s*w;
}
void solve(){
n=read(),k=read();
LL L=0,R=n,ans=0;
while(L<R){
LL mid=(L+R)>>1;
LL sum=0,a,b,x,tot=0,val=0;
for(int l=1,r;l<=n;){
r=n/(n/l);
a=n%l;
b=n/l;
x=0;
if(a>=mid){
x=ceil((a-mid)/b)+1;
if(a-(x-1)*b==mid){
val++;
}
}
sum+=x;
tot+=(a+a-(x-1)*b)*(x)/2;
l=r+1;
}
if(sum==k){
printf("%lld\n",tot);
return ;
}
if(sum>k){
if(sum-val<k){
printf("%lld\n",tot-(sum-k)*mid);
return ;
}
}
if(sum>k) L=mid+1;
else R=mid;
}
return ;
}
int main(){
t=1;//t=read();
while(t--){
solve();
}
return 0;
}

浙公网安备 33010602011771号