【2019icpc Regional 南昌 B】A Funny Bipartite Graph 题解

题目大意

  给定一幅 n n n 个点的二分图。左边的每个点度数至少为 1 1 1 至多为 3 3 3,且左边每个点只会连向右边编号大于等于它的点。
  现在你要选择一些边,限制如下:

  • 右边的每一个点都要被覆盖到;
  • 有一个 01 矩阵 A n × n A_{n\times n} An×n,若 A i , j = 1 A_{i,j}=1 Ai,j=1 则表示左边第 i i i 个点和第 j j j 个点不能同时被覆盖到;
  • 对于左边的每一个点,如果它没被覆盖到则代价为 0 0 0,否则代价为 M i d i M_i^{d_i} Midi,其中 d i d_i di 表示被它覆盖了多少次。满足上面两条的情况下要使得代价最小。

  求最小代价,或输出无解。

   n ≤ 18 ,   1 ≤ M i ≤ 100 n \leq 18,~1 \leq M_i \leq 100 n18, 1Mi100
  多测, T ≤ 10 T \leq 10 T10
  1s

\\
\\
\\

题解

  考虑状压,首先会想到一个比较暴力的状压,设 f i , s L , s R f_{i,s_L,s_R} fi,sL,sR 表示左边考虑了前 i i i 个点,左边已选点集为 s L s_L sL,右边已选点集为 s R s_R sR,的最小代价。

  进而发现好难优化,好像无论如何都要记录两个集合?

  然后就需要发现题目的性质:考虑了左边前 i i i 个点的时候,左边后 n − i n-i ni 个点一定未选;而由于左边的点只能连向右边编号大于等于它的点,所以右边前 i i i 个点必须全部已选(不然以后没机会了)。
  因此, s L s_L sL 的后 n − i n-i ni 位和 s R s_R sR 的前 i i i 位都是没有用的!把 s L s_L sL 的前 i i i 位和 s R s_R sR 的后 n − i n-i ni 位拼起来形成一个 2 n 2^n 2n 的状态,这样状压就是 O ( n ⋅ 2 n ) O(n\cdot2^n) O(n2n) 的了!

  这就很像计组里面学的 32 位整数乘除法,它把乘数和结果同时存在了一个 64 位整数上,这也是灵活地把两个状态叠在一起。
  您好,请重修计组

代码

#include<bits/stdc++.h>
#define fo(i,a,b) for(int i=a;i<=b;i++)
using namespace std;

typedef long long LL;

const int maxn=20, maxN=262150;
const int inf=2139062143;

int n,N,AA[maxn],v[maxn][4],v0[maxn],M[maxn],f[maxn][maxN],er[maxn];
bool G[maxn][maxn],A[maxn][maxn];

void ReadBit(bool &data)
{
	char ch=getchar();
	while (ch!='0' && ch!='1') ch=getchar();
	data=(ch=='1');
}

inline void Min(int &a,const int &b) {a=(a<b) ?a :b ;}

int T;
int main()
{
	er[0]=1;
	fo(i,1,18) er[i]=er[i-1]<<1;
	
	scanf("%d",&T);
	while (T--)
	{
		scanf("%d",&n);
		N=(1<<n)-1;
		fo(i,1,n)
		{
			v0[i]=0;
			fo(j,1,n)
			{
				ReadBit(G[i][j]);
				if (G[i][j]) v[i][++v0[i]]=j;
			}
		}
		fo(i,1,n)
		{
			AA[i]=0;
			fo(j,1,n)
			{
				ReadBit(A[i][j]);
				AA[i]|=A[i][j]<<(j-1);
			}
		}
		fo(i,1,n) scanf("%d",&M[i]);
		
		memset(f,127,sizeof(f));
		f[0][0]=0;
		fo(i,0,n-1)
			fo(s,0,N) if (f[i][s]<inf)
			{
				int nowL=s&(er[i]-1), nowR=s^nowL, nxt=i+1;
				bool alrdy=nowR&er[i];
				int cho=s|er[i];
				
				if (alrdy) Min(f[i+1][s^er[i]],f[i][s]);
				if (AA[nxt]&nowL || v0[nxt]==0) continue;
				
				if (alrdy || v[nxt][1]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]],f[i][s]+M[nxt]);
				if (v0[nxt]>1)
				{
					if (alrdy || v[nxt][2]==nxt) Min(f[i+1][cho|er[v[nxt][2]-1]],f[i][s]+M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][2]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][2]-1]],f[i][s]+M[nxt]*M[nxt]);
				}
				if (v0[nxt]>2)
				{
					if (alrdy || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][3]-1]],f[i][s]+M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]);
					if (alrdy || v[nxt][2]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][2]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]);
					if (alrdy || v[nxt][1]==nxt || v[nxt][2]==nxt || v[nxt][3]==nxt) Min(f[i+1][cho|er[v[nxt][1]-1]|er[v[nxt][2]-1]|er[v[nxt][3]-1]],f[i][s]+M[nxt]*M[nxt]*M[nxt]);
				}
			}
		
		int ans=inf;
		fo(s,0,N) Min(ans,f[n][s]);
		printf("%d\n",(ans==inf) ?-1 :ans);
	}
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值