当前位置:   article > 正文

“东信杯”广西大学第一届程序设计竞赛(同步赛)F-出装方案(二分图最大匹配/状压dp/最大费用最大流)_东信杯 f 出装方案

东信杯 f 出装方案

题目

思路来源

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=37522548(MCMF)

https://ac.nowcoder.com/acm/contest/view-submission?submissionId=37514575(状压dp)

心得

本来就是一个二分图最大权匹配的KM板子题,上了板子就过了。

但是,看到了一个高中生的状压dp做法,

不得不说,有些高中生真的认真,代码通俗易懂可读性强,

的确是一步一个脚印扎扎实实写的代码(嗯白膜法师这种已经不能叫高中生了)

所以,借着人家的代码学一学从没学过的状压dp

QAQ睡个觉果然读代码能力强了不少,以后看不懂代码要及时休息啊,效率++

 

补一下MCMF的题解

按原来的样子建图,

二分图部分,每条边,费用=强度提升值,流量=1,

从每个左侧顶点流出来的流量就是1,只能匹配一个

超级源点s连左侧顶点,右侧顶点连超级汇点t,这些边,费用=0,流量=1

跑一遍最大费用最大流,费用即为总强度提升的部分

可以边权取负,跑最小费用最大流,最后再把tot的负再取回来

也可以每次spfa增广的时候找最长路,很灵活吖

代码

  1. #include <cstdio>
  2. #include <cstring>
  3. #include <algorithm>
  4. int a[110][110];
  5. int dp[1 << 15];
  6. int main()
  7. {
  8. int T; scanf("%d", &T);
  9. while(T--)
  10. {
  11. int n; scanf("%d", &n);
  12. for(int i = 0; i < n; i++)
  13. for(int j = 0; j < n; j++) scanf("%d", &a[i][j]);
  14. memset(dp, 0, sizeof(dp));
  15. for(int S = 1; S < (1 << n); S++)
  16. {
  17. int tot = 0;
  18. for(int i = 0; i < n; i++) tot += ((S >> i) & 1);
  19. tot--;
  20. for(int i = 0; i < n; i++)
  21. {
  22. if((S >> i) & 1)
  23. dp[S] = std::max(dp[S], dp[S ^ (1 << i)] + a[tot][i]);
  24. }
  25. }
  26. printf("%d\n", dp[(1 << n) - 1]);
  27. }
  28. return 0;
  29. }

个人理解

先把人家的代码粘过来。

状压dp是二进制枚举思想,用第i位来枚举第i件物品,

1代表该物品已被选,不能再选了;0代表该物品尚未被选。

tot是枚举的二进制数中的1的个数,第tot个人可以在尚未被选的里面选一个。

为了与下标对应,tot减1。

核心句

if((S >> i) & 1)dp[S] = std::max(dp[S], dp[S ^ (1 << i)] + a[tot][i]);

感觉这个很形象啊QAQ,

第tot个人知道,当他选第i件商品的时候,能达成tot个人之和的最优,

于是他让前tot-1个人别选第i件商品了,“都让让都让让,你们从剩下tot-1件商品里挑,把第i件给我"。

我写博客咋变成这个画风了

然而我们不知道第tot个人选哪件能达成最优,

所以我们就要在状压的某状态(如01010101)的某个1值里,枚举一件给第tot个人。

 

即n个人分n件商品的最优,一定是(n-1)个人分其中(n-1)个商品的最优,加上最后一个人选剩下一件商品所求得。

状压dp,好就好在当第i位为1时,把第i位变0原值会变小,所以大一定从小转移而来。

因为有这个最优子结构的性质,tot个人的最优,一定是由tot-1个人的某种最优选法转移而来,故有等式成立。

 

如11可以由01或10转移而来,

前一种转移代表第一个人选了第二件而第二个人选了第一件,

后一种转移代表第一个人选了第一件而第二个人选了第二件,

但是既然这两种已经被前两个人选了,我们只用11保留这两种情况的最大值即最优情况。

 

其实这就跟递归一样,你不知道某个状态的值,甚至其子状态的值,

但是最底层的值和转移方程你是知道的,所以你就知道了一切.jpg。

 

状压dp一般很明显,O(2^n)所以一般n不超过15。

当然KM算法O(n^3)在这里显得优了好多。

只是学习一波状压dp罢了。

传说中的dp一题能想一晚上真不是吹的啊

代码2(MaxCostMaxFlow,MCMF)

