当前位置: 首页 > news >正文

国庆期间做题记录

好摆好摆,几天就写了几个题。

QOJ 网络赛题

记一辈子的题。

Xor Mirror

题意如下:

维护一个长为 \(n\) 的序列 \(A\),支持两种操作:

  • 输入 \(1,l,r,k\),令 \(B_i=A_{i\oplus k}\space i\in[l,r)\),再令 \(A_i=B_i\space i\in[l,r)\)

  • 输入 \(2,l,r\),输出 \(\sum_{i=l}^{r-1}A_i\)

保证 \(n=2^k-1\),\(k\in[1,18]\)

题解:

操作非常阴间。

异或出来的数肯定是一段又一段的,所以不考虑线段树了,考虑分块。

如果按 \(\sqrt n\) 分块又显得不太好,因为还是碎碎的。

所以考虑按 \(i\) 的二进制值域高低位分块。

具体而言,把 \(i\) 拆成二进制,然后高九位和低九位分开。

同时将 \(k\) 拆成二进制,高九位低九位也分开。

发现,高九位相同的会被挪到一起,低九位相同的可以暴力挪。

那么修改整块就是直接一个指针挪过去。

修改散块需要重构,但是重构后原来的指针就完蛋了,所以要新建一个块再重构。

但是空间就起飞了,所以我们还需要内存回收。

大致过程如下,橙色表示修改的区间。

非常的抽象是吧,接下来看代码更抽象。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N = (1<<18)+5;
int T,n,q;
vector<int> a[N];
ll sum[605];
int to[605],tag[605];
int pos1,pos2,vis[605];
int X,Y,B;
void newnode(){pos1=pos2=-1;for(int i=0;i<X;i++){if(vis[i]==0){if(pos1==-1) pos1=i;else if(pos2==-1) pos2=i;else return ;}}
}
void modify(int l,int r,int k){int lx=l/B,ly=l%B,rx=r/B,ry=r%B,kx=k/B,ky=k%B;if(lx==rx){vis[to[lx]]--;ll s=0;vector<int> vec;for(int j=0;j<Y;j++){if(j<ly || j>ry){vec.push_back(a[to[lx]][j^tag[lx]]);}else{vec.push_back(a[to[lx^kx]][j^tag[lx^kx]^ky]);}s+=vec[j];}newnode();to[lx]=pos1,tag[lx]=0,vis[pos1]++;a[pos1]=vec;sum[pos1]=s;}else{vis[to[lx]]--,vis[to[rx]]--;ll s1=0,s2=0;vector<int> v1,v2;for(int j=0;j<Y;j++){if(j<ly){v1.push_back(a[to[lx]][j^tag[lx]]);}else{v1.push_back(a[to[lx^kx]][j^tag[lx^kx]^ky]);}s1+=v1[j];}for(int j=0;j<Y;j++){if(j>ry){v2.push_back(a[to[rx]][j^tag[rx]]);}else{v2.push_back(a[to[rx^kx]][j^tag[rx^kx]^ky]);}s2+=v2[j];}if(rx-lx>1){vector<int> tmp,tmp2;for(int i=0;i<X;i++){tmp2.push_back(tag[i]);;tmp.push_back(to[i]);}for(int i=lx+1;i<=rx-1;i++){vis[to[i]]--;vis[tmp[i^kx]]++;to[i]=tmp[i^kx];tag[i]=tmp2[i^kx]^ky;}}newnode();to[lx]=pos1,tag[lx]=0,vis[pos1]++;a[pos1]=v1;sum[pos1]=s1;to[rx]=pos2,tag[rx]=0,vis[pos2]++;a[pos2]=v2;sum[pos2]=s2;}
}
ll query(int l,int r){int lx=l/B,ly=l%B,rx=r/B,ry=r%B;ll res=0;if(lx==rx){for(int j=ly;j<=ry;j++){res+=a[to[lx]][j^tag[lx]];}}else{for(int j=ly;j<Y;j++){res+=a[to[lx]][j^tag[lx]];}for(int i=lx+1;i<=rx-1;i++){res+=sum[to[i]];}for(int j=0;j<=ry;j++){res+=a[to[rx]][j^tag[rx]];}}return res;
}
void get(){int p=0;while((1<<p)<n) p++;int bn=(p+1)/2;B=(1<<bn);X=n/B,Y=B;
}
void solve(){cin>>n>>q;get();for(int i=0;i<X;i++) a[i].clear();for(int i=0;i<X;i++) sum[i]=tag[i]=0,to[i]=i,vis[i]=1;for(int i=0;i<X;i++){vector<int> vec;for(int j=0,x;j<Y;j++){cin>>x;vec.push_back(x);sum[i]+=x;}a[i]=vec;}for(int i=1,op,l,r;i<=q;i++){cin>>op>>l>>r;r--;if(op==1){int k;cin>>k;modify(l,r,k);}else{cout << query(l,r) << '\n';}}
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;while(T--){solve();}return 0;
}

