1. 程式人生 > >UVA 1471 Defense Lines 防線

UVA 1471 Defense Lines 防線

https://vjudge.net/problem/UVA-1471

給一個長度為n的序列,要求刪除一個連續子序列,使剩下的序列有一個長度最大的連續遞增子序列。

例如  Sample Input

2

9

5 3 4 9 2 8 6 7 1

7

1 2 3 10 4 5 6

Output

4

6

思路1: 最簡單的想法是列舉起點j和終點i,然後數一數,分別向前或向後能延伸的最長長度,記為g(i)和f(i)。如果預先計算g和f,這樣時間複雜度為O(n^2)。
超時:

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <sstream>
#include <algorithm>
#include <iostream>
#include <cmath>
using namespace std;

int main() {
	int n,t,temp,tn;
	vector<int> nums;
	vector<int> f,g;
	scanf("%d", &t); 
	while(t--) 
	{
		cin>>n;
		tn=0;
		nums.resize(n);
		f.resize(n);
		g.resize(n);
		while(tn<n)
		{
			scanf("%d",&nums[tn++]);
		}
		tn=1;
		int icount=1;g[0]=1;
		while(tn<n)
		{
		   if(nums[tn-1]<nums[tn])
		   {
   			  g[tn]=++icount; 
   		   }	
   		   else
   		   {
   		   	 g[tn]=1;icount=1;
   		   }
   		   tn++;
		}
		icount=1;f[n-1]=1;tn=n-2;
		while(tn>-1)
		{
		   if(nums[tn]<nums[tn+1])
		   {
   			  f[tn]=++icount; 
   		   }	
   		   else
   		   {
   		   	  f[tn]=1;icount=1;
   		   }
   		   tn--;
		}
		int imax=0;
		for(int i=0;i<n;i++)
		{
			for(int j=i+1;j<n-1;j++)
			{
				if(g[i]<=f[j+1])
				imax=max(imax,g[i]+f[j+1]);
			}
		}
		cout<<imax<<endl;
	}
	return 0;
}

思路2:參見書242頁

