BZOJ1489: [HNOI2009]双递增序

138 篇文章 0 订阅
33 篇文章 0 订阅

题意去哪了我去

根据某题解的题意描述,题目应该是给一个序列,让你把他分成两个长度为n/2的序列,使得两个序列里的数权值和下标都是递增的,奇数的情况我不知道,但看起来数据n应该都是偶数的

做法有很多种,可以直接DP,la1la1la教了我一种图论做法(Orz la1la1la),然后窝想了想,打了一个 贪心+DP

先讲一下图论的做法,有道题和这个有点像,问的是至少要分成几个序列,那
可以发现这是一个最小链覆盖,建出他的反图(即x>=y且x位置在y的前面就连一条边),最长链的长度即最小链覆盖,那么如果最长链的长度为1直接Yes,大于2则输出No
然后讨论=2的情况
可知有若干对x>=y,那么对于一对x>=y,他们两个一定不会在同一个序列里面,那么图可以分成若干个联通块(为了方便,边是双向的),每个联通块扫一次可以知道这个联通块里的点有多少个在一个序列,多少个在另一个序列(因为分成两个序列有解,所以一定是二分图),然后背包一下判断是否能弄出一个长度为n/2的序列
复杂度 O(n2)

然后是我的做法(感觉我是讲不清楚的)
(在处理的过程中任意一步如果两个序列都放不了当前的ai,即无解)
首先处理出对于每个位置i,a[i~n]的最小值,然后从1开始扫,一直到最小的数之前,第二个序列是不能放数字的,这之前的所有数都放在第一个序列,然后将最小的放在第二个序列,然后后面的贪心决策也是类似这样。
特别的,如果第一个序列是空的(即刚开始或者刚被清空,清空操作下文会提到),当前的数一定放在第一个序列,因为有可能有两个数相同且都是某一段的最小值,这时候按上文的做法会错
对每个当前的数操作完后,判断一下是否两个序列尾都小于后面的最小值,是的话后面怎么放和前面已经无关了,类似上文处理联通块的做法,把两个序列目前的数都取出来,清空序列,取出来的数用来DP。
然后取完如果可能有解就背包一下判断是否能弄出一个长度为n/2的序列

code:(贪心+DP)

#include<set>
#include<map>
#include<deque>
#include<queue>
#include<stack>
#include<cmath>
#include<ctime>
#include<bitset>
#include<string>
#include<vector>
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<climits>
#include<complex>
#include<iostream>
#include<algorithm>
#define ll long long
using namespace std;

const int maxn = 2010;
int a[maxn],mn[maxn];
int d[maxn][2],num,f[maxn];
int tail1,tail2,n1,n2;
int n;

void read(int &x)
{
    char c;
    while(!((c=getchar())>='0'&&c<='9'));
    x=c-'0';
    while((c=getchar())>='0'&&c<='9') x=(x<<3)+(x<<1)+c-'0';
}

int main()
{
    int t;read(t);
    while(t--)
    {
        read(n);
        for(int i=1;i<=n;i++) read(a[i]);
        mn[n+1]=a[n]+1;
        for(int i=n;i>=1;i--) mn[i]=min(mn[i+1],a[i]);

        int i; num=0;
        tail1=tail2=mn[1]-1; n1=n2=0;
        for(i=1;i<=n;i++)
        {
            if(i==n)
            {
                if(tail1>=a[i]) n2++;
                else if(tail2>=a[i]) break;
                else n2++;
                continue;
            }
            if(!n1) tail1=a[i],n1++;
            else
            {
                if(a[i]==mn[i])
                {
                    if(tail2>=a[i]) break;
                    tail2=a[i]; n2++;
                }
                else
                {
                    if(tail1>=a[i]) break;
                    tail1=a[i]; n1++;
                }
            }
            if(mn[i+1]>tail1)
            {
                num++;
                d[num][0]=n1;
                d[num][1]=n2;
                n1=n2=0;
            }
        }
        if(i<=n) {printf("No!\n"); continue;}
        num++;
        d[num][0]=n1;
        d[num][1]=n2;
        memset(f,-1,sizeof f); f[0]=1;
        for(i=1;i<=num;i++)
        {
            for(int j=n/2;j>=0;j--)
            {
                if(f[j]==1) 
                {
                    if(j+d[i][0]<=n)f[j+d[i][0]]=1;
                    if(j+d[i][1]<=n)f[j+d[i][1]]=1;
                }
            }
        }
        if(f[n/2]==1) printf("Yes!\n");
        else printf("No!\n");
    }

    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值