今年的APIO好邪啊。
T1铁人两项
题目大意
给一个无向图,问有多少三元组(三个元素两两不同)使得它们构成一条简单路径 。
题解
无向图这种东西不好直接处理,考虑点双缩点建圆方树。
然后就出现了一个比较神的做法,把方点点权设为点双大小,圆点点权设为-1,那么我们枚举三元组中的起点和终点,那么这条路径的权值就是可能的中间点个数。
为什么?考虑两种圆点。
1、起点和终点,因为它们已经被当成三元组中的元素了,所以我们要在和它们相邻的点双中减掉它们,所以这个减一是对的。
2、其他圆点,这些圆点会被相邻的两个点双算两次,所以我们要减一。
发现我们只需算出每个点的贡献和,然后可以算每个点被覆盖了多少次再乘上权值就可以了。
代码

#include<iostream> #include<cstdio> #define N 210002 using namespace std; typedef long long ll; ll ans,size[N],val[N],sum; int head[N],h[N],st[N],tot,tot1,top,n,m,num,dfn[N],low[N],tott; struct edge{ int n,to; }e[N*3],E[N*3]; inline void add(int u,int v){ e[++tot].n=head[u]; e[tot].to=v; head[u]=tot; } inline void add1(int u,int v){ E[++tot1].n=h[u]; E[tot1].to=v; h[u]=tot1; } void tarjan(int u,int fa){ dfn[u]=low[u]=++tott;st[++top]=u; val[u]=-1;sum++; for(int i=head[u];i;i=e[i].n)if(e[i].to!=fa){ int v=e[i].to; if(!dfn[v]){ tarjan(v,u); low[u]=min(low[u],low[v]); if(low[v]>=dfn[u]){ add1(u,++num);int x=0;val[num]=1; do{ x=st[top--]; val[num]++; add1(num,x); } while(x!=v); } } else low[u]=min(low[u],dfn[v]); } } void dfs(int u){ if(u<=n)size[u]=1; for(int i=h[u];i;i=E[i].n){ int v=E[i].to; dfs(v); ans+=2ll*size[u]*size[v]*val[u]; size[u]+=size[v]; } ans+=2ll*size[u]*(sum-size[u])*val[u]; } inline int rd(){ int x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } int main(){ n=rd();m=rd();int u,v;num=n; for(int i=1;i<=m;++i){u=rd();v=rd();add(u,v);add(v,u);} for(int i=1;i<=n;++i)if(!dfn[i]){sum=0;tarjan(i,0);dfs(i);} cout<<ans; return 0; }
T2选圆圈
题目大意
给定n个圆,每次挑出半径最大的圆删除,同时删除和这个圆有交的所有圆,知道所有圆都被删除,求出每个圆是被那个圆删掉的。
题解
如果把它看成一道一般的计算几何题,会很麻烦。
如果会KD-tree的话,就好说了,直接建立KD-tree,每次在上面暴力找就好了。
树上维护最左和最右,找的时候判一下如果和最左最右都没有交就不往下找了。
代码

