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

猜你喜欢

转载自blog.csdn.net/qiang_____0712/article/details/84944901