APIO2018解题报告

今年的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;
} 
View Code

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;
} 
View Code

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 }
View Code
原文地址:https://www.cnblogs.com/ZH-comld/p/10142415.html