AtCoder

AT 没有提交记录查看,这非常不好,只能找近几场的赛题了。

Clearance

题意如下:

维护一个序列,支持一个操作:

  • \(l,r,k\) 表示在 \(l,r\) 区间中每个位置取 \(min(k,A_i)\) 个物品,并 \(A_i=max(A_i-k,0)\)

题解

势能分析线段树。

以区间 \(min\) 为势能,如果区间 \(min\) 小于 \(k\) 就一直递归,否则打标记返回 \(k\times cnt_u\)

如果出现了一个 \(0\),那么将其的 \(cnt\) 设置为 \(0\),将 \(A_i\) 设置为 \(inf\)

以及如果一个区间里 \(min\) 全为 \(0\) 则提前返回 \(0\)

证明:

  • 如果区间 \(min\) 一直不为 \(0\),就是普通线段树。

  • 如果有一个节点为 \(0\),将其设为 \(inf\),最多会有 \(n\) 次递归到叶子。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
constexpr int N = 3e5+10;
constexpr ll inf = 1e16;
int n,q;
ll a[N];
struct SegTree{
#define ls u<<1
#define rs u<<1|1
#define mid ((l+r)>>1)ll tag[N<<2],mn[N<<2];int cnt[N<<2];void pushup(int u){mn[u]=min(mn[ls],mn[rs]);cnt[u]=cnt[ls]+cnt[rs];}void build(int u,int l,int r){if(l==r){mn[u]=a[l];tag[u]=0;cnt[u]=1;return ;}build(ls,l,mid);build(rs,mid+1,r);pushup(u);}void upd(int u,ll k){mn[u]+=k;tag[u]+=k;if(mn[u]<=0){cnt[u]=0;mn[u]=inf;}}void pushdown(int u){if(!tag[u]) return ;if(mn[ls]!=inf) upd(ls,tag[u]);if(mn[rs]!=inf) upd(rs,tag[u]);tag[u]=0;}ll query(int u,int l,int r,int x,int y,ll k){if(mn[u]==inf) return 0;if(l==r && mn[u]<=k){ll res=mn[u];mn[u]=inf;cnt[u]=0;return res;}if(l>=x && r<=y){if(mn[u]>k){mn[u]-=k;tag[u]-=k;return k*1ll*cnt[u];}else{ll res=0;pushdown(u);if(x<=mid) res+=query(ls,l,mid,x,y,k);if(mid<y) res+=query(rs,mid+1,r,x,y,k);pushup(u);return res;}}ll res=0;pushdown(u);if(x<=mid) res+=query(ls,l,mid,x,y,k);if(mid<y) res+=query(rs,mid+1,r,x,y,k);pushup(u);return res;}
}T;
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;for(int i=1;i<=n;i++) cin>>a[i];T.build(1,1,n);cin>>q;for(int i=1,l,r;i<=q;i++){ll k;cin>>l>>r>>k;cout << T.query(1,1,n,l,r,k) << '\n';}return 0;
}

Range Shuffle Query

题意如下:

给你一个长度为 \(N\) 的序列 \(A = (A_1, A_2, \dots, A_N)\)

处理 \(Q\) 个查询。

每个查询都给出了整数 \(L\), \(R\), \(X\) 并要求你解决以下问题。

假设 \(B = (A_L,A_{L+1}, \dots, A_R)\) 是由 \(A\)\(L\)\(R\) 元素组成的序列。

