神犇考题第三题:
设 S ={x|x∈Z,1≤x≤n}给定 N 个 S 的子集 Si,每个集合有一个权值 Wi。这 N 个集合满足其中任意 K 个集合的并集中至少有 K 个元素。请从中选取若干个集合(可以不选),使得其并集的元素个数恰等于选取的集合数,且这些集合的权值和最小。
考试的时候觉得这个题相比另外两个挺可写的,于是就使劲想,想出了最大权闭合图,想出了每个集合代表一个数字,可就是没想到把这两个联系起来,无奈,最后还是在包含关系的时候出搞错了,果然我还是太弱了,构造能力捉急。
首先有一个非常有用的条件,就是任意k个集合的并集中至少有k个元素,那么,如果有一个合法的方案,那么一定可以用一个单独的元素来代表这一个集合。
我们考虑拿到一个集合权值的代价,就是要将这个集合的所有元素都拿到(k个集合,k个元素),怎么办呢?我们只要找到所有元素表示的集合,把它们都拿到就行了。但与此同时我们拿到的集合可能会额外增加许多元素,那么把这些元素代表的集合也拿到,直到不会产生新的元素为止,这就是一个闭合图了。
于是把权值取反,跑一遍最大权闭合图就行了。
这个题精髓是条件的转化与模型的构造。
sset
1 #include<iostream> 2 #include<cstdio> 3 #include<algorithm> 4 #include<cmath> 5 #include<cstring> 6 #define maxn 1000 7 #define maxm 120000 8 #define inf 2147483647 9 using namespace std; 10 struct et 11 { 12 int s,t,val,next; 13 }e[maxm]; 14 int dis[maxn],gap[maxn],fir[maxn],last[maxn],g[maxn],s[maxm],t[maxm],next[maxm],mat[maxn],sign[maxn]; 15 int a[maxn][maxn]; 16 bool v[maxn]; 17 int n,m,tot,st,ed,num,top; 18 19 bool match(int x) 20 { 21 for (int j=fir[x];j;j=next[j]) 22 { 23 int k=t[j]; 24 if (!v[k]) 25 { 26 v[k]=1; 27 if (mat[k]==0||match(mat[k])) 28 { 29 mat[k]=x; 30 return 1; 31 } 32 } 33 } 34 return 0; 35 } 36 37 int dfs(int now,int flow) 38 { 39 if (now==ed) return flow; 40 int sap=0; 41 for (int j=last[now];j;j=e[j].next) 42 { 43 int k=e[j].t; 44 if (e[j].val&&dis[now]==dis[k]+1) 45 { 46 last[now]=j; 47 int tmp=dfs(k,min(e[j].val,flow-sap)); 48 e[j].val-=tmp; 49 e[j^1].val+=tmp; 50 sap+=tmp; 51 if (sap==flow) return sap; 52 } 53 } 54 if (dis[st]>=num) return sap; 55 if (!(--gap[dis[now]])) dis[st]=num; 56 ++gap[++dis[now]]; 57 last[now]=fir[now]; 58 return sap; 59 } 60 61 void add(int x,int y,int z) 62 { 63 e[++tot].s=x; e[tot].t=y; e[tot].val=z; e[tot].next=fir[x]; fir[x]=tot; 64 e[++tot].s=y; e[tot].t=x; e[tot].val=0; e[tot].next=fir[y]; fir[y]=tot; 65 } 66 67 int main() 68 { 69 //freopen("sset.in","r",stdin); 70 //freopen("sset.out","w",stdout); 71 scanf("%d",&n); 72 //构造二分图 73 tot=0; 74 for (int i=1;i<=n;i++) 75 { 76 scanf("%d",&g[i]); 77 for (int j=1;j<=g[i];j++) 78 { 79 scanf("%d",&a[i][j]); 80 s[++tot]=i; t[tot]=a[i][j]; next[tot]=fir[i]; fir[i]=tot; 81 } 82 } 83 //跑完美匹配 84 for (int i=1;i<=n;i++) 85 { 86 memset(v,0,sizeof(v)); 87 match(i); 88 } 89 for (int i=1;i<=n;i++) sign[mat[i]]=i; 90 memset(fir,0,sizeof(fir)); 91 //构造最大权闭合图 92 st=0; ed=n+1; num=n+2; tot=1; 93 for (int i=1;i<=n;i++) 94 for (int j=1;j<=g[i];j++) 95 if (a[i][j]!=sign[i]) add(i,mat[a[i][j]],inf); 96 int z,sum=0; 97 for (int i=1;i<=n;i++) 98 { 99 scanf("%d",&z); 100 z=-z; 101 if (z>0) add(st,i,z),sum+=z;else add(i,ed,-z); 102 } 103 //跑网络流 104 memset(dis,0,sizeof(dis)); 105 memset(gap,0,sizeof(gap)); 106 gap[0]=num; 107 for (int i=st;i<=ed;i++) last[i]=fir[i]; 108 int ans=0; 109 while (dis[st]<num) ans+=dfs(st,inf); 110 if (sum-ans>0) printf("%d\n",-(sum-ans)); 111 else printf("%d\n",0); 112 return 0; 113 }