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

题目大意

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

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

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

  $n \leq 18,~1 \leq M_i \leq 100$
  多测,$T \leq 10$
  1s

题解

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

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

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

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

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#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);
}
}