1. 程式人生 > 實用技巧 >POJ2373 Dividing the Path

POJ2373 Dividing the Path

題意描述

Dividing the Path

在一條長為 \(L\) 的鏈上,有 \(n\) 個不同的特殊區間,要用多個長度為 \(2\times i(a\leq i\leq b)\) 的區間來覆蓋它們。

要求區間之間不能有重複,而且 \(1\)~\(L\) 段都要覆蓋到,同時每個特殊區間必須用同一個區間來覆蓋它。

求出區間的最少數量,如果無法達到輸出 \(-1\)

演算法分析

這...,就是 DP 吧。

\(f(i)\) 表示前 \(1\)~\(i\) 段全覆蓋的最小區間使用量。

顯然 \(i\)\(2\) 的倍數:

\(f(i)=min_{i-2\times b\leq j\leq i-2\times a}\{f(j)+1\}\)

但是還要求 \(i\) 不在某個特殊區間內部,因為特殊區間是不能被分割的。

那麼如何判斷某個點是否在一個特殊區間內部呢?

其實處理方式很簡單:

每讀入一個特殊區間就將那段區間的每個數都加 \(1\),如果某個點的數為 \(0\),就表示它不在某個特殊區間內部。

具體實現用差分

int vis[N];
memset(vis,0,sizeof(vis)); 
for(int i=1;i<=n;i++){
	int x=read(),y=read();
	vis[x+1]++;vis[y]--;
}
for(int i=1;i<=l;i++) vis[i]+=vis[i-1];

然後開開心心 TLE,原因是找 \(min\{f(j)\}\)

的時候遍歷了整個 \(2\times a\)~\(2\times b\),默默算算時間必定 TLE。

於是就需要一種資料結構記錄每一段 \(2\times a\)~\(2\times b\) 的最小值——單調佇列。

結束。

程式碼實現

#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<queue>
#define N 1000010
#define INF 0x3f3f3f3f
using namespace std;

int n,l,a,b,f[N],vis[N];
deque<int>q;

int read(){
	int x=0,f=1;char c=getchar();
	while(c<'0' || c>'9') f=(c=='-')?-1:1,c=getchar();
	while(c>='0' && c<='9') x=x*10+c-48,c=getchar();
	return x*f;
}

int main(){
	//freopen("divide.in","r",stdin);
	//freopen("divide.out","w",stdout);
	n=read(),l=read(),a=read(),b=read();
	memset(vis,0,sizeof(vis)); 
	for(int i=1;i<=n;i++){
		int x=read(),y=read();
		vis[x+1]++;vis[y]--;
	}
	for(int i=1;i<=l;i++)
		vis[i]+=vis[i-1];
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int i=2*a;i<=l;i+=2){
		while(!q.empty() && f[q.back()]>=f[i-2*a]) q.pop_back();
		q.push_back(i-2*a);
		while(!q.empty() && q.front()<i-2*b) q.pop_front();
		if(vis[i]) continue;
		if(!q.empty()) f[i]=f[q.front()]+1;
	}
	if(f[l]>=INF) puts("-1");
	else printf("%d\n",f[l]);
	//fclose(stdin);fclose(stdout);
	return 0;
}

完結撒花