P1410 子序列 题解
toolong114514 · · 题解
解题思路
这道题要求将原序列分为两部分,这启发我们想到二分图。
遍历一遍所有元素,将所有满足
如果染色全部成功,那么说明原序列能被划分成两段严格递增子序列。
这个时候直接输出 Yes! 或 No! 是存在谬误的,因为“原序列能被划分成两段严格递增子序列”是“原序列能被划分成两段均为
我们考虑一个可行性背包 DP。
记建好图后连通块总数为
定义
转移部分很好写,都是一些套路,就不展开了。
最后判断 Yes!,否则输出 No!。
时间复杂度约为
参考代码
#include<iostream>
#include<cstring>
using namespace std;
const int N=4e6+10;
struct node{
int to,nxt;
}edge[2*N];
int head[5005];
int cnt;
void add(int u,int v){
edge[++cnt]={v,head[u]};
head[u]=cnt;
}
bool vst[5005];
bool f[5005][5005];
int a[5005],col[5005];
int w[5005][2];
bool flag;
int n,bl;
void dfs(int x){
vst[x]=true;
w[bl][col[x]]++;
for(int i=head[x];i!=-1;i=edge[i].nxt){
if(!flag) return;
if(vst[edge[i].to]){
if(col[edge[i].to]!=col[x]^1){
flag=false;
return;
}
}else{
col[edge[i].to]=col[x]^1;
dfs(edge[i].to);
}
}
}
int main(){
ios::sync_with_stdio(false);
while(cin>>n){
memset(head,-1,sizeof(head));
memset(vst,0,sizeof(vst));
memset(col,0,sizeof(col));
memset(f,0,sizeof(f));
memset(w,0,sizeof(w));
cnt=bl=0;
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1;i<=n;i++){
for(int j=i+1;j<=n;j++){
if(a[i]>a[j]){
add(j,i);
add(i,j);
}
}
}
flag=true;
for(int i=1;i<=n;i++){
if(!vst[i]){
bl++;
dfs(i);
}
}
if(!flag) cout<<"No!\n";
else{
f[0][0]=1;
for(int i=1;i<=bl;i++){
for(int j=0;j<=n;j++){
if(j>=w[i][0]) f[i][j]|=f[i-1][j-w[i][0]];
if(j>=w[i][1]) f[i][j]|=f[i-1][j-w[i][1]];
}
}
if(f[bl][n/2]) cout<<"Yes!\n";
else cout<<"No!\n";
}
}
return 0;
}
本代码可以通过原题数据评测。
通过讨论区广泛认同的 hack 数据 的记录:record。
与之对比的错误二分图做法记录:record 以及 code。
Written by toolong114514 on 2024/8/24.