执行下面的程序一次:

首先,从 \(B\) 中删除每个值至少为 \(X\) 的元素。

然后,任意重新排列 \(B\) 的其余元素。

可以得到多少个不同的序列 \(B\) ?并对 \(998244353\) 的模数。

题解:

有可重集的排列数 \(\dfrac{n!}{n_1!n_2!\dots n_k!}\)

然后上面的 \(n\) 直接 莫队+值域分块,下面的依托显然可以在莫队移动时 \(O(1)\) 维护。

因为取模的性质烂到家了,所以用最保守的维护方式:先把贡献全清了再加回去。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define B 500
#define L(i) ((i-1)*B+1)
#define R(i) min(i*B,n)
#define bel(i) ((i-1)/B+1)
using namespace std;
constexpr int N = 3e5+10;
constexpr int p = 998244353;
ll qpow(ll a,ll b){ll res=1;while(b){if(b&1) res=res*a%p;b>>=1;a=a*a%p;}return res%p;
}
int n,m;
ll a[N];
struct Q{int l,r,x,id;bool operator < (const Q &a)const{return (l/B==a.l/B) ? (l/B)&1 ? r<a.r : r>a.r : l<a.l; }
}q[N];
int l=1,r=0;
ll fac[N],ans[N],inv[N];
ll cnt[N],mul[N],sum[N];
void add(int x){mul[bel(x)]=mul[bel(x)]*fac[cnt[x]]%p;cnt[x]++;sum[bel(x)]++;mul[bel(x)]=mul[bel(x)]*inv[cnt[x]]%p;
}
void del(int x){mul[bel(x)]=mul[bel(x)]*fac[cnt[x]]%p;cnt[x]--;sum[bel(x)]--;mul[bel(x)]=mul[bel(x)]*inv[cnt[x]]%p;	
}
ll query(int x){ll res1=0,res2=1;for(int i=1;i<bel(x);i++){res1=(res1+sum[i])%p;res2=res2*mul[i]%p;}for(int j=L(bel(x));j<x;j++){res1=(res1+cnt[j])%p;res2=res2*inv[cnt[j]]%p;}return fac[res1]*res2%p;
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);fac[0]=1;for(int i=1;i<N;i++) fac[i]=fac[i-1]*i%p,mul[i]=1;inv[0]=1;for(int i=1;i<N;i++) inv[i]=qpow(fac[i],p-2);cin>>n>>m;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=1;i<=m;i++){cin>>q[i].l>>q[i].r>>q[i].x;q[i].id=i;}sort(q+1,q+1+m);for(int i=1;i<=m;i++){while(l>q[i].l) add(a[--l]);while(r<q[i].r) add(a[++r]);while(l<q[i].l) del(a[l++]);while(r>q[i].r) del(a[r--]);ans[q[i].id]=query(q[i].x);}for(int i=1;i<=m;i++){cout << ans[i] << '\n';}return 0;
}

Luogu

狼坑 Trous de loup

如果不修改,显然可以双指针直接做。

修改就相当于是有一定的容错,可以再套一个双指针,表示删去的最大区间的数。

转移可以用单调队列优化。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
constexpr int N = 2e6+10;
int n,d,ans;
ll m,a[N],arr[N];
int head,tail,que[N],l;
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n>>m>>d;for(int i=1;i<=n;i++){cin>>a[i];arr[i]=arr[i-1]+a[i];}ans=d,que[tail]=d,l=1;for(int i=d+1;i<=n;i++){while(head<=tail && arr[i]-arr[i-d]>arr[que[tail]]-arr[que[tail]-d]) tail--;que[++tail]=i;while(head<=tail && arr[i]-arr[l-1]-arr[que[head]]+arr[que[head]-d]>m){l++;while(head<=tail && que[head]-d+1<l) head++;}ans=max(ans,i-l+1);}cout << ans << '\n';return 0;
}

一步最优

题意如下:

给出一个序列。

贪心选择序列中的子段和最大的子段,所有子段不能有交,最多选择 \(m\) 段。

回答该算法在 \(m\in [1,n]\) 时能得到所有子段和之和的最大值最小值

题解:

发现出现差距的操作是子段和相同时,选择了哪一段