要学会建图和改板子啊,毕竟抄了这么一个大板子

  1. #include<bits/stdc++.h>
  2. typedef long long ll;
  3. using namespace std;
  4. const int maxn=35;//点的数量
  5. const int N=2*maxn;
  6. const int maxm=5000;//2*maxn*maxn+4*maxn 源点出maxn条 汇点入maxn条 中间点两两连
  7. const ll INF=1e18;
  8. struct edge
  9. {
  10. int u,v;
  11. ll cos,nex,w;
  12. }e[maxm];
  13. ll vis[N],dis[N];
  14. ll cost[maxn][maxn];
  15. int head[N],cnt;
  16. void add(int u,int v,ll w,ll cos)
  17. {
  18. e[cnt].v=v;
  19. e[cnt].w=w;
  20. e[cnt].cos=cos;
  21. e[cnt].nex=head[u];
  22. head[u]=cnt++;
  23. }
  24. void add2(int u,int v,ll w,ll cos)
  25. {
  26. add(u,v,w,cos);
  27. add(v,u,0,-cos);
  28. }
  29. bool spfa(int s,int t)
  30. {
  31. for(int i=0;i<2*maxn;++i)
  32. dis[i]=INF;
  33. memset(vis,0,sizeof vis);
  34. vis[t]=1;
  35. dis[t]=0;
  36. deque<int>q;
  37. q.push_back(t);
  38. while(!q.empty())
  39. {
  40. int u=q.front();
  41. vis[u]=0;
  42. q.pop_front();
  43. for(int i=head[u];~i;i=e[i].nex)
  44. {
  45. int v=e[i].v;
  46. if(e[i^1].w>0&&dis[v]>dis[u]-e[i].cos)
  47. {
  48. dis[v]=dis[u]-e[i].cos;
  49. if(!vis[v])
  50. {
  51. vis[v]=1;
  52. if(!q.empty()&&dis[v]<dis[q.front()])
  53. q.push_front(v);
  54. else q.push_back(v);
  55. }
  56. }
  57. }
  58. }
  59. return dis[s]<INF;
  60. }
  61. ll dfs(int u,int t,ll low,ll &tot)
  62. {
  63. vis[u]=1;
  64. if(u==t)return low;
  65. ll ans=0;
  66. for(int i=head[u];~i;i=e[i].nex)
  67. {
  68. int v=e[i].v;
  69. if(!vis[v]&&e[i].w&&dis[v]==dis[u]-e[i].cos)
  70. {
  71. ll now=dfs(v,t,min(low,e[i].w),tot);
  72. if(now>0)
  73. {
  74. tot+=e[i].cos*now;
  75. low-=now;
  76. ans+=now;
  77. e[i].w-=now;
  78. e[i^1].w+=now;
  79. if(low==0)break;
  80. }
  81. }
  82. }
  83. return ans;
  84. }
  85. ll max_flow(int s,int t,ll &tot)//tot返回费用 函数返回值为最大流
  86. {
  87. ll ans=0;
  88. while(spfa(s,t))
  89. {
  90. vis[t]=1;
  91. while(vis[t])
  92. {
  93. memset(vis,0,sizeof vis);
  94. ans+=dfs(s,t,INF,tot);
  95. }
  96. }
  97. return ans;
  98. }
  99. void init()
  100. {
  101. cnt=0;
  102. for(int i=0;i<2*maxn;++i)
  103. dis[i]=INF;
  104. memset(head,-1,sizeof head);
  105. memset(vis,0,sizeof vis);
  106. }
  107. int main()
  108. {
  109. ll tot;//总费用
  110. int T,n,s,t;
  111. scanf("%d",&T);
  112. while(T--)
  113. {
  114. tot=0;
  115. init();
  116. scanf("%d",&n);
  117. //超级源 超级汇
  118. s=0;t=2*n+1;
  119. //左供货 右需求
  120. for(int i=1;i<=n;++i)
  121. add2(s,i,1,0);//超级源点
  122. for(int i=1;i<=n;++i)
  123. add2(n+i,t,1,0);//超级汇点
  124. for(int i=1;i<=n;++i)
  125. {
  126. for(int j=1;j<=n;++j)
  127. {
  128. scanf("%lld",&cost[i][j]);
  129. add2(i,n+j,1,-cost[i][j]);//l[i]的流量 dis的费用
  130. }
  131. }
  132. max_flow(s,t,tot);
  133. printf("%lld\n",-tot);
  134. }
  135. return 0;
  136. }

 

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/281713?site
推荐阅读
相关标签
  

闽ICP备14008679号