Codeforces 933A/934C 暴力/dp优化/枚举优化

题意:给你一个由 1 2 构成的串 你可以倒置任意l r的一个区间 问你倒置之后的最长不下降子序列是多长
思路:这题真是曲折,想了很久想不出思路,问cygg,看懂了cygg的n2的dp代码
又自己莽了个n3的代码 982ms勉强过了
然后借鉴cygg的dp优化 几十ms 也过了
然后问px px说他没用dp就n2枚举过了 自己很震惊 啊凭什么都是枚举pxgg就n2
然后仔细询问加对比,终于找到两个人思维所在差异
pxgg用一种更巧妙的方法更深入的认识了这个问题
因为要找最终目标序列就是 11111222221111112222这样一种形式,所以只要枚举中点(中间的2和1分界点),然后枚举左边1与2分界点 右边1与2分界点即可
巧妙之处在于:枚举中点k后 左边12和右边12中点可以同时枚举,减少了一层循环
其实在于:每一个k只枚举一次!而不是像枚举区间再枚举k一样 同一个k可能枚举很多次!

而自己的方法是 n2枚举倒置区间,再在区间里枚举222111分界的中点。比n3少一点。
刚刚想明白:自己的方法和pxgg的方法的差距在于:
pxgg的枚举21中点 只从左往右扫了一次,而自己的,随着枚举区间的不同,会反复枚举中间的21分界点k 导致多了将近一层循环!
下面是第一次的n3莽过算法

//省略头文件    该代码 982ms  0 memory 哭辽
const int maxn=2010;
int a[maxn];
int s1[maxn],s2[maxn];//从头开始1的数量和2的数量
int main() {
	int n;
    sd(n);
    rep(i,1,n) sd(a[i]);
    rep(i,1,n){
        s1[i]=s1[i-1]+(a[i]==1);
        s2[i]=s2[i-1]+(a[i]==2);
    }
    int ans=1;
    rep(i,0,n){//正常的不交换
        if(ans<s1[i]+s2[n]-s2[i])
            ans=s1[i]+s2[n]-s2[i];
    }
    rep(i,1,n-1){
        rep(j,i+1,n){//交换的区间为i j
            int x=s1[i-1]+s2[n]-s2[j];//两边部分
            int y=0;
            rep(k,i-1,j){
                if(y<s2[k]-s2[i-1]+s1[j]-s1[k]){
                    y=s2[k]-s2[i-1]+s1[j]-s1[k];
                }
            }
            if(ans<x+y) ans=x+y;
        }
    }
    printf("%d\n",ans);
	return 0;
}

下面是加上cygg的dp优化的n2 用空间换时间

//省略头文件  41ms左右! Memory 15m
const int maxn=2010;
int a[maxn];
int s1[maxn],s2[maxn];//从头开始1的数量和2的数量
int dp[maxn][maxn];
int main() {
	int n;
    sd(n);
    rep(i,1,n) sd(a[i]);
    rep(i,1,n){
        s1[i]=s1[i-1]+(a[i]==1);
        s2[i]=s2[i-1]+(a[i]==2);
    }
    int ans=1;
    rep(i,0,n){//正常的不交换
        if(ans<s1[i]+s2[n]-s2[i])
            ans=s1[i]+s2[n]-s2[i];
    }
    //预处理一个前2后1的
    cl(dp,0);
    rep(i,1,n){
        if(a[i]==2){
            rep(j,i+1,n){
                if(a[j]==1){//若当前这个是1 就看是原来所有2 还是已有的21最多
                    dp[i][j]=1+max(s2[j]-s2[i-1],dp[i][j-1]);
                }else{
                    dp[i][j]=dp[i][j-1];
                }
            }
        }
    }
    rep(i,1,n-1){
        rep(j,i+1,n){//交换的区间为i j
            ans=max(ans,s1[i-1]+dp[i][j]+s2[n]-s2[j]);
        }
    }
    printf("%d\n",ans);
	return 0;
}

下面是pxgg不用dp的大法!
枚举222111中点 再枚举左右111222中点

//31ms  tql!!!
const int maxn=2010;
int a[maxn];
int s1[maxn],s2[maxn];//从头开始1的数量和2的数量
int main() {
	int n;
    sd(n);
    rep(i,1,n) sd(a[i]);
    rep(i,1,n){
        s1[i]=s1[i-1]+(a[i]==1);
        s2[i]=s2[i-1]+(a[i]==2);
    }
    int ans=1;
    rep(k,0,n){//k是第一段结束的2
        int tp1=0;
        rep(i,0,k){//1111222211112222  i是第一个结束的1
            tp1=max(tp1,s1[i]+s2[k]-s2[i]);
        }
        int tp2=0;
        rep(j,k,n){//j是第第二段结束的1
            tp2=max(tp2,s1[j]-s1[k]+s2[n]-s2[j]);
        }
        ans=max(ans,tp1+tp2);
    }
    printf("%d\n",ans);
	return 0;
}
发布了120 篇原创文章 · 获赞 12 · 访问量 5274

猜你喜欢

转载自blog.csdn.net/weixin_43735161/article/details/104630432