自然的发现,想让子段和最大,我们需要选择子段长度最小的那一段。

证明:

  • 考虑两段不交,那就两段都能选。
  • 考虑两段包含,选短的可以给其它选择留下更多的数。
  • 考虑两段有交但不包含,因为最大子段和中任意一段前后缀和都大于等于 \(0\)(如果前后缀和小于 \(0\),那么可以直接扔掉。),所以易得两段可以直接合并,不存在。

反之亦然。

线段树动态维护区间最大子段和区间赋值操作。

其中区间赋值只需要赋极小值,可以直接暴力做。

证明:

  • 因为题目要求选择的子段数不超过 \(m\),所以子段和的最大值最小为 \(0\)。当全局都小于 \(0\) 的时候就可以跳过了。总计操作 \(O(n)\) 次。
点击查看代码
#include<bits/stdc++.h>
#define ll long long
#define inf 1e14
using namespace std;
constexpr int N = 2e5+10;
int T,n,a[N];
struct node{ll val;int l,r;
};
node MX(bool t,const node &x,const node &y){if(x.val==y.val) return x.r-x.l<y.r-y.l ? (t==1?x:y) : (t==1?y:x);return x.val>y.val ? x : y;
}
struct Tree{ll v;int l,r;node mx,lmx,rmx;
}tr[N<<2][2];
Tree merge(bool t,const Tree &x,const Tree &y){Tree res={0,0,0};res.v=x.v+y.v;res.lmx=MX(t,x.lmx,(node){x.v+y.lmx.val,x.l,y.lmx.r});res.rmx=MX(t,y.rmx,(node){y.v+x.rmx.val,x.rmx.l,y.r});res.mx=MX(t,MX(t,x.mx,y.mx),(node){x.rmx.val+y.lmx.val,x.rmx.l,y.lmx.r});res.l=x.l,res.r=y.r;return res;
}
struct SegTree{
#define ls u<<1
#define rs u<<1|1
#define mid ((l+r)>>1)void pushup(int u,bool t){tr[u][t]=merge(t,tr[ls][t],tr[rs][t]);}void upd(int u,ll k,bool t){tr[u][t].v=tr[u][t].lmx.val=tr[u][t].rmx.val=tr[u][t].mx.val=k;}void init(int u,int id,int k,bool t){tr[u][t].v=k,tr[u][t].lmx=tr[u][t].rmx=tr[u][t].mx={k,id,id};}void build(bool t,int u=1,int l=1,int r=n){tr[u][t].l=l,tr[u][t].r=r;if(l==r){init(u,l,a[l],t);return;}build(t,ls,l,mid);build(t,rs,mid+1,r);pushup(u,t);}void modify(int x,ll k,bool t,int u=1,int l=1,int r=n){if(l==r){upd(u,k,t);return ;}if(x<=mid) modify(x,k,t,ls,l,mid);else modify(x,k,t,rs,mid+1,r);pushup(u,t);}
}Tr;
void solve(){cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}Tr.build(1);Tr.build(0);ll ans=0;for(int t=1;t>=0;t--){for(int i=1;i<=n;i++){node res=tr[1][t].mx;if(res.val>0){ans+=res.val;for(int j=res.l;j<=res.r;j++) Tr.modify(j,-inf,t);}cout << ans << ' ';}ans=0;cout << '\n';}
}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>T;while(T--){solve();}return 0;
}

[Ynoi2005] rmscne

显然有 \(O(nq)\) 的双指针做法。

但是这玩意没法优化,所以我们换一个。

考虑扫描线,对 \(r\) 升序排序,每次维护 \(l\) 找到一个 \(r'\) 使得 \(r'\) 最小且符合条件。

