目录
题目(老规矩用一本通的数据,试了好多遍真的弄不出来上标,不过上标的字母不太一样,实在不行可以通过链接看)
•对于每个坐标li(对不起还是没有标上去)的询问,遍历这些年份yi(同样抱歉),在对应类型的分段树中查询两点信息
本人的感受
虽然说时间给的比较足,但是还是很容易超时。一本通是过的,洛谷和LOJ应该也行。这题本身不算难也不需要多少数学基础,但是因为容易超时所以需要用算法进行优化。一本通上会等很久,但其实不超时:必须说这题在一本通里绝对是测试点第二多的(129个,能把人急死,第一多的有139个,还没做出来),目前前三快也要136秒,我的估计要450秒左右,不过只要在645秒之内就有可能不超时。
网址
一本通拓展题库:点击进入 一本通拓展题库15000:[APIO2018]新家:点击进入
洛谷题库:点击进入 洛谷P4632 [APIO2018] 新家:点击进入
BZOJ里好像没有但是我在一个叫LOJ的网站中看到了。LOJ:点击进入
LOJ#2585[APIO2018]新家:点击进入
题目(老规矩用一本通的数据,试了好多遍真的弄不出来上标,不过上标的字母不太一样,实在不行可以通过链接看)
15000:[APIO2018]新家
时间限制: 5000 ms 内存限制: 524288 KB
提交数: 34 通过数: 19
【题目描述】
五福街是一条笔直的道路,这条道路可以看成一个数轴,街上每个建筑物的坐标都可以用一个整数来表示。小明是一位时光旅行者,他知道在这条街上,在过去现在和未来共有 n𝑛 个商店出现。第 i𝑖 个商店可以使用四个整数 xi,ti,ai,bi𝑥𝑖,𝑡𝑖,𝑎𝑖,𝑏𝑖 描述,它们分别表示:商店的坐标、商店的类型、商店开业的年份、商店关闭的年份。
小明希望通过时光旅行,选择一个合适的时间,住在五福街上的某个地方。他给出了一份他可能选择的列表,上面包括了 q𝑞 个询问,每个询问用二元组(坐标,时间)表示。第 i𝑖 对二元组用两个整数 li,yi𝑙𝑖,𝑦𝑖 描述,分别表示选择的地点 li𝑙𝑖 和年份 yi𝑦𝑖。
现在,他想计算出在这些时间和地点居住的生活质量。他定义居住的不方便指数为:在居住的年份,离居住点最远的商店类型到居住点的距离。类型 t𝑡 的商店到居住点的距离定义为:在指定的年份,类型 t𝑡 的所有营业的商店中,到居住点距离最近的一家到居住点的距离。我们说编号为 i𝑖 的商店在第 y𝑦 年在营业当且仅当 ai≤y≤bi𝑎𝑖≤𝑦≤𝑏𝑖。注意,在某些年份中,可能在五福街上并非所有 k𝑘 种类型的商店都有至少一家在营业。在这种情况下,不方便指数定义为 −1。你的任务是帮助小明求出每对(坐标,时间)二元组居住的不方便指数。
【输入】
第一行包含三个整数 n𝑛 , k𝑘 和 q𝑞,分别表示商店的数量、商店类型的数量和(坐标,时间)二元组的数量。(1≤n,q≤3×105,1≤k≤n)(1≤𝑛,𝑞≤3×105,1≤𝑘≤𝑛)
接下来 n𝑛 行,每行包含四个整数 xi,ti,ai𝑥𝑖,𝑡𝑖,𝑎𝑖和bi𝑏𝑖 用于描述一家商店,意义如题面所述(1≤xi,ai,bi≤109,1≤ti≤k,ai≤bi)(1≤𝑥𝑖,𝑎𝑖,𝑏𝑖≤109,1≤𝑡𝑖≤𝑘,𝑎𝑖≤𝑏𝑖)
接下来 q𝑞 行,每行包含两个整数 li𝑙𝑖 ,和 yi𝑦𝑖 ,表示一组(坐标,时间)查询(1≤li,yi≤108)(1≤𝑙𝑖,𝑦𝑖≤108)
【输出】
对于每组询问输出一个整数,包含q𝑞个整数,依次表示对于 q𝑞 组(坐标,时间)询问求出的结果。
【输入样例】
4 2 4
3 1 1 10
9 2 2 4
7 2 5 7
4 1 8 10
5 3
5 6
5 9
1 10
【输出样例】
4
2
-1
-1
【提示】
样例输入2
2 1 3
1 1 1 4
1 1 2 6
1 3
1 5
1 7
样例输出2
0
0
-1
样例输入3
1 1 1
100000000 1 1 1
1 1
样例输出3
99999999
【样例解释】
在第一个样例中,有 4 家商店,共 2 种类型,还有 4 个询问。
对于第一个询问:小明在第 3 年住在坐标为 5 的地方。这一年中,编号为 1 和 2 的商店在营业,到编号为 1 的商店的距离为 2 ,到编号为 2 的商店距离为 4 ,所以最大距离为4。
对于第二个询问:小明在第 6 年住在坐标为 5 的地方。这一年中,编号为 1 和 3 的商店在营业,到编号为 1 的商店的距离为 2 ,到编号为 3 的商店距离为 2 ,所以最大距离为2。
对于第三个询问:小明在第 9 年住在坐标为 5 的地方。这一年中,编号为 1 和 4 的商店在营业,它们的类型都为 1,没有类型为 2 的商店在营业,所以答案为 −1 。
同样的情况出现在第四个询问中。
在第二个样例中,有 2 家商店,共 1 种类型,还有三个询问。 两家商店的类型都是 1 。在所有的询问中,小明均住在坐标为 1 的地方。 在前两个询问中,至少有一个商店在营业,所以答案为 0 ,在第三个询问中,两个商店都不在营业,所以答案为 −1 。
在第三个样例中,有 1 家商店和 1 个询问,两者之间的距离是 99999999 。
【数据范围与提示】
编译与评测:−O2−std=c++11−𝑂2−𝑠𝑡𝑑=𝑐++11
子任务 1(5 分):n,q≤400𝑛,𝑞≤400
子任务 2(7 分):n,q≤6×104,k≤400𝑛,𝑞≤6×104,𝑘≤400
子任务 3(10 分):n,q≤3×105𝑛,𝑞≤3×105,对于所有的商店ai=1,bi=108𝑎𝑖=1,𝑏𝑖=108
子任务 4(23 分):n,q≤3×105𝑛,𝑞≤3×105,对于所有的商店ai=1𝑎𝑖=1
子任务 5(35 分):n,q≤6×104𝑛,𝑞≤6×104
子任务 6(20 分):n,q≤3×105
分析
为了解决这个问题,我们可以采用离线处理和分段查询的策略。首先,我们需要按照年份对商店信息进行排序,然后针对每种商店类型,使用分段树或平衡树(如Splay Tree、Segment Tree)来维护每一年每个坐标上最近的同类型商店距离。这样可以在O(logN)的时间复杂度内完成单点更新和区间查询操作。
1. 数据预处理
•排序商店信息
首先,根据商店的开业年份ai对所有商店进行排序。这有助于我们按时间顺序处理商店的状态变化。
•类型映射
创建一个映射表,用来存储每种商店类型t对应的商店列表。这有助于我们针对每种类型独立处理问题。
2. 初始化结构
•分段树/区间树
为每种商店类型构建一个分段树,每个节点覆盖一段坐标范围,存储该范围内最靠近该坐标的营业商店的距离。初始时,树为空,因为尚未有任何商店开业。
3. 时间线遍历
•按年遍历
从最早的开业年份开始,逐年遍历直到最晚的关闭年份。分两步处理:
1.开业处理
对于每年新开业的商店,找到其所属的商店类型,将其坐标插入对应类型分段树的正确位置,更新距离信息。
2.关闭处理
同时移除当年关闭的商店,从分段树中删除相应节点,重新计算区间最远距离。
4. 处理询问
•按坐标排序询问
将所有询问按坐标li排序,这样可以连续处理同坐标的不同年份询问,减少重复计算。
•查询不方便指数
•对于每个坐标li(对不起还是没有标上去)的询问,遍历这些年份yi(同样抱歉),在对应类型的分段树中查询两点信息
1. 向左查询
找到最左边的营业商店,记录距离。
2. 向右查询
找到最右边的营业商店,记录距离。
3. 不方便指数即为这两个距离中的较大值。
如果查询不到商店(即该类型商店在该年未营业),标记结果为-1。
5. 输出结果
按顺序输出每个询问的不方便指数。
代码(C++版,其他语言的自己改)
#include<bits/stdc++.h>
#define INF 0x3f3f3f3f
#define N 300100
using namespace std;
int n,m,Q,bb,cc,tb,tc,ss,tt,ans[N],b[N],cnt[N],cl;
void chg(int now,int l,int r,int u,int v);
struct Shop
{
int cz,pos,type,ti;
bool operator < (const Shop &u) const
{
if(ti!=u.ti) return ti<u.ti;
return cz<u.cz;
}
}shop[N*3];
struct Pq
{
int tp,id;
priority_queue<int,vector<int>,greater<int> >a,b;
void cle(){for(;!b.empty() && a.top()==b.top();a.pop(),b.pop());}
int top(){cle();return a.top();}
void cg(){if(tp!=top()) tp=top(),chg(1,1,bb,id,tp);}
void push(int u){a.push(u);if(id) cg();}
void del(int u){b.push(u);if(id) cg();}
}pq[N],ri;
struct Node
{
int ls,rs,mn;
}node[N<<1];
map<int,int>mb;
multiset<int>se[N];
multiset<int>::iterator it,i2;
inline void add(int u,int v)
{
if(!cnt[v]) cl--;cnt[v]++;
se[v].insert(u);
i2=it=se[v].lower_bound(u);
it++,i2--;
pq[u].push(*i2);
if(it!=se[v].end()) pq[*it].del(*i2),pq[*it].push(u);
else ri.del(*i2),ri.push(u);
}
inline void del(int u,int v)
{
cnt[v]--;if(!cnt[v]) cl++;
i2=it=se[v].lower_bound(u);
it++,i2--;
pq[u].del(*i2);
if(it!=se[v].end()) pq[*it].del(u),pq[*it].push(*i2);
else ri.del(u),ri.push(*i2);
se[v].erase(--it);
}
inline void up(int now){int L=node[now].ls,R=node[now].rs;node[now].mn=min(node[L].mn,node[R].mn);}
void build(int now,int l,int r)
{
if(l==r)
{
node[now].mn=INF;
return;
}
int mid=((l+r)>>1);
node[now].ls=++tt;
build(tt,l,mid);
node[now].rs=++tt;
build(tt,mid+1,r);
up(now);
}
void chg(int now,int l,int r,int u,int v)
{
if(l==r)
{
node[now].mn=v;
return;
}
int mid=((l+r)>>1);
if(u<=mid) chg(node[now].ls,l,mid,u,v);
else chg(node[now].rs,mid+1,r,u,v);
up(now);
}
int ask(int now,int l,int r,int u,int v)
{
if(u<=l&&r<=v) return node[now].mn;
int mid=((l+r)>>1);
if(v<=mid) return ask(node[now].ls,l,mid,u,v);
if(u>mid) return ask(node[now].rs,mid+1,r,u,v);
return min(ask(node[now].ls,l,mid,u,v),ask(node[now].rs,mid+1,r,u,v));
}
inline bool judge(int u,int v)
{
u=lower_bound(b+1,b+bb+1,u)-b;
v=upper_bound(b+1,b+bb+1,v)-b-1;
if(v<u) return 0;
if(v<bb && ask(1,1,bb,v+1,bb)<u) return 0;
if(ri.top()<u) return 0;
return 1;
}
inline int calc(int u)
{
if(cl) return -1;
int l,r,mid;
for(l=0,r=1e8;l<r;)
{
mid=((l+r)>>1);
if(judge(u-mid,u+mid)) r=mid;
else l=mid+1;
}
return l;
}
int main()
{
int i,j,p,q,o,z;
cin>>n>>m>>Q;cl=m;
for(i=1;i<=m;i++) se[i].insert(0),ri.push(0);
for(i=1;i<=n;i++)
{
scanf("%d%d%d%d",&z,&o,&p,&q);
b[++tb]=z;
shop[++ss].cz=1;
shop[ss].pos=z;
shop[ss].type=o;
shop[ss].ti=p;
shop[++ss].cz=3;
shop[ss].pos=z;
shop[ss].type=o;
shop[ss].ti=q;
}
for(i=1;i<=Q;i++)
{
scanf("%d%d",&p,&q);
shop[++ss].cz=2;
shop[ss].pos=p;
shop[ss].ti=q;
shop[ss].type=i;
}
sort(b+1,b+tb+1);
for(i=1;i<=tb;i++)
{
if(i==1 || b[i]!=b[i-1])
{
b[++bb]=b[i];
mb[b[i]]=bb;
}
}
for(i=1;i<=ss;i++) if(shop[i].cz!=2) shop[i].pos=mb[shop[i].pos];
for(i=1;i<=bb;i++) pq[i].id=i,pq[i].push(INF);
build(tt=1,1,bb);
sort(shop+1,shop+ss+1);
for(i=1;i<=ss;i=j)
{
z=shop[j=i].ti;
for(;j<=ss&&shop[j].ti==z&&shop[j].cz==1;j++)
add(shop[j].pos,shop[j].type);
for(;j<=ss&&shop[j].ti==z&&shop[j].cz==2;j++)
ans[shop[j].type]=calc(shop[j].pos);
for(;j<=ss&&shop[j].ti==z&&shop[j].cz==3;j++)
del(shop[j].pos,shop[j].type);
}
for(i=1;i<=Q;i++) printf("%d\n",ans[i]);
}
总结
遇到这种子任务众多且没啥难度的题,基本上都会卡。一定要优化算法,制定策略。它给你时间限制只要是这种题就可以用来调整时间复杂度。
什么是APIO?
我还真查了一下,APIO全称是Asia-Pacific Informatics Olympiad,,指亚洲与太平洋地区信息学奥林匹克竞赛。是一个面向亚太地区在校中学生的信息学学科竞赛.旨在给青少年提供更多的赛事机会,推动亚太地区的信息学奥林匹克的发展。该竞赛性质为区域性的网上准同步赛,在每年五月举办。APIO中国区由中国计算机学会主办。链接