[洛谷]P3406 海底高鐵 (#模擬)

題目背景

大東亞海底隧道連接着廈門、新北、博艾、那霸、鹿兒島等城市,橫穿東海,耗資1000億博艾元,歷時15年,於公元2058年建成。憑藉該隧道,從廈門可以乘坐火車直達臺灣、博艾和日本,全程只需要4個小時。

題目描述

該鐵路經過N個城市,每個城市都有一個站。不過,由於各個城市之間不能協調好,於是乘車每經過兩個相鄰的城市之間(方向不限),必須單獨購買這一小段的車票。第i段鐵路連接了城市i和城市i+1(1<=i<N)。如果搭乘的比較遠,需要購買多張車票。第i段鐵路購買紙質單程票需要Ai博艾元。

雖然一些事情沒有協調好,各段鐵路公司也爲了方便乘客,推出了IC卡。對於第i段鐵路,需要花Ci博艾元的工本費購買一張IC卡,然後乘坐這段鐵路一次就只要扣Bi(Bi<Ai)元。IC卡可以提前購買,有錢就可以從網上買得到,而不需要親自去對應的城市購買。工本費不能退,也不能購買車票。每張卡都可以充值任意數額。對於第i段鐵路的IC卡,無法乘坐別的鐵路的車。

Uim現在需要出差,要去M個城市,從城市P1出發分別按照P1,P2,P3...PM的順序訪問各個城市,可能會多次訪問一個城市,且相鄰訪問的城市位置不一定相鄰,而且不會是同一個城市。

現在他希望知道,出差結束後,至少會花掉多少的錢,包括購買紙質車票、買卡和充值的總費用。

輸入格式

第一行兩個整數,N,M。

接下來一行,M個數字,表示Pi

接下來N-1行,表示第i段鐵路的Ai,Bi,Ci

輸出格式

一個整數,表示最少花費

輸入輸出樣例

輸入 #1複製

9 10
3 1 4 1 5 9 2 6 5 3
200 100 50
300 299 100
500 200 500
345 234 123
100 50 100
600 100 1
450 400 80
2 1 10

輸出 #1複製

6394

說明/提示

2到3以及8到9買票,其餘買卡。

對於30%數據 M=2

對於另外30%數據 N<=1000 ,M<=1000

對於100%的數據 M,N<=100000,Ai,Bi,Ci<=100000


思路

經過文化課的洗禮後,I,am back!

完了,文化課降智,這道黃題想了好久。。

廢話少說。對於第i個城市,與第i+1個城市相連的是第i段鐵路。所以任意兩個相鄰的站點購買同種車票有且僅有一種費用,通過第i段鐵路的價格,僅與在第i個城市和第i+1個城市之間的通過次數有關。若你不是很懂這句話,可以再讀一讀題目。

那我們不就只需要求某段鐵路的通過次數不就ok了?

對於每個區間,用一個for對其所經次數+1即可。如下代碼:

#include <stdio.h>
#include <iostream>
#define ll long long
#define maxn 100001
using namespace std;
ll n,m,s,p[maxn],a[maxn],b[maxn],c[maxn],vis[maxn];
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register ll i,j,k;
	cin>>n>>m;
	for(i=1;i<=m;i++)
	{
		cin>>p[i];//訪問第i個城市 
	}
	for(i=1;i<=n-1;i++)
	{
		cin>>a[i]>>b[i]>>c[i];//a[i]紙質單程票,b[i]辦卡的價格,c[i]是工本費 
	}
	for(i=1;i<=m-1;i++)
	{
		if(p[i]<=p[i+1])
			for(j=p[i];j<p[i+1];j++)//不能j<p[i+1]。因爲我們求的是鐵路的通過次數,而不是城市的通過次數,因此p[i+1]不能算在裏面 
			{
				vis[j]++;//記錄通過次數 
			}
		else
			for(j=p[i]-1;j>=p[i+1];j--)//同理 
			{
				vis[j]++;
			}
	}
	for(i=1;i<=n-1;i++)
	{
		s=s+min(a[i]*vis[i]/*買紙質票*/,(b[i]*vis[i]+c[i])/*辦卡*/);
	}
	cout<<s<<endl;
	return 0;
}

滿心歡喜地交上去,卻只有70分,後3個點TLE。

爲什麼會T?很簡單,我們的時間全部花費在了記錄vis[i]。如果|p[i]-p[i+1]|的值很大,這個程序的效率會大大降低,退化到近似於O(m^2)!

仔細想一想,我們真的需要這麼記錄嗎?

回憶我們在樹狀數組模版2的思路,對於每一個區間改操作,在其左端點的位置+區間的值,再在右端點+1的位置-區間的值。這不就是相當於差分數組嗎?那麼同理,對於區間[p[i],p[i+1]),是不是就可以仿照上面的思路,讓vis[p[i]]++,讓vis[p[i+1]]--(若p[i]>p[i+1]),最後再跑一遍前綴和使得vis數組的值不變呢?

#include <stdio.h>
#include <iostream>
#define ll long long
#define maxn 100001
using namespace std;
ll n,m,s,p[maxn],a[maxn],b[maxn],c[maxn],vis[maxn];
signed main()
{
	ios::sync_with_stdio(false);
	cin.tie(0);
	cout.tie(0);
	register ll i,j,k;
	cin>>n>>m;
	for(i=1;i<=m;i++)
	{
		cin>>p[i];//訪問第i個城市 
	}
	for(i=1;i<=n-1;i++)
	{
		cin>>a[i]>>b[i]>>c[i];//a[i]紙質單程票,b[i]辦卡的價格,c[i]是工本費 
	}
	for(i=1;i<=m-1;i++)
	{
		ll l,r;
		if(p[i]<=p[i+1])
		{
			l=p[i];
			r=p[i+1];
		}
		else
		{
			l=p[i+1];
			r=p[i];
		}
		vis[l]++;
		vis[r]--;
	}
	for(i=1;i<=n-1;i++)
	{
		vis[i]=vis[i]+vis[i-1];//別忘了再跑一遍前綴和,差分的逆運算是前綴和,所以跑一遍前綴和的目的是爲了保持原數不變 
	}
	for(i=1;i<=n-1;i++)
	{
		s=s+min(a[i]*vis[i]/*買紙質票*/,(b[i]*vis[i]+c[i])/*辦卡*/);
	}
	cout<<s<<endl;
	return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章