今年的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*],E[N*];
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]=-;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=;val[num]=;
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]=;
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=;char c=getchar();bool f=;
while(!isdigit(c)){if(c=='-')f=;c=getchar();}
while(isdigit(c)){x=(x<<)+(x<<)+(c^);c=getchar();}
return f?-x:x;
}
int main(){
n=rd();m=rd();int u,v;num=n;
for(int i=;i<=m;++i){u=rd();v=rd();add(u,v);add(v,u);}
for(int i=;i<=n;++i)if(!dfn[i]){sum=;tarjan(i,);dfs(i);}
cout<<ans;
return ;
}

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=;char c=getchar();bool f=;
while(!isdigit(c)){if(c=='-')f=;c=getchar();}
while(isdigit(c)){x=(x<<)+(x<<)+(c^);c=getchar();}
return f?-x:x;
}
inline ll sq(ll x){return x*x;}
int now_x;
struct zb{
ll x[],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[],mi[];
}tr[N];
inline void update(int p){
int ls=tr[p].ls,rs=tr[p].rs;
for(int i=;i<;++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 ;
int mid=(l+r)>>;
now_x=tag;
nth_element(a+l,a+mid+,a+r+);
int p=++tot;
tr[p].x=a[mid];
tr[p].ls=build(l,mid-,tag^);
tr[p].rs=build(mid+,r,tag^);
update(p); return p;
}
inline bool pd(int x,zb y){
if(tr[x].mx[]<y.x[]-y.r)return ;
if(tr[x].mx[]<y.x[]-y.r)return ;
if(tr[x].mi[]>y.x[]+y.r)return ;
if(tr[x].mi[]>y.x[]+y.r)return ;
return ;
}
inline bool check(zb x,zb y){
ll dis1=sq(x.x[]-y.x[])+sq(x.x[]-y.x[]),dis2=sq(x.r+y.r);
if(dis2>=dis1)return ;else return ;
}
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=;i<=n;++i)a[i].x[]=rd(),a[i].x[]=rd(),a[i].r=rd(),a[i].id=i;
int root=build(,n,);
sort(a+,a+n+,cmp);
for(int i=;i<=n;++i)if(!ans[a[i].id]){
ans[a[i].id]=a[i].id;
work(root,a[i]);
}
for(int i=;i<=n;++i)printf("%d ",ans[i]);
return ;
}

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还慢?

代码

 #include<iostream>