#include<iostream> #include<cstdio> #include<algorithm> #define N 300002 using namespace std; typedef long long ll; int n,tot,ans[N]; inline ll rd(){ ll x=0;char c=getchar();bool f=0; while(!isdigit(c)){if(c=='-')f=1;c=getchar();} while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} return f?-x:x; } inline ll sq(ll x){return x*x;} int now_x; struct zb{ ll x[2],r;int id; bool operator <(const zb &b)const{ return x[now_x]<b.x[now_x]; } }a[N]; inline bool cmp(zb a,zb b){if(a.r!=b.r)return a.r>b.r;else return a.id<b.id;} struct node{ zb x;int ls,rs;ll mx[2],mi[2]; }tr[N]; inline void update(int p){ int ls=tr[p].ls,rs=tr[p].rs; for(int i=0;i<2;++i){ tr[p].mx[i]=tr[p].x.x[i]+tr[p].x.r; tr[p].mi[i]=tr[p].x.x[i]-tr[p].x.r; if(ls){ tr[p].mx[i]=max(tr[p].mx[i],tr[ls].mx[i]); tr[p].mi[i]=min(tr[p].mi[i],tr[ls].mi[i]); } if(rs){ tr[p].mx[i]=max(tr[p].mx[i],tr[rs].mx[i]); tr[p].mi[i]=min(tr[p].mi[i],tr[rs].mi[i]); } } } int build(int l,int r,int tag){ if(l>r)return 0; int mid=(l+r)>>1; now_x=tag; nth_element(a+l,a+mid+1,a+r+1); int p=++tot; tr[p].x=a[mid]; tr[p].ls=build(l,mid-1,tag^1); tr[p].rs=build(mid+1,r,tag^1); update(p); return p; } inline bool pd(int x,zb y){ if(tr[x].mx[0]<y.x[0]-y.r)return 0; if(tr[x].mx[1]<y.x[1]-y.r)return 0; if(tr[x].mi[0]>y.x[0]+y.r)return 0; if(tr[x].mi[1]>y.x[1]+y.r)return 0; return 1; } inline bool check(zb x,zb y){ ll dis1=sq(x.x[0]-y.x[0])+sq(x.x[1]-y.x[1]),dis2=sq(x.r+y.r); if(dis2>=dis1)return 1;else return 0; } void work(int p,zb x){ if(!ans[tr[p].x.id]&&check(tr[p].x,x))ans[tr[p].x.id]=x.id; if(tr[p].ls&&pd(tr[p].ls,x))work(tr[p].ls,x); if(tr[p].rs&&pd(tr[p].rs,x))work(tr[p].rs,x); } int main(){ n=rd(); for(int i=1;i<=n;++i)a[i].x[0]=rd(),a[i].x[1]=rd(),a[i].r=rd(),a[i].id=i; int root=build(1,n,0); sort(a+1,a+n+1,cmp); for(int i=1;i<=n;++i)if(!ans[a[i].id]){ ans[a[i].id]=a[i].id; work(root,a[i]); } for(int i=1;i<=n;++i)printf("%d ",ans[i]); return 0; }
T3新家
题目大意
给定n个四元组,表示每个商店的位置,类型,开门时间和关门时间,有若干次询问,每次问一个时间点上的一个位置的代价,代价指所有类型到这个点的最小距离的最大值。
上课时讲了一个NB的线段树分治的做法,但我现在仍不会。
这道题虽并不简单,但仍然有很多套路的地方在里面 。
题解
首先,一个区间我们把它拆成两个,在l和r+1处,然后把他们和询问放在一起按时间排序,这样我们就可以忽略时间的影响了。
然后我们考虑这样一个问题,给出一个数轴,里面有各种类型的点,给定询问点,求一个最小半径,使得半径以内有所有类型的点。
不难想到二分答案,然后直接做非常麻烦,考虑维护一个pre,记录上一个改类型的点出现的位置,这玩意我们可以用multiset维护。
当我们二分的答案mid合法时,需满足mid-pos<=pos-min(mid+1,n)后面的min是指后面区域内所有的pre,这个比较显然。
然后我们用线段树维护所有位置的min,每个节点用一个可删堆去维护在该位置的所有pre。
最好不要(或不能)离散化,要讨论好多种情况,直接动态开点就可以了。
优化
直接做是两个log的,瓶颈在查询上,去膜了kczno1的题解。
由于我们有了线段树,所以可以不用二分答案了,直接在线段树上二分,判一下mid+1是否合法,看一下是往左边走还是往右边走。
但为什么我的一个log跑的比两个log还慢?
代码

