AtCoder Beginner Contest 254 G,H
G
下面“换乘”的定义是从一栋楼的某层到另一栋楼的相同一层,题中说这样耗时为。
首先如果,那么我们不妨将和以及和交换顺序。此时我们就假定出发的楼层一定低于(或相等)于到达的楼层。
有一个显然的结论:
在最优路线中,我们只能朝上走,不会朝下走。
通过这个显然的结论可以得到一个显然的推论:
答案等于高差()加上最少换乘电梯次数。
前面这个是常量,所以我们只关系最少换乘电梯的次数。
貌似这个可以用倍增解决,比如设表示从层出发,换乘次最高可以到达的层数。
然后一开始和最终都要换乘楼房,所以还要将答案加上。
但这样有一个问题——我们最终乘坐的电梯和最优解是同一个,也就意味着最后我们没必要换乘楼房,那么此时就不能加上;同理,一开始我们乘坐的电梯可能和最优解相同。
如何解决?显然要将出发、到达和中间的电梯分开考虑。那么此时我们需要另一个观察:
开始时,我们尽量不要离开这栋楼,也就是,如果有往上的电梯,那么我们就一直往上。
结束前,我们尽早到达最终的楼房,然后再达电梯一路向上。
大致就是下图的流程:
这样,我们只统计中间最少需要的换乘次数,再加上即可。
时间复杂度为,注意要将楼层离散化。
#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl
#define x1 x114514
#define x2 x1919810
#define y1 y114514
#define y2 y1919810
const int maxn=1000005,maxlog=22;
int n,m,q;
int a[maxn],b[maxn],c[maxn];
int x1[maxn],x2[maxn],y1[maxn],y2[maxn];
std::map<int,int> cnt[maxn];
int mx[maxn],nxt[maxn][maxlog];
std::set<std::pair<int,int>> S[maxn];
using iter=std::set<std::pair<int,int>>::iterator;
int main() {
scanf("%d%d%d",&n,&m,&q);
std::vector<int> numbers;
for(int i=1;i<=m;i++) {
scanf("%d%d%d",&a[i],&b[i],&c[i]);
numbers.push_back(b[i]);
numbers.push_back(c[i]);
}
for(int i=1;i<=q;i++) {
scanf("%d%d%d%d",&x1[i],&y1[i],&x2[i],&y2[i]);
if(y1[i]>y2[i]) {
std::swap(x1[i],x2[i]);
std::swap(y1[i],y2[i]);
}
numbers.push_back(y1[i]);
numbers.push_back(y2[i]);
}
std::sort(numbers.begin(),numbers.end());
numbers.resize(std::unique(numbers.begin(),numbers.end())-numbers.begin());
auto number=[&](int ori)->int{
return std::lower_bound(numbers.begin(),numbers.end(),ori)-numbers.begin()+1;
};
for(int i=1;i<=m;i++) {
cnt[a[i]][b[i]]++;
cnt[a[i]][c[i]]--;
}
for(int i=1;i<=n;i++) {
if(cnt[i].empty()) continue;
int cur=0,lef=-1;
for(auto item : cnt[i]) {
if(lef==-1) lef=item.first;
cur+=item.second;
if(!cur) {
int rig=item.first;
// debug(lef); debug(rig);
S[i].insert({lef,rig});
lef=number(lef),rig=number(rig);
mx[lef]=std::max(mx[lef],rig);
lef=-1;
}
}
}
for(int i=0;i<(int)numbers.size();i++) {
//real number : i+1
nxt[i+1][0]=std::max({i+1,nxt[i][0],mx[i+1]});
}
for(int i=1;i<maxlog;i++) {
for(int j=1;j<=(int)numbers.size();j++) {
nxt[j][i]=nxt[nxt[j][i-1]][i-1];
}
}
for(int i=1;i<=q;i++) {
//求start,end,from,to
int start=y1[i],end=y2[i],from,to;
//找from和to,如果找不到,那from=start,to=end(防止x1[i]=x2[i],y1[i]=y2[i]这种情况)
iter it;
it=S[x1[i]].lower_bound({start+1,-1});
if(it==S[x1[i]].begin()||(--it)->second<start) {
from=start;
} else {
from=it->second;
}
it=S[x2[i]].lower_bound({end+1,-1});
if(it==S[x2[i]].begin()||(--it)->second<end) {
to=end;
} else {
to=it->first;
}
//debug(start); debug(end); debug(from); debug(to);
if(number(to)<=number(from)) {
//有交集,直接换乘
printf("%d\n",end-start+(x1[i]!=x2[i]));
continue;
}
int floor=number(to),cnt=0,pos=number(from);
for(int j=maxlog-1;~j;j--) {
if(nxt[pos][j]<floor) {
pos=nxt[pos][j];
cnt+=1<<j;
}
}
cnt++; pos=nxt[pos][0];
if(pos<floor) {
printf("-1\n"); continue;
}
cnt++;//换乘,注意由于之前我们统计的是搭乘电梯数,此时的cnt已经加上了1
//默认 end > start 否则交换
printf("%d\n",end-start+cnt);
}
return 0;
}
这题程序很难写对(H也一样),所以下面给几个样例:
Sample 1:
2 1 3
1 232 333
1 222 1 222
1 222 2 222
2 333 2 232
Answer :
0
1
103
Sample 2:
3 5 5
1 1610 1720
1 1710 1780
2 1380 1650
3 1550 1570
3 1640 1730
1 1380 1 1780
3 1640 2 1780
1 1580 2 1700
1 1720 1 1730
2 1730 3 1570
Answer:
402
142
123
10
163
H
自己想了一个单log的做法,应该比题解快吧。
通常这样乘二和除二下取整的,我们可以使用Trie。
在这题中,我们对数组的每个数创建0-1Trie,记为TrieA。但与平时不同,我们建立Trie的时候,不能加入前导。比如对于Sample A的数组,我们构建如下的Trie:
在这个Trie上,我们对某个数乘二就相当于在其末尾加一个。比如样例中数组,将乘后,Trie就会变成这个样子:
在这个Trie上,我们对某个数除以二下取整,就相当于将某尾一位删掉,比如样例中数组,将除以下取整后,Trie就会变成这个样子:
在这个Trie上,我们肯定先进行除以二下取整的操作,再进行乘二操作。(否则你先乘二,之后除以二相当于每进行任何操作)那么我们对一个数进行变换,就相当于将这个节点往祖先走若干步,再向左儿子(也就是数位为的边,相当于乘二)走几步。比如将先除以二,再乘以二,就相当于在Trie上进行如下变换:
那么,我们也按相同的方法对构建TrieB,那么我们就是对TrieA进行上面的操作,使其和TrieB相同,并要使得操作尽可能少。
首先我们考虑什么时候会是。
此时我们可以得到一个充要条件:
对于某个点,如果我们在TrieA中有个点在其右子树中,在TrieB中有个点在其右子树中,并且,则无解。
因为在右子树中意味着这一位为,那么在右子树外的点一定不能通过进入右子树中(因为乘二是在某尾加上,没法在某尾加上),所以我们右子树的点只能进行内部的匹配,那如果TrieA中的点不够TrieB需要的,自然就无解了。并且容易发现这个条件是充分的。(之前我以为是无解,导致wa了个点,反例见后文下发样例二)
那么剩下的情况就合法了。但如何方便的统计答案?
可以发现,假设对于某个点,我们TrieB中需要个数在这个子树中,而TrieA中有个,那么我们需要移进或者移出 个数就可以了。
那么可以对Trie上每个点分别统计贡献,时间复杂度为。
具体的判断和统计答案的程序如下:
void match(int pos1,int pos2) {
if(!pos1&&!pos2) return;
int al=trieA.val[pos1].lson,ar=trieA.val[pos1].rson;
int bl=trieB.val[pos2].lson,br=trieB.val[pos2].rson;
if(br&&trieA.val[ar].siz<trieB.val[br].siz) {
printf("-1\n");
exit(0);
}
ans+=(ll)abs(trieA.val[pos1].siz-trieB.val[pos2].siz);
match(al,bl); match(ar,br);
}
整体程序如下,还是很简洁的:
#include<bits/stdc++.h>
#define debug(...) std::cerr<<#__VA_ARGS__<<" : "<<__VA_ARGS__<<std::endl
using ll=long long;
const int maxn=100005,maxlog=40;
int n;
ll a[maxn],b[maxn];
ll ans;
struct trie {
struct node {
int lson,rson;
int siz,tag;
}val[maxn*maxlog];
int cur,root;
int add() {
cur++;
val[cur].lson=val[cur].rson=0;
val[cur].siz=val[cur].tag=0;
return cur;
}
trie() {
cur=0; root=add();
}
void insert(int pos,ll dig,const ll &num) {
val[pos].siz++;
if(dig<0) {
return;
}
if(num&(1ll<<dig)) {
if(!val[pos].rson) val[pos].rson=add();
insert(val[pos].rson,dig-1,num);
} else {
if(!val[pos].lson) val[pos].lson=add();
insert(val[pos].lson,dig-1,num);
}
}
void flush(int pos) {
if(!pos) return;
int L=val[pos].lson,R=val[pos].rson;
flush(L); flush(R);
if(R==0&&!val[L].tag) val[pos].tag=0;
else val[pos].tag=1;
}
}trieA,trieB;
void match(int pos1,int pos2) {
if(!pos1&&!pos2) return;
int al=trieA.val[pos1].lson,ar=trieA.val[pos1].rson;
int bl=trieB.val[pos2].lson,br=trieB.val[pos2].rson;
if(br&&trieA.val[ar].siz<trieB.val[br].siz) {
printf("-1\n");
exit(0);
}
ans+=(ll)abs(trieA.val[pos1].siz-trieB.val[pos2].siz);
match(al,bl); match(ar,br);
}
int main() {
scanf("%d",&n);
for(int i=1;i<=n;i++) {
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;i++) {
scanf("%lld",&b[i]);
}
for(int i=1;i<=n;i++) {
ll limit=maxlog-1;
while(limit>=0&&!(a[i]&(1ll<<limit))) limit--;
trieA.insert(trieA.root,limit,a[i]);
}
for(int i=1;i<=n;i++) {
ll limit=maxlog-1;
while(limit>=0&&!(b[i]&(1ll<<limit))) limit--;
trieB.insert(trieB.root,limit,b[i]);
}
trieA.flush(trieA.root);
trieB.flush(trieB.root);
match(trieA.root,trieB.root);
printf("%lld\n",ans);
return 0;
}
这题也很容易写假,所以给出下面的几个附加样例,供自测:
Extra test 1 :
4
0 1 2 9
0 1 1 9
Answer : 1
Extra test 2 :
6
19 19 21 23 25 31
18 23 25 64 72 120
Answer : 18
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
· 25岁的心里话