洛谷P3403
神仙建模題。
Description
一棟有 \(h\) 層的樓,初始位置為 \(1\),有如下操作:
- 向上移動 \(x\) 層;
- 向上移動 \(y\) 層;
- 向上移動 \(z\) 層;
- 回到 \(1\) 層。
問可以到達的樓層數。
Solution
首先資料 \(h\le 2^{63}-1\) 暴搜顯然不行。然後就考慮樓層之間的關係。
比較容易想到的是,以樓層為點來建圖。但是如果直接暴力建每個樓層之間的關係的邊,同樣會因為資料範圍太大而爆炸。
所以我們換個思路,考慮給出的操作之間的關係。
我們可以先處理其中兩種上升操作,然後將這些情況求出來,再反過來找第三種上升操作能達到的新樓層。
這裡設 \(\text{dis}(i)\)
然後可以得出:
\(\begin{cases} \text{dis}[(i+y)\bmod x]=\min\{\text{dis}(i\bmod x)+y\}\\ \text{dis}[(i+z)\bmod x]=\min\{\text{dis}(i\bmod x)+z\} \end{cases}\)
這和我們的最短路更新方式很相似。
我們就可以考慮將 \(i+y,i+z\) 作為點,用最短路求出所有的 \(\text{dis}(i)\) \((i\in [0,x))\)。
接下來我們考慮 \(\text{dis}\)
如果我們知道只通過操作 \(2,3\) 能到達的最小高度,這說明在此基礎上能達到的剩下的樓層只需要由操作 \(1\) 來補全即可。
如果上面的話不理解可以手動模擬一下。而且可以保證沒有漏掉的情況。
所以求出所有的 \(\text{dis}\) 之後,答案就是 \(\large{\sum_{i=0}^{x-1}[\frac{(h-\text{dis}(i))}{x}+1]}\) \((\text{dis}(i)\le h)\)。
上面這個式子也可以手動模擬一下,應該不是很難理解。
至於為什麼每次答案要加一,因為我們每次統計答案要加上 \(\text{dis}(i)\) 這層本身。
Other things
但是還是存在 \(h\) 太大的問題,並且考慮到真正需要的下標都在 \([0,x)\) 範圍內,所以我們只在這個範圍內建邊就好了。
另外要注意,因為樓層高度範圍是 \([1,h]\),所以 \(\text{dis}(1)=1\)。
最後,如果 \(x,y,z\) 中有至少一個是 \(1\),就說明所有的樓層都可達,此時上面的都是扯淡,答案是 \(h\)。
剩下的細節都在程式碼裡(也沒什麼細節)。
Code
#include<queue>
#include<cmath>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<algorithm>
#define maxn 200100
#define INF 0x3f3f3f3f
#define int long long
using namespace std;
bool vis[maxn];
int h,x,y,z,tot,ans;
int Dis[maxn],head[maxn];
struct edge{int fr,to,dis,nxt;}e[maxn<<2];
int read(){
int s=0,w=1;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')s=(s<<1)+(s<<3)+ch-'0',ch=getchar();
return s*w;
}
void add(int fr,int to,int dis){
e[++tot]=(edge){fr,to,dis,head[fr]};head[fr]=tot;
}
void SPFA(){
memset(Dis,INF,sizeof Dis);
memset(vis,false,sizeof vis);
deque<int> q;q.push_back(1);
Dis[1]=1;vis[1]=true;
while(!q.empty()){
int u=q.front();
q.pop_front();vis[u]=false;
for(int i=head[u];i;i=e[i].nxt){
int to=e[i].to;
if(Dis[to]>Dis[u]+e[i].dis){
Dis[to]=Dis[u]+e[i].dis;
if(!vis[to]){
vis[to]=1;
if(!q.empty()&&Dis[q.front()]>Dis[to])q.push_front(to);
else q.push_back(to);
}
}
}
}
}
signed main(){
h=read();
x=read();y=read();z=read();
if(x==1||y==1||z==1){cout<<h;return 0;}
for(int i=0;i<x;i++)
add(i,(i+y)%x,y),add(i,(i+z)%x,z);
SPFA();
for(int i=0;i<x;i++)
if(Dis[i]<=h) ans+=(h-Dis[i])/x+1;
printf("%lld\n",ans);
return 0;
}