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;
}