题意去哪了我去
根据某题解的题意描述,题目应该是给一个序列,让你把他分成两个长度为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;
}