#include<cstdio>
#include<queue>
#include<set>
#include<algorithm>
#include<cstring>
#define N 300002
#define inf 2e8
using namespace std;
typedef long long ll;
int tr[N*],Ls[N*],Rs[N*],n,k,qu,tot,ans[N],ji,tong[N],gan,top,wei[N*],wen,root;
inline int rd(){
int x=;char c=getchar();bool f=;
while(!isdigit(c)){if(c=='-')f=;c=getchar();}
while(isdigit(c)){x=(x<<)+(x<<)+(c^);c=getchar();}
return f?-x:x;
}
struct node{
int tmp,pos,tim,tag;
bool operator <(const node &b)const{
if(tim!=b.tim)return tim<b.tim;
else return tag<b.tag;
}
}q[N<<];
struct HESP{
priority_queue<int,vector<int>,greater<int> >q1,q2;
inline void push(int x){q1.push(x);}
inline void del(int x){
q2.push(x);while(q2.size()&&q1.top()==q2.top())q1.pop(),q2.pop();
}
inline int top(){return q1.top();}
inline int size(){return q1.size()-q2.size();}
}h[N*];
multiset<int>s[N];
multiset<int>::iterator it,it2,it3;
void upd(int &cnt,ll l,ll r,int x,int x1,int x2){
if(!cnt)cnt=++top;
// cout<<cnt<<" "<<l<<" "<<r<<endl;
if(l==r){
// cout<<l<<" ";
if(!wei[cnt])wei[cnt]=++wen;
if(~x1)h[wei[cnt]].push(x1);
if(~x2)h[wei[cnt]].del(x2);
if(h[wei[cnt]].size())tr[cnt]=h[wei[cnt]].top();else tr[cnt]=inf;
// cout<<r<<endl;
return;
}
int mid=(l+r)>>;
if(mid>=x)upd(Ls[cnt],l,mid,x,x1,x2);
else upd(Rs[cnt],mid+,r,x,x1,x2);
tr[cnt]=min(tr[Ls[cnt]],tr[Rs[cnt]]);
}
inline int query(int pos){
int cnt=,l=,r=top,now=inf;
while(l<r){
int mid=(l+r)>>;int tmp=min(now,tr[cnt<<|]);
if(pos<=mid&&tmp+mid>=*pos)cnt=Ls[cnt],r=mid,now=tmp;
else cnt=Rs[cnt],l=mid+;
}
return l-pos;
}
int check(int cnt,ll l,ll r,int L,int R){
if(!cnt)return inf;
if(l>=L&&r<=R)return tr[cnt];
int mid=(l+r)>>,ans=2e9;
if(mid>=L)ans=min(ans,check(Ls[cnt],l,mid,L,R));
if(mid<R)ans=min(ans,check(Rs[cnt],mid+,r,L,R));
return ans;
}
int main(){
n=rd();k=rd();qu=rd();int x,t,a,b;
memset(tr,0x7f,sizeof(tr));
for(int i=;i<=n;++i){
x=rd();t=rd();a=rd();b=rd();
q[++tot]=node{t,x,a,};
q[++tot]=node{t,x,b+,-};
}
for(int i=;i<=qu;++i){
q[++tot].pos=rd(),q[tot].tim=rd(),q[tot].tmp=i;
q[tot].tag=inf;
}
sort(q+,q+tot+);
for(int i=;i<=k;++i){
s[i].insert(inf);s[i].insert(-inf);
upd(root,,inf,inf,-inf,-);
}
sort(q+,q+tot+);
for(int i=;i<=tot;++i){
int pos=q[i].pos;
if(q[i].tag==){
if(!tong[q[i].tmp])ji++;tong[q[i].tmp]++;
it=s[q[i].tmp].upper_bound(pos);it2=it--;
upd(root,,inf,*it2,pos,*it);
upd(root,,inf,pos,*it,-);
s[q[i].tmp].insert(pos);
}
else if(q[i].tag==-){
tong[q[i].tmp]--;if(!tong[q[i].tmp])ji--;
it=s[q[i].tmp].find(pos);
it2=it3=it;it2--;it3++;
upd(root,,inf,*it3,*it2,pos);
upd(root,,inf,*it,-,*it2);
s[q[i].tmp].erase(it);
}
else{
if(ji!=k){ans[q[i].tmp]=-;continue;}
int l=pos,r=inf,as=-;
while(l<=r){
int mid=(l+r)>>;
if(pos-check(,,inf,mid+,inf)<=mid-pos){
r=mid-;as=mid;
}else l=mid+;
}
ans[q[i].tmp]=as-pos;
// ans[q[i].tmp]=query(pos);
}
}
for(int i=;i<=qu;++i)printf("%d\n",ans[i]);
return ;
}