然后对 \(r\) 求一个最大的 \(l'\) 且符合条件。

那么答案就在 \(l',r'\) 之间,然后对其求 \(r'-i\) 的最小值即可。

用并查集维护。

点击查看代码
#include<bits/stdc++.h>
#define ll long long
using namespace std;
constexpr int N = 2e6+10;
struct SegTree{
#define ls (u<<1)
#define rs (u<<1|1)
#define mid ((l+r)>>1)int tr[N<<2],tag[N<<2];void pushup(int u){tr[u]=min(tr[ls],tr[rs]);}void upd(int u,int r,int c){tr[u]=c-r+1;tag[u]=c;}void pushdown(int u,int l,int r){if(tag[u]==-1) return ;upd(ls,mid,tag[u]);upd(rs,r,tag[u]);tag[u]=-1;}void build(int u,int l,int r){tag[u]=-1;if(l==r) return ;build(ls,l,mid);build(rs,mid+1,r);}void modify(int u,int l,int r,int x,int y,int c){if(l>=x && r<=y){upd(u,r,c);return ;}pushdown(u,l,r);if(x<=mid) modify(ls,l,mid,x,y,c);if(mid<y) modify(rs,mid+1,r,x,y,c);pushup(u);}int query(int u,int l,int r,int x,int y){if(l>=x && r<=y) return tr[u];int res=2e9;pushdown(u,l,r);if(x<=mid) res=min(res,query(ls,l,mid,x,y));if(mid<y) res=min(res,query(rs,mid+1,r,x,y));return res;}
}T;
int n,a[N],m;
struct Q{int l,id;};
vector<Q> q[N];
int fa[N],lst[N],ans[N];
int find(int x){return fa[x]==x ? x : fa[x]=find(fa[x]);}
int main(){ios::sync_with_stdio(0);cin.tie(0);cout.tie(0);cin>>n;for(int i=1;i<=n;i++){cin>>a[i];}for(int i=0;i<=n;i++) fa[i]=i;cin>>m;for(int i=1,l,r;i<=m;i++){cin>>l>>r;q[r].push_back({l,i});}T.build(1,1,n);for(int i=1;i<=n;i++){T.modify(1,1,n,lst[a[i]]+1,i,i);if(lst[a[i]]) fa[lst[a[i]]]=lst[a[i]]+1;lst[a[i]]=i;for(auto x:q[i]){int l=x.l,r=find(l),id=x.id;ans[id]=T.query(1,1,n,l,r);}}for(int i=1;i<=m;i++){cout << ans[i] << '\n';}return 0;
}
http://www.hskmm.com/?act=detail&tid=28964

相关文章:

  • 02020508 EF Core高级08-表达式树、Expression和委托的关系、查看表达式树结构、AST、手动创建表示树、工厂方法
  • UnitTask中的Forget()与 CTS
  • commons-net - 详解
  • 12 种 Pandas 测试技巧,让数据处理少踩坑
  • 02020505 EF Core高级05-实体的5种状态、EntityEntry、AsNoTracking、实体状态跟踪
  • securityCTF 2025 pwn方向题解
  • 02020507 EF Core高级07-悲观并发控制、乐观并发控制、EF Core连接MySQL、RowVersion
  • linux防火墙操作命令
  • 02020506 EF Core高级06-EF Core批量删除更新插入、全局筛选器、软删除、全局筛选的性能问题
  • 机器学习社会影响与导航系统研究
  • ubuntu24.04 desktop 安装vnc远程桌面(亲测)
  • 完整教程:游标查询在对话历史场景下的独特优势
  • [论文笔记] A Contemporary Survey of Large Language Model Assisted Program Analysis
  • 251011
  • 一种整理HTML和JS代码的方法
  • 元推理框架,是人类文明的《神农本草经》,源于自指自洽的觉悟与洗礼
  • SSL/TLS加密算法:守护网络通信的安全框架
  • 未来计划
  • 【程序员必看】MySQL数据类型全解析:选错类型性能直接掉80%!
  • NOIP2023
  • 理解WPF Stylet中Command=“{s:Action 方法名}“的设计与实现 - 实践
  • 2025环氧地坪漆厂家推荐:常州新禾,品质保证施工无忧!
  • 概率论习题
  • 2025上海经侦律师TOP5榜单:专业法律服务与高效解决方案
  • 概率论部分习题
  • 2025家居ERP推荐:赛思软件助力企业高效管理!
  • 2025彩钢瓦保养优质厂家推荐,江苏承优建筑工程专业服务!
  • 优维科技一面
  • 2025家纺摄影公司最新推荐榜:创意视觉与专业服务的完美结合
  • 2025磁力泵加工厂推荐中正化工,专业定制高效耐用产品!