可以先預處理出每一個點能往前和往後延伸的長度(g(i)和f(i))。然後列舉終點i,快速找一個g(j)最大的起點。如果有兩個候選的起點,一個j,一個j‘,A[j']<=A[j]且g[j']>g[j],那麼j一定可以排除,g(j')更大,而且更容易拼接。

固定i的情況下,所有有價值的(A[j],g(j))按照A[j]排序(A[j]相同的值保留g(j)大的那個),將會形成一個有序表,根據之前的結論g(j)是遞增的,有序,那麼二分查詢就派上用處了。

然後再考慮,變動i對之前的有序表的影響,i增加,把之前的A[i],g(i)加入到有序表中,如果滿足A[i']比它A[i]小且g(i')最大的二元組,即它前面的一個元素,滿足g(i')>=g(i),那麼這個元素不該保留。否則應該加入這個二元組,加入這個二元組之後,為了保持有序表的性質,還要往後檢查刪除一些g(i*)小的元素。時間複雜的(nlongn)

#include <cstdio>
#include <cstring>
#include <vector>
#include <queue>
#include <stack>
#include <sstream>
#include <algorithm>
#include <iostream>
#include <cmath>
#include <map>
#include <set>
using namespace std;

int main() {
	int n,t,temp,tn;
	vector<int> nums;
	vector<int> f,g;
	map<int,int> imap;
	scanf("%d", &t);
	while(t--) {
		cin>>n;
		tn=0;
		nums.resize(n);
		f.resize(n);
		g.resize(n);
		imap.clear();
		while(tn<n) {
			scanf("%d",&nums[tn++]);
		}
		tn=1;
		int icount=1;
		g[0]=1;
		while(tn<n) {
			if(nums[tn-1]<nums[tn]) {
				g[tn]=++icount;
			} else {
				g[tn]=1;
				icount=1;
			}
			tn++;
		}
		icount=1;
		f[n-1]=1;
		tn=n-2;
		while(tn>-1) {
			if(nums[tn]<nums[tn+1]) {
				f[tn]=++icount;
			} else {
				f[tn]=1;
				icount=1;
			}
			tn--;
		}
		int imax=1;
		imap.insert(make_pair(nums[0],g[0]));
		map<int,int>::iterator it,it1;
		for(int i=1; i<n; i++) {
			it=imap.lower_bound(nums[i]); //找到大於等於nums[i]的第一個
			it1=it;
			bool iskeep=true;
			if(it!=imap.begin()) {
				it--; //因為是列遞增,所以向前移動1個,就是小於nums[i]的
				imax=max(imax,f[i]+(it->second));
				iskeep = it->second< g[i]; //如果前面的大於或者等於這個num[i]那麼這個就是沒有的
				//另外,如果前面的大了,那麼後面的更大或者等於,必定大於g[i]所以不用處理後面的
			}
			if(iskeep) {
				//s.erase(cur);
				if(it->first==nums[i]) it->second=g[i]; //這個是為了處理鍵值相同插入失敗的情況
				it=imap.insert(make_pair(nums[i],g[i])).first; //注意這裡插入後返回的引數含義
				it++;
				while(it != imap.end() && it->second <= g[i] ) imap.erase(it++); //持續刪除,注意刪除後it的指向
			}
			//for(it=imap.begin();it!=imap.end();it++)
			//cout<<"("<<(*it).first<<" "<<(*it).second<<")  ";
			//cout<<endl;

		}
		cout<<imax<<endl;
	}
	return 0;
}

這裡要特別注意map的用法,它本身元素安裝關鍵字排序的。另外

1.ap在進行插入的時候是不允許有重複的鍵值的,如果新插入的鍵值與原有的鍵值重複則插入無效,可以通過insert的返回值來判斷是否成功插入。下面是insert的函式原型:
          pair<iterator, bool> insert(const value_type& x);
可以通過返回的pair中第二個bool型變數來判斷是否插入成功。

2. // map是關聯式容器,呼叫erase後,當前迭代器已經失效

// 正確的寫法
    for (itor = m.begin(); itor != m.end();)
    {
        if (itor->second == "def")
        {
            m.erase(itor++) ; // erase之後,令當前迭代器指向其後繼。
        }
        else
        {
            ++itor;
        }
    }

思路3 所以可以利用LIS的優化方法把該題的時間複雜的優化到O(nlogn)。方法仍是利用一個數組d[i]記錄長度為 i 的連續遞增序列的最後一個元素的最小值,顯然該序列是單調遞增的,所以上面紅色字型的操作可以通過二分查詢直接得到f[j]的值,進而得到一個可行的長度ans, 然後更新陣列d即可,更新的方法是如果以a[i]小於陣列d中記錄的與a[i]長度相同的序列的最後一個元素的值,那麼把這個值改為a[i], 即  d[f[i]] = min(a[i], d[f[i]]);  最終ans的最大值即為答案
我的理解d[I]表示的是長度為i的遞增子序列,最後一個元素的能夠達到的最小值。顯然這個序列遞增。

#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
 
using namespace std;
 
const int MAXN = 200050;
const int INF = 1 << 30;
int a[MAXN], f[MAXN], g[MAXN], d[MAXN];
 
int main()
{
    int t, n, i;
    scanf("%d", &t);
    while(t--)
    {
        scanf("%d", &n);
        for(i = 1; i <= n; i++)
            scanf("%d", &a[i]);
 
        f[1] = 1;
        for(i = 2; i <= n; i++)
            if(a[i] > a[i - 1])
                f[i] = f[i - 1] + 1;
            else
                f[i] = 1;
 
        g[n] = 1;
        for(i = n - 1; i > 0; i--)
            if(a[i] < a[i + 1])
                g[i] = g[i + 1] + 1;
            else
                g[i] = 1;
 
        int ans = 0;
        for(i = 0; i <= n; i++)
            d[i] = INF;         //d[i]的值全部賦值為INF,方便二分查詢和更新d[i]
        for(i = 1; i <= n; i++)
        {
            int len = (lower_bound(d + 1, d + 1 + i, a[i]) - (d + 1)) + g[i];
            ans = max(len, ans);
            d[f[i]] = min(a[i], d[f[i]]);
        }
        printf("%d\n", ans);
    }
    return 0;
}