百度AI小课堂-上升子序列(中等)(二分图染色+DP)

题面

题意:

一个长度为 n n n的数组 a a a,把他拆分成两个严格递增的数组,使得这两个数组的长度差值最小。无解输出 − 1 -1 1.

思路:

对于 i &lt; j i&lt;j i<j并且 a i &gt; = a j a_i&gt;=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(nk)最小的可行 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;
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值