这个题目是Sherlock推荐我做的。
题目意思是给一个长度为n(n <= 100000)的序列,里面的数满足1 <= a[i] <= n。要找一个最长的连续子串,使得这个子串是1..k的一个排列。

昨天晚上mmd想了个n^(3/2)的算法,今天Sherlock想了个nlogn的算法。我一直在考虑有没有O(n)的算法。我觉得应该有的。
今天晚上吃饭的时候忽然想出来了。回来写了果然AC。

下面是我的算法:
注意到满足题目要求的序列必有如下性质:
1。最大值等于长度
2。必含1
3。序列和为k * (k + 1) / 2
4。序列内元素不重复
如果一个序列同时满足性质3,4,那么一定符合题目要求。
于是如果做了O(n)的预处理,可以用O(1)的时间验证某序列是否满足题目要求。
然后我的算法就顺理成章了:
1。预处理s[i],为序列的部分和
2。预处理rl[i],为从i开始往右,最长的不重复序列的末端的下标
3。预处理m[i],为从i开始,到i右边的第一个1(或者最右端),这一段数的最大值。
4。枚举左端点lp,则lp到其右边的第一个1(或者最右端)中的最大值为m[lp]。把m[lp]作为序列长度,则序列的右端点为lp + m[lp] - 1。利用s[i]和rl[i]数组可以验证这段序列是否满足题目要求,若满足,就更新最优解。
5。把输入序列反过来,重复步骤1-4。
以上每步的时间复杂度都是O(n),故算法总的时间复杂度也是O(n)。

下面是我的代码

/*************************************************************************
Author: WHU_GCC
Created Time: 2008-1-4 18:06:14
File Name: spoj744.cpp
Description: 
***********************************************************************
*/

#include 
<iostream>
using namespace std;

#define out(x) (cout << #x << ": " << x << endl)
typedef 
long long int64;
const int maxint = 0x7FFFFFFF;
const int64 maxint64 = 0x7FFFFFFFFFFFFFFFLL;
template 
<class T> void show(T a, int n) for (int i = 0; i < n; ++i) cout << a[i] << ' '; cout << endl; }
template 
<class T> void show(T a, int r, int l) for (int i = 0; i < r; ++i) show(a[i], l); cout << endl; }

const int maxn = 100010;

int n;
int a[maxn];
int s[maxn];
int rl[maxn];
int hash[maxn];
int m[maxn];

int calc()
{
    s[
0= 0;
    
for (int i = 1; i <= n; i++)
        s[i] 
= a[i] + s[i - 1];

    memset(hash, 
0sizeof(hash));
    
for (int i = 1, j = 1; i <= n; i++)
    
{
        
while (j <= n && hash[a[j]] == 0)
            hash[a[j
++]] = 1;
        rl[i] 
= j - 1;
        hash[a[i]] 
= 0;
    }


    
for (int i = n; i >= 1; i--)
        
if (a[i] == 1)
            m[i] 
= 1;
        
else if (i == n)
            m[i] 
= a[i];
        
else
            m[i] 
= max(m[i + 1], a[i]);

    
int ret = 0;
    
for (int i = 1; i <= n; i++)
        
if (rl[i] >= i + m[i] - 1 && s[i + m[i] - 1- s[i - 1== m[i] * (m[i] + 1/ 2)
            ret 
>?= m[i];
    
return ret;
}


int main()
{
    scanf(
"%d"&n);
    
for (int i = 1; i <= n; i++)
        scanf(
"%d"&a[i]);
    
int ans = calc();
    reverse(a 
+ 1, a + n + 1);
    ans 
>?= calc();
    printf(
"%d\n", ans);
    
return 0;
}


posted on 2008-01-04 20:45 Felicia 阅读(846) 评论(11)  编辑 收藏 引用 所属分类: 杂题
Comments
  • # re: [杂题]SPOJ744
    wywcgs
    Posted @ 2008-01-04 22:57
    和我的想法很像,呵呵,缘分阿  回复  更多评论   
  • # re: [杂题]SPOJ744
    Felicia
    Posted @ 2008-01-05 16:10
    嘿嘿,看来我们真是有缘  回复  更多评论   
  • # re: [杂题]SPOJ744[未登录]
    1
    Posted @ 2008-01-06 12:00
    这样做如何:
    #define N 10000
    int a[N];
    bool fis[N];
    memset(fis, false, sizeof(fis));
    对于i, 由于1《=a[i] 《= n,所以,如果a[i-1]=a[i]-1,并且
    a[i]的前a[i]-1个元素刚好是1....a[i-1]的最后一个元素,那么a[i],就分设置fis[i] = true ,or false;
    这个做完后,再扫描一次,看看fis[i]为true的i中,哪个a[i]最大。
      回复  更多评论   
  • # re: [杂题]SPOJ744
    Felicia
    Posted @ 2008-01-06 13:52
    感觉你说得不是很清楚
    并且
    a[i]的前a[i]-1个元素刚好是1....a[i-1]的最后一个元素,那么a[i],就分设置fis[i] = true ,or false;

    这两句是什么意思?
    而且有 N <= 100000,貌似你定义错了
      回复  更多评论   
  • # re: [杂题]SPOJ744
    2
    Posted @ 2008-01-06 15:21
    #define N 100000
    简单点定义一个bool数组fis[N];
    如果a[1...n]中存在从1到a[i]连续a[i]个数,那么fis[i] = true;or fis[i] =false;

    那么f[i+1] = (a[i]==a[i+1]-1)&&(fis[i]);

    。  回复  更多评论   
  • # re: [杂题]SPOJ744
    2
    Posted @ 2008-01-06 15:38
    看错题目了。。。。  回复  更多评论   
  • # re: [杂题]SPOJ744
    Felicia
    Posted @ 2008-01-06 21:30
    晕  回复  更多评论   
  • # re: [杂题]SPOJ744
    richardxx
    Posted @ 2008-01-23 18:57
    嗯,有点难度,好方法,受教了!!  回复  更多评论   
  • # re: [杂题]SPOJ744
    Felicia
    Posted @ 2008-01-23 19:34
    看了你的blog了,真的非常赞!  回复  更多评论   
  • # re: [杂题]SPOJ744
    gnaggnoyil
    Posted @ 2009-03-04 12:29
    Orz wh大牛.  回复  更多评论   
  • # re: [杂题]SPOJ744
    吴豪
    Posted @ 2009-04-29 15:55
    @gnaggnoyil
    .......................  回复  更多评论   

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理