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
跑一遍最大费用最大流,费用即为总强度提升的部分
可以边权取负跑最小费用最大流,最后再把ans的负再取回来
也可以每次spfa增广的时候找最长路,很灵活吖
#include
#include
#include
int a[110][110];
int dp[1 << 15];
int main()
{
int T; scanf("%d", &T);
while(T--)
{
int n; scanf("%d", &n);
for(int i = 0; i < n; i++)
for(int j = 0; j < n; j++) scanf("%d", &a[i][j]);
memset(dp, 0, sizeof(dp));
for(int S = 1; S < (1 << n); S++)
{
int tot = 0;
for(int i = 0; i < n; i++) tot += ((S >> i) & 1);
tot--;
for(int i = 0; i < n; i++)
{
if((S >> i) & 1)
dp[S] = std::max(dp[S], dp[S ^ (1 << i)] + a[tot][i]);
}
}
printf("%d\n", dp[(1 << n) - 1]);
}
return 0;
}
先把人家的代码粘过来。
状压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一题能想一晚上真不是吹的啊
要学会建图和改板子啊,毕竟抄了这么一个大板子
#include
typedef long long ll;
using namespace std;
const int maxn=35;//点的数量
const int maxm=5000;//2*maxn*maxn+2*maxn 源点出maxn条 汇点入maxn条 中间点两两连
const ll INF=1e18;
struct edge
{
int u,v;
ll cos,nex,w;
}e[maxm];
ll vis[2*maxn],dis[2*maxn];
ll l[2*maxn],r[2*maxn],cost[2*maxn][2*maxn];
int head[maxn],cnt;
void add(int u,int v,ll w,ll cos)
{
e[cnt].v=v;
e[cnt].w=w;
e[cnt].cos=cos;
e[cnt].nex=head[u];
head[u]=cnt++;
}
void add2(int u,int v,ll w,ll cos)
{
add(u,v,w,cos);
add(v,u,0,-cos);
}
bool spfa(int s,int t)
{
for(int i=0;i<2*maxn;++i)
dis[i]=INF;
memset(vis,0,sizeof vis);
vis[t]=1;
dis[t]=0;
dequeq;
q.push_back(t);
while(!q.empty())
{
int u=q.front();
vis[u]=0;
q.pop_front();
for(int i=head[u];~i;i=e[i].nex)
{
int v=e[i].v;
if(e[i^1].w>0&&dis[v]>dis[u]-e[i].cos)
{
dis[v]=dis[u]-e[i].cos;
if(!vis[v])
{
vis[v]=1;
if(!q.empty()&&dis[v]0)
{
tot+=e[i].cos*now;
low-=now;
ans+=now;
e[i].w-=now;
e[i^1].w+=now;
if(low==0)break;
}
}
}
return ans;
}
ll max_flow(int s,int t,ll &tot)//tot返回费用 函数返回值为最大流
{
ll ans=0;
while(spfa(s,t))
{
vis[t]=1;
while(vis[t])
{
memset(vis,0,sizeof vis);
ans+=dfs(s,t,INF,tot);
}
}
return ans;
}
void init()
{
cnt=0;
for(int i=0;i<2*maxn;++i)
dis[i]=INF;
memset(head,-1,sizeof head);
memset(vis,0,sizeof vis);
}
int main()
{
ll tot;//总费用
int T,n,s,t;
scanf("%d",&T);
while(T--)
{
tot=0;
init();
scanf("%d",&n);
//超级源 超级汇
s=0;t=2*n+1;
//左供货 右需求
for(int i=1;i<=n;++i)
add2(s,i,1,0);//超级源点
for(int i=1;i<=n;++i)
add2(n+i,t,1,0);//超级汇点
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
{
scanf("%lld",&cost[i][j]);
add2(i,n+j,1,-cost[i][j]);//l[i]的流量 dis的费用
}
}
max_flow(s,t,tot);
printf("%lld\n",-tot);
}javascript:void(0);
return 0;
}