cf 811c Vladik and Memorable Trip

原题链接:http://codeforces.com/contest/811/problem/C

 

题意:将数组中的连续数字连成若干个“线段”(或者不连),其实就是区间。区间必须满足对于其中的任意数字,能够覆盖数组中的所有相等数字,比如对数组:1, 2, 5, 2 ,5,    [2, 5 ,2 ,5]是满足条件的区间,而[2, 5, 2]不是,因为它并没有包含所有的5.

题目求不相交的满足条件的区间内不同数字的异或和的最大值。

 

思路:这是一道普通的DP题,类似最长上升子序列,先求出每个数字的最长区间(左右端点),再定义dp[i]为左端点为1, 长度为i的区间所能得到的最大异或和。则dp[i]= dp[j-1]+res, j是被包含在[1, i]的满足条件的区间的左端点,res是对应的异或和。这么dp下去就好了。

 

看别人的题解时自己尝试了一下,dp部分是枚举所有满足条件的区间,WA了。思考后发现,这么做有个错误,事实上第一步求出来的区间不一定是对的,没有保证在区间内和区间外同时出现同一个数字(也就是不满足条件的)。而枚举数字的位置可以在上述情况发生(或者说,对应区间不在[1, i]内)时停止枚举。具体实现见代码。

 

AC代码:

#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;  
const int MAXN = 5005;     
int num[MAXN],dp[MAXN]; 
struct Edge{
    int l,r;
}edge[MAXN]; 
bool vis[MAXN];  
int main()  
{  
    int n,m;
    scanf("%d",&n);  
    dp[0]=0;
    for(int i=0;i<MAXN;i++) edge[i].l=edge[i].r=0; 
    for(int i = 1 ; i <= n ; ++i)  
    {  
        scanf("%d",&num[i]);  
        m=num[i];
        if(!edge[m].l)edge[m].l = i;  
        edge[m].r = i;  
    }  
    for(int i = 1 ; i <= n ; ++i)  
    {  
        dp[i] = dp[i-1];  
        memset(vis,0,sizeof(vis));  
        int st = edge[num[i]].l;
        int res = 0;  
        for(int j = i ; j >= 1 ; --j)  
        {  
            if(!vis[num[j]])  
            {  
                if(edge[num[j]].r>i)break;  //对应区间不在[1, i]内,停止枚举
                st = min(st,edge[num[j]].l);  
                res^=num[j];  
                vis[num[j]] = 1;  
            }  
            if(j<=st)dp[i] = max(dp[i] , dp[j-1]+res);//维护区间最值  
        }  
    }  
    printf("%d\n",dp[n]);  
}  

 

posted @ 2017-08-29 16:44  Bangbangbanana  阅读(214)  评论(0编辑  收藏  举报