题面
题意:
一个长度为 n n n的数组 a a a,把他拆分成两个严格递增的数组,使得这两个数组的长度差值最小。无解输出 − 1 -1 −1.
思路:
对于
i
<
j
i<j
i<j并且
a
i
>
=
a
j
a_i>=a_j
ai>=aj那么说明
a
i
a_i
ai和
a
j
a_j
aj一定不能在同一个数组中,我们对于不能在同一组的连接一条无向边,构成一个无向图,如果这个图是二分图则说明有解,否则无解。
对于有解的情况,我们对于图中的连通块进行
D
P
DP
DP,由于这个图是二分图,因此每个连通块都可以划分成最多两个不在同一阵营的子集,假设第
i
i
i个连通块的两个子集大小分别为
w
i
1
w_{i1}
wi1和
w
i
2
,
w_{i2},
wi2,由于确定好一个数组之后另一个也确定了,因此我们让
D
P
i
j
DP_{ij}
DPij代表到第
i
i
i个连通块时第一个数组的大小为
j
j
j是否可行,最后我们遍历
D
P
n
u
m
,
k
DP_{num,k}
DPnum,k,其中
n
u
m
num
num是连通块的个数,
k
k
k代表第一个数组的个数,找到
∣
k
−
(
n
−
k
)
∣
|k-(n-k)|
∣k−(n−k)∣最小的可行
k
k
k即为答案。复杂度
O
(
n
2
)
O(n^2)
O(n2)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1e3+100;
typedef long long ll;
vector<ll> G[N];
ll color[N],a[N];
bool vis[N];
ll w[N][3];ll cnt;
ll num1,num2;
bool dfs(ll u,ll c){
color[u]=c;
for(auto v:G[u]){
if(color[v]==c) return 0;
if(color[v]==0&&!dfs(v,-c)) return 0;
}
return 1;
}
void dfs2(ll x,bool ok){
if(ok) num1++;
else num2++;
vis[x]=1;
for(auto v:G[x]){
if(!vis[v]) dfs2(v,!ok);
}
}
ll dp[N][N];
int main(){
ll T;
cin>>T;
while(T--){
cnt=0;
ll n;
cin>>n;
memset(vis,0,sizeof(vis));
memset(color,0,sizeof(color));
memset(dp,0,sizeof(dp));
memset(w,0,sizeof(w));
for(ll i=1;i<=n;i++){
cin>>a[i];
G[i].clear();
}
for(ll i=2;i<=n;i++){
for(ll j=1;j<i;j++){
if(a[j]>=a[i]){
G[j].push_back(i);
G[i].push_back(j);
}
}
}
bool flag=0;
for(ll i=1;i<=n;i++){
if(!color[i]){
if(!dfs(i,1)){
flag=1;break;
}
}
}
if(flag){
cout<<-1<<endl;
}
else{
for(ll i=1;i<=n;i++){
if(!vis[i]){
num1=num2=0;
dfs2(i,1);
w[++cnt][1]=num1;
w[cnt][2]=num2;
}
}
dp[0][0]=1;
for(int i=1;i<=cnt;i++){
for(int j=1;j<=n;j++){
if(j>=w[i][1]){
if(dp[i-1][j-w[i][1]]) dp[i][j]=1;
}
if(j>=w[i][2]){
if(dp[i-1][j-w[i][2]]) dp[i][j]=1;
}
}
}
ll ans=0x7f7f7f7f;
for(int i=0;i<=N-50;i++){
if(dp[cnt][i]) ans=min((ll)ans,abs((i)-(n-i)));
}
cout<<ans<<endl;
}
}
return 0;
}