D. Unmerge( 找规律 + 01背包 )
题目链接:https://codeforces.com/contest/1382/problem/D
题意:对于一个长度为2n的排列(排列是指一个没有重复元素,有顺序的正整数集合)(保证n为正整数),问:是否可以把这个排列分解成两个长度为n的队列,使得每次取出两个队列的较小队首后,恰好能还原成原来的排列( 即把归并排序后的序列拆成原来的两个序列,要求长度都是n )。如果可以,输出YES,否则输出NO
思路:思考的时候,发现一个问题:对于某一个极大值来讲,从它开始到下一个比它大的值必须要在一个队列里连续排列。
就拿这组样例说话:
3 2 6 1 5 7 8 4
如果3在第一个队列里了,那么2肯定也在同一个队列里并且紧随其后,才可能保证3,2连续被弹出,否则,如果3在第一个队列,2在第二个队列,那么一开始弹出的就不是3而是2,如果2在6后面,在6没有被弹出之前也不可能轮到2,构造均宣布失败。
那么,这么一个数列就被我们分成了若干个小段,每段由一个段首极大值和在它后面的若干个较小值组成。比如上面的例子,最终我们就将其分成了如下的段落:
[3,2],[6,1,5],[7],[8,4]
到了这里就容易想到:如果我们能从这些长度不同的段落中挑出任意段,使之恰好能塞满长度n的一个排列,那么我们就构造成功了。(题目不需要我们考虑段与段之间的顺序问题)比如上面的样例答案就是:
[3,2,8,4],[6,1,5,7]
所以好像是个背包问题?是恰好装满01背包。
代码:
#include<bits/stdc++.h>
using namespace std;
int price[4005],weight[4005];
int dp[4005],a[4005],n;
int main()
{
int T;cin>>T;
while ( T-- ) {
cin>>n;
for ( int i=1; i<=2*n; i++ ) scanf("%d",&a[i]);a[2*n+1]=0x3f3f3f3f;
int cnt=0,mx=0,tot=0;
for ( int i=1; i<=2*n+1; i++ ) {
if ( i==1 ) {
mx = a[i];
cnt = 1;
continue;
}
if ( a[i]<mx ) {
cnt++;
}
else {
price[tot] = cnt;
weight[tot++] = cnt;
mx = a[i];cnt=1;
}
}
memset(dp,-0x3f3f3f3f,sizeof(dp));
dp[0] = 0;
for ( int i=0; i<tot; i++ ) {
for ( int j=n; j>=1; j-- ) {
if ( j>=weight[i] ) {
dp[j] = max( dp[j], dp[j-weight[i]] + price[i] );
}
}
}
if ( dp[n]>0 ) cout << "yes" << endl;
else cout <<"no"<<endl;
}
return 0;
}