题目
给你一个包含0-9的序列,如果你选择了这个序列的第i位和第j为,那么你能得到w[i][j]+w[j][i]的收益,然后对于你选择的那些数字是有一定的花费的,花费为a[i]*(k-1)+b[i],a[i]和b[i]是数字i的两个参数,k代表数字i选了多少次。
题解
其实就是最大权闭合子图的变式。
顺便回顾了最大权闭合子图的写法。下面会用(u,v,上限)这种形式表示建边。
一般是用于,需要付出代价选每个物品,某些物品凑一起组合可以产生一定价值。
方法是最小割。先假设拿到所有的价值,不付出代价,建立类似二分图,左边一排是物品,右边是组合,先(当前组合所需每个物品,组合,INF)然后(S,每个物品,物品代价) (每个组合,T,组合收益)
答案=所有价值-最小割
如果最小割中,某个物品与S的边被割了,说明选了这个物品,付出了代价,某个组合与T的边被割了,说明这个组合没有凑成,这个价值没有拿到。
然而在此题中有变式,物品被分为几类,第一次拿某类物品需要付出额外代价。也就是只要拿了这类物品就需要付出一个代价。可以给每类一个节点,(S,类节点,对应代价)(*),(类节点,所有该类物品,INF),可以发现,如果选了该类某个物品,那么( *)边必然会被割掉,因为不割会出现一条显然的增广路,并且也不会影响物品单独的代价。
代码
可能有同学看不懂我的语言。。我贴一下建边的代码,Dinic的板子就不帖了。
啊。。这个高亮有点亮
#define rep(i,x,y) for(int i=x;i<=y;++i)
int n,ans=0;cin>>n;
scanf("%s",s+1);
rep(i,0,9) scanf("%d%d",a+i,b+i);
rep(i,1,n) rep(j,1,n) {
scanf("%d",w[i]+j);
if(i!=j) ans+=w[i][j];
}
S=n*n+11,T=S+1;
rep(i,1,n) add_edge(S,i,a[s[i]-'0'],0);
int cur=n+1;
rep(i,1,n) rep(j,1,n) if(i!=j){
add_edge(i,cur,INF,0);
add_edge(j,cur,INF,0);
add_edge(cur,T,w[i][j],0);
++cur;
}
rep(i,1,n) add_edge(n*n+s[i]-'0'+1,i,INF,0);
rep(i,0,9) add_edge(S,n*n+i+1,b[i]-a[i],0);
int mf=MF();
printf("Case #%d: %d\n",++kase,ans-mf);
后记
啊啊啊啊网络流什么都不记得了真的是菜死我了。
另外也不是很明白网络流的复杂度,二分图,大概1w多个点,3w条边,20组居然能跑得这么快的吗,Dinic跑了31ms。。可能是我对他们复杂度有什么误解。