多校A层冲刺NOIP2024模拟赛09
多校A层冲刺NOIP2024模拟赛09
\(T1\) A. 排列最小生成树 (pmst) \(50pts\)
-
部分分
- 子任务 \(1\) :输出 \(n-1\) 。
- 子任务 \(2\) :暴力建边跑最小生成树。
-
正解
- 当连接所有的 \((i,i+1)\) 时,一定有最终生成树所有边的边权都 \(<n\) ;而且若最终生成树里存在一条边的边权 \(\ge n\) ,那它一定不优。
- 前者比较显然就不写证明了。
- 后者若所有的边的边权都 \(\ge n\) 一定不优,暂且不考虑这种情况。
- 设这条边为 \((u,j,|p_{u}-p_{v}| \times |u-v| \ge n)\) ,若断掉这条边一定可以用另外的一条形如 \((i,i+1,|p_{i}-p_{i+1}|<n)\) 的边进行替代,故一定不优。
- 而 \(|p_{u}-p_{v}| \times |u-v|<n\) 当且仅当 \(|p_{u}-p_{v}|,|u-v|\) 中有一个满足 \(<\sqrt{n}\) ,而这是很容易人为钦定的。
- 这样的话,每个点只需要考虑 \(O(\sqrt{n})\) 条边,总边数就是 \(O(n\sqrt{n})\) 。排序用桶排序代替或对每种边权开一个
vector
记录端点使得天然有序;并查集(均摊后)单次 \(O(\log n)\) 还是 \(O(\alpha(n))\) 因很难卡满,所以关系不是很大。- 在学校 \(OJ\) 上
sort/stable_sort
直接过了,但在 accoders NOI 上被卡到了 \(50pts\) 。
- 在学校 \(OJ\) 上
点击查看代码
vector<pair<int,int> >e[50010]; int p[50010],pos[50010],cnt=0; void add(int u,int v,int w,int n) { if(w<=n) { e[w].push_back(make_pair(u,v)); } } struct DSU { int fa[50010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; int main() { freopen("pmst.in","r",stdin); freopen("pmst.out","w",stdout); int n,m,x,y,i,j; ll ans=0; cin>>n; m=sqrt(n)+1; for(i=1;i<=n;i++) { cin>>p[i]; pos[p[i]]=i; } for(i=1;i<=n;i++) { for(j=1;j<=m&&i+j<=n;j++) { add(i,i+j,abs(p[i]-p[i+j])*j,n); add(pos[i],pos[i+j],abs(pos[i]-pos[i+j])*j,n); } } D.init(n); for(j=1;j<=n;j++) { for(i=0;i<e[j].size();i++) { x=D.find(e[j][i].first); y=D.find(e[j][i].second); if(x!=y) { D.fa[x]=y; ans+=j; } } } cout<<ans<<endl; fclose(stdin); fclose(stdout); return 0; }
- 当连接所有的 \((i,i+1)\) 时,一定有最终生成树所有边的边权都 \(<n\) ;而且若最终生成树里存在一条边的边权 \(\ge n\) ,那它一定不优。
\(T2\) B. 卡牌游戏 (cardgame) \(15pts\)
-
钦定下标从 \(0\) 开始,且令 \(\forall i \in [0,nm-1], a_{i}=a_{i \bmod n},b_{i}=b_{i \bmod m}\) ,不然有点难处理。
-
部分分
- 子任务 \(1\) :模拟;在发现有循环节 \(\operatorname{lcm}(n,m)\) 后可以适当优化。
点击查看代码
ll a[100010],b[100010]; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } int main() { freopen("cardgame.in","r",stdin); freopen("cardgame.out","w",stdout); ll n,m,cnt,d,y=0,z=0,p=0,i,j,k; scanf("%lld%lld",&n,&m); d=gcd(n,m); for(i=0;i<=n-1;i++) { scanf("%lld",&a[i]); } for(i=0;i<=m-1;i++) { scanf("%lld",&b[i]); } for(i=0,j=0,k=1;k<=n/d*m;i++,j++,k++) { i-=(i==n)*n; j-=(j==m)*m; if(a[i]>b[j]) { y++; } if(a[i]==b[j]) { p++; } if(a[i]<b[j]) { z++; } } printf("%lld\n",y*d); printf("%lld\n",z*d); printf("%lld\n",p*d); fclose(stdin); fclose(stdout); return 0; }
-
正解
- 考虑如何计算每轮中 \(\operatorname{lcm}(n,m)\) 个回合的贡献。
- 观察到 \(a_{i}\) 遇到的数字一定形如 \(b_{(i+kn) \bmod m}(k \in [0,\frac{m}{\gcd(n,m)}-1])\) ,而这些数恰好是在模 \(\gcd(n,m)\) 意义下和 \(i\) 同余的所有的 \(b_{j}(j \in [0,m-1])\) ,且每个数恰好出现了一次。
- 然后二分/双指针即可。
点击查看代码
ll a[100010],b[100010]; vector<ll>r[100010]; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } int main() { freopen("cardgame.in","r",stdin); freopen("cardgame.out","w",stdout); ll n,m,d,pos1,pos2,y=0,z=0,p=0,i; cin>>n>>m; d=gcd(n,m); for(i=0;i<=n-1;i++) { cin>>a[i]; } for(i=0;i<=m-1;i++) { cin>>b[i]; r[i%d].push_back(b[i]); } for(i=0;i<=d-1;i++) { sort(r[i].begin(),r[i].end()); } for(i=0;i<=n-1;i++) { pos1=lower_bound(r[i%d].begin(),r[i%d].end(),a[i])-r[i%d].begin(); pos2=upper_bound(r[i%d].begin(),r[i%d].end(),a[i])-r[i%d].begin(); y+=pos1; p+=pos2-pos1; } z=n*m/d-y-p; cout<<y*d<<endl; cout<<z*d<<endl; cout<<p*d<<endl; fclose(stdin); fclose(stdout); return 0; }
\(T3\) C. 比特跳跃 (jump) \(19pts\)
-
部分分
- 子任务 \(1\) :暴力建边求单源最短路。
- 子任务 \(2\) :从 \(1\) 跳跃到 \(n\) 再从 \(n\) 跳跃到其他节点代价都是 \(0\) 。
-
正解
- 子任务 \(3\) / \(s=1\)
- 仿照子任务 \(2\) 的做法,考虑找到一个最大的 \(t\) 使得 \(2^{t} \le n\) 。
- 对于 \(x\in [2,2^{t}]\) ,都可以从 \(1\) 跳跃到 \(2^{t}\) 再跳跃到其他节点,故代价为 \(0\) 。
- 对于 \(x=2^{t}+r(r \in [1,2^{t}-1])\) ,可以先从 \(1\) 跳跃到 \(x \bigoplus (2^{t+1}-1) \in[1,2^{t}]\) 再跳跃到 \(x\) ,故代价为 \(0\) 。
- 而当 \(n=2^{t+1}-1\) 时无法仅通过代价为 \(0\) 的跳跃到达,故暴力建边求最短路即可。
- 子任务 \(4\) / \(s=2\)
- 考虑进行拆位统计贡献,而跳跃的代价只和二进制表示下不同的位有关。
- 假设 \(u,v\) 在二进制表示下不同的位分别是 \(2^{x_{1}},2^{x_{2}},2^{x_{3}}, \dots ,2^{x_{k}}\) ,那么从 \(u\) 到 \(u \bigoplus 2^{x_{1}} \bigoplus 2^{x_{2}}\) 之间的连边是不重要的,因为完全可以先到 \(u \bigoplus 2^{x_{1}}\) 再到 \(u \bigoplus 2^{x_{1}} \bigoplus 2^{x_{2}}\) 。
- 故可以只保留有一位不相同的进行连边,暴力建边求最短路。
- 为避免连重边,考虑只枚举为 \(1\) 的二进制位即可。但这样又出现了另一个问题,就是 \(1,2\) 之间无法之间连边,我们增加一个超级源点 \(0\) 作为中转点即可。
- 子任务 \(5\) / \(s=3\)
- 跳多次的代价一定超过跳一次的代价,即跳跃到 \(i\) 若不是从 \(i\) 二进制表示下子集过来,那一定不如直接从 \(1\) 跳过来。
- 先加上从 \(1\) 到其他点的跳跃边求出单源最短路,然后更新加重新迭代即可。
- 但直接枚举子集的时间复杂度过高,无法接受。考虑新开一个数组 \(f_{i}\) 表示 \(i\) 二进制表示下子集的最小 \(dis\) ,然后就可以通过枚举在这个集合中的元素进行更新了。
点击查看代码
vector<pair<ll,ll> >e[100010]; ll dis[100010],vis[100010],f[100010]; priority_queue<pair<ll,ll> >q; void add(ll u,ll v,ll w) { e[u].push_back(make_pair(v,w)); } void init1(ll s) { memset(dis,0x3f,sizeof(dis)); dis[s]=0; q.push(make_pair(-dis[s],s)); } void init2(ll n) { for(ll i=1;i<=n;i++) { q.push(make_pair(-dis[i],i)); } } void dijsktra(ll s) { memset(vis,0,sizeof(vis)); while(q.empty()==0) { ll x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(ll i=0;i<e[x].size();i++) { if(dis[e[x][i].first]>dis[x]+e[x][i].second) { dis[e[x][i].first]=dis[x]+e[x][i].second; q.push(make_pair(-dis[e[x][i].first],e[x][i].first)); } } } } } int main() { freopen("jump.in","r",stdin); freopen("jump.out","w",stdout); ll n,m,t,s,k,u,v,w,i,j; scanf("%lld%lld%lld%lld",&n,&m,&s,&k); for(i=1;i<=m;i++) { scanf("%lld%lld%lld",&u,&v,&w); add(u,v,w); add(v,u,w); } if(s==1) { for(t=2;t<=n;t*=2); t/=2; memset(dis,0,sizeof(dis)); if(2*t-1==n) { dis[n]=k; for(i=0;i<e[n].size();i++) { dis[n]=min(dis[n],e[n][i].second); } } } if(s==2) { for(i=1;i<=n;i++) { for(j=0;j<=31;j++) { if((i>>j)&1) { add(i,i^(1<<j),k*(1<<j)); add(i^(1<<j),i,k*(1<<j)); } } } init1(1); dijsktra(1); } if(s==3) { for(i=2;i<=n;i++) { add(1,i,k*(1|i)); add(i,1,k*(1|i)); } init1(1); dijsktra(1); memset(f,0x3f,sizeof(f)); f[1]=0; for(i=2;i<=n;i++) { for(j=0;j<=31;j++) { if((i>>j)&1) { dis[i]=min(dis[i],f[i^(1<<j)]+k*i); f[i]=min(dis[i],f[i^(1<<j)]); } } } init2(n); dijsktra(1); } for(i=2;i<=n;i++) { printf("%lld ",dis[i]); } fclose(stdin); fclose(stdout); return 0; }
- 子任务 \(3\) / \(s=1\)
\(T4\) D. 区间 (interval) \(35pts\)
- 部分分
- 子任务 \(1,2\)
-
模拟,适当剪枝。时间复杂度为 \(O(qn^{2})\) 。
点击查看代码
ll a[300010],b[300010],r[300010]; vector<ll>pos[300010]; stack<ll>s; int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); ll n,q,x,y,ans,i,j,k; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=2;i<=n;i++) { scanf("%lld",&b[i]); } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<a[i]) { s.pop(); } r[i]=(s.empty()==0)?s.top():n; s.push(i); } for(i=1;i<=n;i++) { for(j=i+1;j<=r[i];j++) { if(b[j]>a[i]) { pos[i].push_back(j); } } } scanf("%lld",&q); for(k=1;k<=q;k++) { scanf("%lld%lld",&x,&y); ans=0; for(i=x;i<=y;i++) { for(j=0;j<pos[i].size()&&pos[i][j]<=y;j++) { ans++; } } printf("%lld\n",ans); } fclose(stdin); fclose(stdout); return 0; }
-
- 子任务 \(3\)
-
因为 \(a_{i},b_{i} \le 3\) 所以每个 \(l\) 对应的 \(r\) 不会很多。
-
询问本质上是一个静态二维数点问题,主席树/ \(CDQ\) 分治/扫描线的时间复杂度均为 \(O((n+q) \log n)\) ,但主席树比 \(CDQ\) 分治/扫描线空间复杂度多带一个 \(O(\log n)\) 。
点击查看主席树代码
int a[300010],b[300010],r[300010]; stack<int>s; struct PDS_SMT { int root[300010],rt_sum; struct SegmentTree { int ls,rs,sum; }tree[5355550]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0; return rt_sum; } void update(int pre,int &rt,int l,int r,int pos) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum++; if(l==r) { return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos); } else { update(rson(pre),rson(rt),mid+1,r,pos); } } ll query(int rt1,int rt2,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt2].sum-tree[rt1].sum; } int mid=(l+r)/2; ll ans=0; if(x<=mid) { ans+=query(lson(rt1),lson(rt2),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); } return ans; } }T; int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); int n,q,x,y,ans,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=2;i<=n;i++) { scanf("%d",&b[i]); } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<a[i]) { s.pop(); } r[i]=(s.empty()==0)?s.top():n; s.push(i); } for(i=1;i<=n;i++) { T.root[i]=T.root[i-1]; for(j=i+1;j<=r[i];j++) { if(b[j]>a[i]) { T.update(T.root[i],T.root[i],1,n,j); } } } scanf("%d",&q); for(i=1;i<=q;i++) { scanf("%d%d",&x,&y); printf("%lld\n",T.query(T.root[x-1],T.root[y],1,n,x,y)); } fclose(stdin); fclose(stdout); return 0; }
点击查看 CDQ 分治代码
int a[300010],b[300010],r[300010],cnt=0; ll ans[300010]; stack<int>s; struct node { int x,y,val,id; bool operator < (const node &another) const { return (x==another.x)?(y<another.y):(x<another.x); } }q[3500010]; void add(int x,int y,int val,int id) { cnt++; q[cnt].x=x; q[cnt].y=y; q[cnt].val=val; q[cnt].id=id; } struct BIT { ll c[300010]; int lowbit(int x) { return (x&(-x)); } void update(int n,int x,int val) { if(val==0) { return; } for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(int x) { ll ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; void cdq(int l,int r,int k) { if(l==r) { return; } int mid=(l+r)/2,x,y; cdq(l,mid,k); cdq(mid+1,r,k); for(x=l,y=mid+1;y<=r;y++) { for(;q[x].x<=q[y].x&&x<=mid;x++) { T.update(k,q[x].y,(q[x].id==0)*q[x].val); } ans[q[y].id]+=(q[y].id!=0)*q[y].val*T.getsum(q[y].y); } x--; for(int i=l;i<=x;i++) { T.update(k,q[i].y,-(q[i].id==0)*q[i].val); } inplace_merge(q+l,q+mid+1,q+r+1); } int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); int n,m,x,y,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=2;i<=n;i++) { scanf("%d",&b[i]); } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<a[i]) { s.pop(); } r[i]=(s.empty()==0)?s.top():n; s.push(i); for(j=i+1;j<=r[i];j++) { if(b[j]>a[i]) { add(i+1,j+1,1,0); } } } scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); x++; y++; add(x-1,x-1,1,i); add(y,y,1,i); add(y,x-1,-1,i); add(x-1,y,-1,i); } cdq(1,cnt,n+1); for(i=1;i<=m;i++) { printf("%lld\n",ans[i]); } fclose(stdin); fclose(stdout); return 0; }
点击查看扫描线代码
int a[300010],b[300010],r[300010],cnt=0; ll ans[300010][5]; stack<int>s; struct node { int x,y,id; bool operator < (const node &another) const { return (x==another.x)?(y<another.y):(x<another.x); } }q[10000010]; void add(int x,int y,int id) { cnt++; q[cnt].x=x; q[cnt].y=y; q[cnt].id=id; } struct BIT { ll c[300010]; int lowbit(int x) { return (x&(-x)); } void update(int n,int x,int val) { if(val==0) { return; } for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(int x) { ll ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T; int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); int n,m,x,y,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=2;i<=n;i++) { scanf("%d",&b[i]); } for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]<a[i]) { s.pop(); } r[i]=(s.empty()==0)?s.top():n; s.push(i); for(j=i+1;j<=r[i];j++) { if(b[j]>a[i]) { add(i+1,j+1,0); } } } scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d",&x,&y); x++; y++; add(x-1,x-1,i); add(y,y,i); add(y,x-1,i); add(x-1,y,i); } sort(q+1,q+1+cnt); for(i=1;i<=cnt;i++) { if(q[i].id==0) { T.update(n+1,q[i].y,1); } else { ans[q[i].id][0]++; ans[q[i].id][ans[q[i].id][0]]=T.getsum(q[i].y); } } for(i=1;i<=m;i++) { printf("%lld\n",ans[i][4]+ans[i][3]-ans[i][2]-ans[i][1]); } fclose(stdin); fclose(stdout); return 0; }
-
- 子任务 \(1,2\)
- 正解
-
考虑将询问离线下来,枚举右端点进行扫描线。
-
维护一个单调递减的栈来满足第一个要求。对于每个 \(r\) 找到最小的左端点 \(l\) 使得 \(\forall i \in [l,r-1],a_{i}<b_{r}\) ,挂个 \(ST\) 表再二分是不必要的,因为只有单调栈内的元素才可能产生贡献,所以直接在单调栈里二分即可。
-
二分出单调栈的左端点 \(l\) 后 \(s_{l \sim top}\) 这些点均可以作为合法的左端点进行转移,但因为实际下标可能不连续,线段树挨个遍历修改时间复杂度不可接受。
-
常见的做法是线段树维护历史版本和来做。具体地,将 \(s_{l} \sim r-1\) 都新加一个贡献(仅对在栈内的生效),而通过记录某个位置是否在栈内可以很方便地支持这个操作。
-
每个左端点都对某些右端点有贡献,贡献彼此之间可能不相同,故考虑每扫到一个右端点就视为新建一个版本,然后查询历史版本和。
点击查看代码
ll a[300010],b[300010],ans[300010]; deque<ll>s; vector<pair<ll,ll> >q[300010]; struct SMT { struct SegentTree { ll sum,hsum,lazy,cnt; }tree[1200010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].cnt=tree[lson(rt)].cnt+tree[rson(rt)].cnt; tree[rt].hsum=tree[lson(rt)].hsum+tree[rson(rt)].hsum; } void build(ll rt,ll l,ll r) { if(l==r) { return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushlazy(ll rt,ll lazy) { tree[rt].hsum+=lazy*tree[rt].cnt; tree[rt].lazy+=lazy; } void pushdown(ll rt) { pushlazy(lson(rt),tree[rt].lazy); pushlazy(rson(rt),tree[rt].lazy); tree[rt].lazy=0; } void update_val(ll rt,ll l,ll r,ll x,ll y,ll val) { if(x<=l&&r<=y) { tree[rt].hsum+=val*tree[rt].cnt; tree[rt].lazy+=val; return; } pushdown(rt); ll mid=(l+r)/2; if(x<=mid) { update_val(lson(rt),l,mid,x,y,val); } if(y>mid) { update_val(rson(rt),mid+1,r,x,y,val); } pushup(rt); } void update_tim(ll rt,ll l,ll r,ll pos,ll val) { if(l==r) { tree[rt].cnt+=val; return; } pushdown(rt); ll mid=(l+r)/2; if(pos<=mid) { update_tim(lson(rt),l,mid,pos,val); } else { update_tim(rson(rt),mid+1,r,pos,val); } pushup(rt); } ll query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].hsum; } pushdown(rt); ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),mid+1,r,x,y); } return ans; } }T; ll under(ll x) { ll ans=-1; if(s.empty()==0) { ll l=0,r=s.size()-1,mid; while(l<=r) { mid=(l+r)/2; if(a[s[mid]]<x) { ans=mid; r=mid-1; } else { l=mid+1; } } } return ans; } ll lower(ll x) { ll ans=-1; if(s.empty()==0) { ll l=0,r=s.size()-1,mid; while(l<=r) { mid=(l+r)/2; if(s[mid]>=x) { ans=mid; r=mid-1; } else { l=mid+1; } } } return ans; } int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); ll n,m,l,r,pos,tmp,i,j; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=2;i<=n;i++) { scanf("%lld",&b[i]); } scanf("%lld",&m); for(i=1;i<=m;i++) { scanf("%lld%lld",&l,&r); q[r].push_back(make_pair(l,i)); } T.build(1,1,n); for(i=1;i<=n;i++) { pos=under(b[i]); if(pos!=-1) { T.update_val(1,1,n,s[pos],i-1,1); } for(j=0;j<q[i].size();j++) { pos=lower(q[i][j].first); ans[q[i][j].second]=T.query(1,1,n,q[i][j].first,i-1); } while(s.empty()==0&&a[s.back()]<=a[i]) { T.update_tim(1,1,n,s.back(),-1); s.pop_back(); } s.push_back(i); T.update_tim(1,1,n,i,1); } for(i=1;i<=m;i++) { printf("%lld\n",ans[i]); } fclose(stdin); fclose(stdout); return 0; }
-
-
另外一个做法是再开一棵副线段树维护单调栈内元素的贡献,在弹出时将贡献算入主线段树,查询时两棵线段树均进行查询。
点击查看代码
ll a[300010],b[300010],ans[300010]; deque<ll>s; vector<pair<ll,ll> >q[300010]; struct SMT { struct SegentTree { ll len,sum,lazy; }tree[1200010]; ll lson(ll x) { return x*2; } ll rson(ll x) { return x*2+1; } void pushup(ll rt) { tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void build(ll rt,ll l,ll r) { tree[rt].len=r-l+1; if(l==r) { return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushdown(ll rt) { if(tree[rt].lazy!=0) { tree[lson(rt)].sum+=tree[rt].lazy*tree[lson(rt)].len; tree[rson(rt)].sum+=tree[rt].lazy*tree[rson(rt)].len; tree[lson(rt)].lazy+=tree[rt].lazy; tree[rson(rt)].lazy+=tree[rt].lazy; tree[rt].lazy=0; } } void update(ll rt,ll l,ll r,ll x,ll y,ll val) { if(x<=l&&r<=y) { tree[rt].sum+=val*tree[rt].len; tree[rt].lazy+=val; return; } pushdown(rt); ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } pushup(rt); } ll query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].sum; } pushdown(rt); ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),mid+1,r,x,y); } return ans; } }T[2]; ll under(ll x) { ll ans=-1; if(s.empty()==0)//栈为空时, accoders 直接 RE 了,但学校 OJ 没爆 { ll l=0,r=s.size()-1,mid; while(l<=r) { mid=(l+r)/2; if(a[s[mid]]<x) { ans=mid; r=mid-1; } else { l=mid+1; } } } return ans; } ll lower(ll x) { ll ans=-1; if(s.empty()==0) { ll l=0,r=s.size()-1,mid; while(l<=r) { mid=(l+r)/2; if(s[mid]>=x) { ans=mid; r=mid-1; } else { l=mid+1; } } } return ans; } int main() { freopen("interval.in","r",stdin); freopen("interval.out","w",stdout); ll n,m,l,r,pos,tmp,i,j; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } for(i=2;i<=n;i++) { scanf("%lld",&b[i]); } scanf("%lld",&m); for(i=1;i<=m;i++) { scanf("%lld%lld",&l,&r); q[r].push_back(make_pair(l,i)); } T[0].build(1,1,n); T[1].build(1,1,n); for(i=1;i<=n;i++) { pos=under(b[i]); if(pos!=-1) { T[1].update(1,1,n,pos+1,s.size(),1); } for(j=0;j<q[i].size();j++) { pos=lower(q[i][j].first); ans[q[i][j].second]=T[0].query(1,1,n,q[i][j].first,i-1); if(pos!=-1) { ans[q[i][j].second]+=T[1].query(1,1,n,pos+1,s.size()); } } while(s.empty()==0&&a[s.back()]<=a[i]) { tmp=T[1].query(1,1,n,s.size(),s.size()); T[0].update(1,1,n,s.back(),s.back(),tmp); T[1].update(1,1,n,s.size(),s.size(),-tmp); s.pop_back(); } s.push_back(i); } for(i=1;i<=m;i++) { printf("%lld\n",ans[i]); } fclose(stdin); fclose(stdout); return 0; }
-
总结
- 在发现 \(T1\) \(45 \min\) 还没切的前提下,直接去写后面的部分分了。写部分分的时候也是匆匆忙忙,基本没从值域入手。
- \(T1\)
- 在最后尝试随机化做法下,发现 \(n \le 2000\) 下前后各选 \(50\) 个点就拍上了,但没意识到最终最小生成树上所有边的边权都 \(<n\) ,导致 \(2 \times 50n\) 的边数都开不下,没再去想只需要考虑 \(O(\sqrt{n})\) 条边的做法。
- 以为会像 多校A层冲刺NOIP2024模拟赛08 T1 A. 传送 (teleport) 一样通过几何直观性可以很好理解,所以把 \(|p_{i}-p_{j}| \times |i-j|\) 理解成了矩形面积然后在二维平面上进行覆盖。
- \(T1,T2\) 交换了下位置是没想到的。
- \(T2\) 想到同余系/按下标分类的方法了,但没时间(还剩 \(40 \min\) ,且还要分配给 \(T1\) 的随机化做法)去想具体实现了,而且同余系也掌握得不是很好。
- \(T3\) 对 \(s=1\) 的部分分想的时间太短了,没想到还可以从 \(n\) 进行跳跃。
- \(T4\) 分析单调栈分割区间时见复杂度少乘了个 \(n\) ,导致误认为是 \(T1,T4\) 互换了位置,后面所写的主席树和 \(CDQ\) 分治的时空复杂度都少乘了一个 \(O(n)\) 。但在意识到这个问题后,仍坚持写完了主席树和 \(CDQ\) 分治,没有直接摆烂,挺好。
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18486908,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。