1. 程式人生 > 實用技巧 >[HCTF 2018]WarmUp

[HCTF 2018]WarmUp

題目描述

  • 給定一個數軸,標號為\(0-L\)

  • \(0\)開始出發,每次可以向右移動\(S-T\)個單位償付

  • 在數軸中共有\(M\)個特殊點,求到達L最少經過的特殊點的數量

思路分析

\(1、\)首先,對於一個比賽題目,我們要學會觀察資料範圍,鑑於\(30%\)的資料,我們應該可以用暴力完全便利一遍,求出最小值來

\(2、\)但是,要是想拿滿分,數軸的長度\(L\)最大值為\(10^9\),開陣列一定會炸掉的QAQ

\(3、\)對於一個很大的距離來說,我們可以很艱難的想到的是離散化,只需要選出不會影響條件的相對距離就可以了,不需要看絕對距離

\(4、\)那麼我們就開始離散化,首先看題目,給定的移動範圍為\(S-T\)

,如果單看這兩個數的話,湊距離!

想到了沒有

沒錯,小凱的疑惑(順帶傳送門P3951 小凱的疑惑

我們可以得出,這個東東最大湊不出來的距離,那麼我們就可以設定一個離散化的值為\(S\times T\)

那麼我們就可以開始離散化了

int bas=s*t;
for(int i=1;i<=m;i++)//離散化 
{
	int w=stone[i]-stone[i-1];
	if(w>=bas) w=bas;
	lish[i]=lish[i-1]+w;
	vis[lish[i]]=1;//標記石子 
}

通過把他原先的石子相鄰的距離求出來,然後比較一下是否比這個離散化值大,如果很大,那麼就縮小,並且標記一下石子所在的位置

在這裡可能會有一個小疑問,為什麼要用離散化呢,因為離散化可以進行路徑壓縮,就比如你在兩個相隔很遠的石子間蹦來蹦去,沒有意義,既浪費時間又浪費空間,因為在這一段中它是沒有貢獻的,所以刪去不會產生影響

上個圖片歇歇眼睛

狀態轉移方程設定

離散化結束以後,這就是一個很簡單的線性\(dp\)

那麼我們可以設\(f[i]\)為當調到第\(i\)的位置上時,踩到石子次數的最小值

\[j \in [S,T] \]

\[f[i]=min\begin{cases} f[i-j]+1\ \ \ (vis[i]=1)\\ \\ \\f[i-j]\ \ \ (vis[i]=0) \end{cases}\]

程式碼實現

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<queue>
#include<stack>
#include<cmath>
#define int long long 
using namespace std;
const int N=1e6+9;
int l,s,t,m;
int stone[109];//每個石頭的位置
int f[N]; 
int lish[109];
int vis[N];
signed main()
{
	cin>>l>>s>>t>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>stone[i];
	}
	if(s==t)//判斷一下相等的情況,此時走的步數是一定的 
	{
		int cnt=0;
		for(int i=1;i<=m;i++)
		{
			if(stone[i]%s==0) cnt++;
		}
		cout<<cnt<<endl;
		return 0;
	}
	sort(stone+1,stone+1+m);//保證呈位置升序
	int bas=s*t;
	for(int i=1;i<=m;i++)//離散化 
	{
		int w=stone[i]-stone[i-1];
		if(w>=bas) w=bas;
		lish[i]=lish[i-1]+w;
		vis[lish[i]]=1;//標記石子 
	}
	l=lish[m]+bas; 
	int ans=0x3f3f3f3f;
	memset(f,0x3f3f3f3f,sizeof(f));
	f[0]=0;
	for(int i=1;i<=l;i++)
	{
		for(int j=s;j<=t;j++)
		{
			if(i>=j)
			{
				f[i]=min(f[i],f[i-j]+vis[i]);
			}
		}
	}
	for(int i=lish[m];i<=l;i++)
	{
		ans=min(f[i],ans);
	}
	cout<<ans<<endl;
	return 0;
}