[ZROI 9.15模拟赛] Tutorial
Link:
可能要补一补之前的题了
题目名字天(Sky)的(De)炭(C)好评啊……
A:
从买/卖物品的配对来考虑:
可以发现如果当前物品为卖,肯定从之前选最小的(无论其为买/卖),因为贡献都是差值!
如果要买的物品当前状态为卖,那么相当于将那条匹配链的卖的那一端转换
用优先队列维护$pair(w[i],0/1)$,0/1分别表示当前为卖/买的状态即可
#include <bits/stdc++.h> using namespace std; #define X first #define Y second typedef long long ll; typedef pair<int,int> P; const int MAXN=1e5+10; int T,n,cnt,dat[MAXN];ll res=0; priority_queue<P,vector<P>,greater<P> > q; int main() { scanf("%d",&T); while(T--) { scanf("%d",&n); for(int i=1;i<=n;i++) scanf("%d",&dat[i]); res=0;cnt=0; while(!q.empty()) q.pop(); q.push(P(dat[1],1)); for(int i=2;i<=n;i++) { P t=q.top(); if(dat[i]>t.X) { res+=dat[i]-t.X; cnt+=t.Y;q.pop(); if(!t.Y) q.push(P(t.X,1)); q.push(P(dat[i],0)); } else q.push(P(dat[i],1)); } printf("%lld %d\n",res,cnt*2); } return 0; }
考场上写的$O(n^2)$ $dp$发现并不能优化……
其实已经想到用匹配来做,但没发现那条可反悔贪心的性质
一般需要优化的匹配问题除了网络流,都是顺序考虑每一个数而非整体考虑
这里还要注意必须将卖设为0,因为在$w[i]$相同时要先将卖转移
(如果优先队列中放的是结构体,注意每个维度的顺序!)
B:
关键要将上下边界也看成大的障碍点
考虑长度$d$不能通过的条件:
将所有长度小于$d$的连边后上下边界连通,这样一定无法通过这道屏障
用类似$Kruskal$的方法将所有边排序后用并查集维护连通性即可
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define pb push_back typedef double db; typedef long long ll; typedef pair<int,int> P; const int MAXN=2e5+10; P dat[MAXN]; int n,l,f[MAXN],tot; struct edge{int x,y;double w;}e[MAXN]; double sqr(double a){return a*a;} bool cmp(edge a,edge b){return a.w<b.w;} void add_edge(int x,int y,double w) {e[++tot]=(edge){x,y,w};} int find(int x) {return f[x]==x?x:f[x]=find(f[x]);} double dist(int x,int y) {return sqrt(sqr(dat[x].X-dat[y].X)+sqr(dat[x].Y-dat[y].Y));} int main() { scanf("%d%d",&n,&l); for(int i=1;i<=n;i++) scanf("%d%d",&dat[i].X,&dat[i].Y); for(int i=0;i<=n+1;i++) f[i]=i; for(int i=1;i<=n;i++) add_edge(0,i,dat[i].Y),add_edge(i,n+1,l-dat[i].Y); for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) add_edge(i,j,dist(i,j)); sort(e+1,e+tot+1,cmp); for(int i=1;i<=tot;i++) { int px=find(e[i].x),py=find(e[i].y); if(px==py) continue;f[px]=py; if(find(0)==find(n+1)) return printf("%.3lf",e[i].w),0; } return 0; }
C:
将0/1分别看作-1/1的贡献
发现一个区间需要的最小修改次数为最大的不相交前/后缀和的和
感性证明:
最值:只要某位的前/后缀和大于零该位就一定要删除,因此该值为下界
可行性:如果后面还有大于零的点则一定会向后拓展
两种实现方式:
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define mid ((l+r)>>1) #define lc k<<1,l,mid #define rc k<<1|1,mid+1,r typedef double db; typedef long long ll; typedef pair<int,int> P; const int MAXN=2e5+10; int n,m,l,r;char s[MAXN]; struct node{int sum,lmx,rmx,mx;}seg[MAXN<<2]; node operator + (node a,node b) { node ret; ret.sum=a.sum+b.sum; ret.lmx=max(a.lmx,a.sum+b.lmx); ret.rmx=max(b.rmx,b.sum+a.rmx); ret.mx=max(a.lmx+b.rmx,max(a.sum+b.mx,b.sum+a.mx)); return ret; } void build(int k,int l,int r) { if(l==r) { if(s[l]=='0') seg[k]=(node){-1,0,0,0}; else seg[k]=(node){1,1,1,1}; return; } build(lc);build(rc); seg[k]=seg[k<<1]+seg[k<<1|1]; } node Query(int a,int b,int k,int l,int r) { if(a<=l&&r<=b) return seg[k]; node ret=(node){0,0,0,0}; if(a<=mid) ret=Query(a,b,lc); if(b>mid) ret=ret+Query(a,b,rc); return ret; } int main() { scanf("%d%d%s",&n,&m,s+1); build(1,1,n); for(int i=1;i<=m;i++) scanf("%d%d",&l,&r),printf("%d\n",Query(l,r,1,1,n).mx); return 0; }
如果求$pre+suf$的最值要注意将区间向外拓展1
(可能取$pre[l-1]$,也就是前缀不选)
#include <bits/stdc++.h> using namespace std; #define X first #define Y second #define mid ((l+r)>>1) #define lc k<<1,l,mid #define rc k<<1|1,mid+1,r typedef double db; typedef long long ll; typedef pair<int,int> P; const int MAXN=8e5+10,INF=1<<28; char s[MAXN]; int res,lft; int n,q,l,r,pre[MAXN],suf[MAXN]; int mx[MAXN],lmx[MAXN],rmx[MAXN]; void pushup(int k) { lmx[k]=max(lmx[k<<1],lmx[k<<1|1]); rmx[k]=max(rmx[k<<1],rmx[k<<1|1]); mx[k]=max(mx[k<<1],max(mx[k<<1|1],lmx[k<<1]+rmx[k<<1|1])); } void build(int k,int l,int r) { if(l==r) { lmx[k]=pre[l],rmx[k]=suf[l]; mx[k]=-INF;return; } build(lc);build(rc); pushup(k); } void Query(int a,int b,int k,int l,int r) { if(a<=l&&r<=b) { if(lft==-INF) res=max(res,mx[k]),lft=lmx[k]; else res=max(res,max(mx[k],lft+rmx[k])), lft=max(lft,lmx[k]); return; } if(a<=mid) Query(a,b,lc); if(b>mid) Query(a,b,rc); } int main() { scanf("%d%d%s",&n,&q,s+1); for(int i=1;i<=n;i++) pre[i]=pre[i-1]+(s[i]=='0'?-1:1); for(int i=n;i>=1;i--) suf[i]=suf[i+1]+(s[i]=='0'?-1:1); build(1,0,n+1); while(q--) { scanf("%d%d",&l,&r); res=lft=-INF;l--;r++; Query(l,r,1,0,n+1); printf("%d\n",res-pre[l]-suf[r]); } return 0; }
针老师题解里的树上倍增可能不太懂啊……