1 #include<iostream> 2 #include<cstdio> 3 #include<queue> 4 #include<set> 5 #include<algorithm> 6 #include<cstring> 7 #define N 300002 8 #define inf 2e8 9 using namespace std; 10 typedef long long ll; 11 int tr[N*32],Ls[N*32],Rs[N*32],n,k,qu,tot,ans[N],ji,tong[N],gan,top,wei[N*32],wen,root; 12 inline int rd(){ 13 int x=0;char c=getchar();bool f=0; 14 while(!isdigit(c)){if(c=='-')f=1;c=getchar();} 15 while(isdigit(c)){x=(x<<1)+(x<<3)+(c^48);c=getchar();} 16 return f?-x:x; 17 } 18 struct node{ 19 int tmp,pos,tim,tag; 20 bool operator <(const node &b)const{ 21 if(tim!=b.tim)return tim<b.tim; 22 else return tag<b.tag; 23 } 24 }q[N<<2]; 25 struct HESP{ 26 priority_queue<int,vector<int>,greater<int> >q1,q2; 27 inline void push(int x){q1.push(x);} 28 inline void del(int x){ 29 q2.push(x);while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop(); 30 } 31 inline int top(){return q1.top();} 32 inline int size(){return q1.size()-q2.size();} 33 }h[N*32]; 34 multiset<int>s[N]; 35 multiset<int>::iterator it,it2,it3; 36 void upd(int &cnt,ll l,ll r,int x,int x1,int x2){ 37 if(!cnt)cnt=++top; 38 // cout<<cnt<<" "<<l<<" "<<r<<endl; 39 if(l==r){ 40 // cout<<l<<" "; 41 if(!wei[cnt])wei[cnt]=++wen; 42 if(~x1)h[wei[cnt]].push(x1); 43 if(~x2)h[wei[cnt]].del(x2); 44 if(h[wei[cnt]].size())tr[cnt]=h[wei[cnt]].top();else tr[cnt]=inf; 45 // cout<<r<<endl; 46 return; 47 } 48 int mid=(l+r)>>1; 49 if(mid>=x)upd(Ls[cnt],l,mid,x,x1,x2); 50 else upd(Rs[cnt],mid+1,r,x,x1,x2); 51 tr[cnt]=min(tr[Ls[cnt]],tr[Rs[cnt]]); 52 } 53 inline int query(int pos){ 54 int cnt=1,l=1,r=top,now=inf; 55 while(l<r){ 56 int mid=(l+r)>>1;int tmp=min(now,tr[cnt<<1|1]); 57 if(pos<=mid&&tmp+mid>=2*pos)cnt=Ls[cnt],r=mid,now=tmp; 58 else cnt=Rs[cnt],l=mid+1; 59 } 60 return l-pos; 61 } 62 int check(int cnt,ll l,ll r,int L,int R){ 63 if(!cnt)return inf; 64 if(l>=L&&r<=R)return tr[cnt]; 65 int mid=(l+r)>>1,ans=2e9; 66 if(mid>=L)ans=min(ans,check(Ls[cnt],l,mid,L,R)); 67 if(mid<R)ans=min(ans,check(Rs[cnt],mid+1,r,L,R)); 68 return ans; 69 } 70 int main(){ 71 n=rd();k=rd();qu=rd();int x,t,a,b; 72 memset(tr,0x7f,sizeof(tr)); 73 for(int i=1;i<=n;++i){ 74 x=rd();t=rd();a=rd();b=rd(); 75 q[++tot]=node{t,x,a,1}; 76 q[++tot]=node{t,x,b+1,-1}; 77 } 78 for(int i=1;i<=qu;++i){ 79 q[++tot].pos=rd(),q[tot].tim=rd(),q[tot].tmp=i; 80 q[tot].tag=inf; 81 } 82 sort(q+1,q+tot+1); 83 for(int i=1;i<=k;++i){ 84 s[i].insert(inf);s[i].insert(-inf); 85 upd(root,1,inf,inf,-inf,-1); 86 } 87 sort(q+1,q+tot+1); 88 for(int i=1;i<=tot;++i){ 89 int pos=q[i].pos; 90 if(q[i].tag==1){ 91 if(!tong[q[i].tmp])ji++;tong[q[i].tmp]++; 92 it=s[q[i].tmp].upper_bound(pos);it2=it--; 93 upd(root,1,inf,*it2,pos,*it); 94 upd(root,1,inf,pos,*it,-1); 95 s[q[i].tmp].insert(pos); 96 } 97 else if(q[i].tag==-1){ 98 tong[q[i].tmp]--;if(!tong[q[i].tmp])ji--; 99 it=s[q[i].tmp].find(pos); 100 it2=it3=it;it2--;it3++; 101 upd(root,1,inf,*it3,*it2,pos); 102 upd(root,1,inf,*it,-1,*it2); 103 s[q[i].tmp].erase(it); 104 } 105 else{ 106 if(ji!=k){ans[q[i].tmp]=-1;continue;} 107 int l=pos,r=inf,as=-1; 108 while(l<=r){ 109 int mid=(l+r)>>1; 110 if(pos-check(1,1,inf,mid+1,inf)<=mid-pos){ 111 r=mid-1;as=mid; 112 }else l=mid+1; 113 } 114 ans[q[i].tmp]=as-pos; 115 // ans[q[i].tmp]=query(pos); 116 } 117 } 118 for(int i=1;i<=qu;++i)printf("%d ",ans[i]); 119 return 0; 120 }