APIO2018解题报告的更多相关文章

  1. CH Round #56 - 国庆节欢乐赛解题报告

    最近CH上的比赛很多,在此会全部写出解题报告,与大家交流一下解题方法与技巧. T1 魔幻森林 描述 Cortana来到了一片魔幻森林,这片森林可以被视作一个N*M的矩阵,矩阵中的每个位置上都长着一棵树 ...

  2. 二模13day1解题报告

    二模13day1解题报告 T1.发射站(station) N个发射站,每个发射站有高度hi,发射信号强度vi,每个发射站的信号只会被左和右第一个比他高的收到.现在求收到信号最强的发射站. 我用了时间复 ...

  3. BZOJ 1051 最受欢迎的牛 解题报告

    题目直接摆在这里! 1051: [HAOI2006]受欢迎的牛 Time Limit: 10 Sec  Memory Limit: 162 MBSubmit: 4438  Solved: 2353[S ...

  4. 习题:codevs 2822 爱在心中 解题报告

    这次的解题报告是有关tarjan算法的一道思维量比较大的题目(真的是原创文章,希望管理员不要再把文章移出首页). 这道题蒟蒻以前做过,但是今天由于要复习tarjan算法,于是就看到codevs分类强联 ...

  5. 习题:codevs 1035 火车停留解题报告

    本蒟蒻又来写解题报告了.这次的题目是codevs 1035 火车停留. 题目大意就是给m个火车的到达时间.停留时间和车载货物的价值,车站有n个车道,而火车停留一次车站就会从车载货物价值中获得1%的利润 ...

  6. 习题: codevs 2492 上帝造题的七分钟2 解题报告

    这道题是受到大犇MagHSK的启发我才得以想出来的,蒟蒻觉得自己的代码跟MagHSK大犇的代码完全比不上,所以这里蒟蒻就套用了MagHSK大犇的代码(大家可以关注下我的博客,友情链接就是大犇MagHS ...

  7. 习题:codevs 1519 过路费 解题报告

    今天拿了这道题目练练手,感觉自己代码能力又增强了不少: 我的思路跟别人可能不一样. 首先我们很容易就能看出,我们需要的边就是最小生成树算法kruskal算法求出来的边,其余的边都可以删掉,于是就有了这 ...

  8. NOIP2016提高组解题报告

    NOIP2016提高组解题报告 更正:NOIP day1 T2天天爱跑步 解题思路见代码. NOIP2016代码整合

  9. LeetCode 解题报告索引

    最近在准备找工作的算法题,刷刷LeetCode,以下是我的解题报告索引,每一题几乎都有详细的说明,供各位码农参考.根据我自己做的进度持续更新中......                        ...

随机推荐

  1. js上传视频(jquery.form.js)

    // 上传目标触发点 <input type="file" class="upvideo" name="upvideo" id=&qu ...

  2. 配置react-sass

    在配置react-sass时遇到很多坑其中 一条如果你的.scss文件失效 请一定要在fileloader之前配置该sass-loader 配置文件如下 基于你不熟悉webpack 容易出这个错误

  3. C#实现,C++实现,JS实现 阿拉伯数字金额转换为中文大写金额

    推荐在线编译器  ideone 1. C#实现 :带有负数处理 //把数字金额转换成中文大写数字的函数 //带有负值处理 function changeNumMoneyToChinese(money) ...

  4. [转帖]一键获取 所有连接过的wifi 密码

    cmd 一键获取 所有连接过的wifi 密码 转帖来源: http://www.cnblogs.com/hookjoy/p/5537623.html for /f "skip=9 token ...

  5. Zookeeper的作用,在Hadoop及hbase中具体作用

    什么是Zookeeper,Zookeeper的作用是什么,在Hadoop及hbase中具体作用是什么 一.什么是Zookeeper ZooKeeper 顾名思义 动物园管理员,他是拿来管大象(Hado ...

  6. mapreduce join

    MapReduce Join 对两份数据data1和data2进行关键词连接是一个很通用的问题,如果数据量比较小,可以在内存中完成连接. 如果数据量比较大,在内存进行连接操会发生OOM.mapredu ...

  7. Prism框架研究(三)

    这一篇主要用来介绍一下基于Prism Library中的核心服务以及如何配置Container,还有一个重要的部分是如何管理各个组件之间的依赖性,下面就这些内容来做一一的介绍. 1 Prism中的核心 ...

  8. MyBatis基础:MyBatis数据基本操作(2)

    1. MyBatis映射器 2. MyBatis数据基本操作 示例项目结构: <project xmlns="http://maven.apache.org/POM/4.0.0&quo ...

  9. 老男孩python学习自修第十一天【内置函数】

    1.基本内置函数 help() 帮助文档 dir() 列出当前文件的所有变量和方法 vars() 列出当前文件的所有变量及其值 type() 返回变量的类型 id() 返回变量的内存地址 len() ...

  10. Druid数据库连接池

    一.Druid连接池的创建 package cn.zhouzhou; import java.io.IOException; import java.io.InputStream; import ja ...