比赛的时候觉得是最大权完全子图,其实最大权闭合图就够了。建模不够优越。
最大权闭合图。如果你不懂这个东西定义可以去看《最小割模型在信息学竞赛中的应用》。
官方题解的意思我就不重复了,说一下自己的理解。
这样建模解决了所跑流一定是完全图的问题。产生正权的边在闭合图的最上层,所以就有这样的性质:如果该边(点)的后继点已经选了,那么加上该正权边既保证了所选图还是闭合的,也一定使闭合图的权增大。所以,只要令负权的COST作为底层节点,那么选一条在两个点都在集合中的边一定会使结果更优,那么最终结果一定是完全图。
该题在建图时,把边权拟作点权,值得借鉴。
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<cmath>
#include<iostream>
#include<algorithm>
#include<vector>
#include<list>
#include<set>
#include<map>
#include<stack>
#include<queue>
#define mem(x,y) memset(x,y,sizeof(x))
#define pb push_back
using namespace std;
typedef long long ll;
typedef pair<ll,ll> pii;
#define bug puts("===========");
const double pi=(acos(-1.0));
const double eps=1e-8;
const ll INF=1e18+10;
const int inf=1e9+10;
const int maxn=30000+500;
const int mod=1e9+7;
///===================================
#define INF 0x3f3f3f3f
const int maxm = 120900;
int n;
struct Node {
int v; // vertex
int cap; // capacity
int flow; // current flow in this arc
int nxt;
} e[maxm * 2];
int g[maxn], fcnt;
int st, ed;
void add(int u, int v, int c) {
//printf("%d %d %d\n",u,v,c);
e[++fcnt].v = v;
e[fcnt].cap = c;
e[fcnt].flow = 0;
e[fcnt].nxt = g[u];
g[u] = fcnt;
e[++fcnt].v = u;
e[fcnt].cap = 0;
e[fcnt].flow = 0;
e[fcnt].nxt = g[v];
g[v] = fcnt;
}
void init(int src,int sink,int n_) {
memset(g, 0, sizeof(g));
fcnt = 1;
n=n_;
st = src, ed = sink;/*修改*/
}
int dist[maxn], numbs[maxn], q[maxn];
void rev_bfs() {
int font = 0, rear = 1;
for (int i = 0; i <= n; i++) { //n为总点数
dist[i] = maxn;
numbs[i] = 0;
}
q[font] = ed;
dist[ed] = 0;
numbs[0] = 1;
while(font != rear) {
int u = q[font++];
for (int i = g[u]; i; i = e[i].nxt) {
if (e[i ^ 1].cap == 0 || dist[e[i].v] < maxn) continue;
dist[e[i].v] = dist[u] + 1;
++numbs[dist[e[i].v]];
q[rear++] = e[i].v;
}
}
}
#define debug puts("--");
int maxflow() {
rev_bfs();
int u, totalflow = 0;
int curg[maxn], revpath[maxn];
for(int i = 0; i <= n; ++i) curg[i] = g[i];
u = st;
while(dist[st] < n) {
//debug;
if(u == ed) { // find an augmenting path
int augflow = INF;
for(int i = st; i != ed; i = e[curg[i]].v)
augflow = min(augflow, e[curg[i]].cap);
for(int i = st; i != ed; i = e[curg[i]].v) {
e[curg[i]].cap -= augflow;
e[curg[i] ^ 1].cap += augflow;
e[curg[i]].flow += augflow;
e[curg[i] ^ 1].flow -= augflow;
}
totalflow += augflow;
u = st;
}
int i;
for(i = curg[u]; i; i = e[i].nxt)
if(e[i].cap > 0 && dist[u] == dist[e[i].v] + 1) break;
if(i) { // find an admissible arc, then Advance
curg[u] = i;
revpath[e[i].v] = i ^ 1;
u = e[i].v;
} else { // no admissible arc, then relabel this vertex
if(0 == (--numbs[dist[u]])) break; // GAP cut, Important!
curg[u] = g[u];
int mindist = n;
for(int j = g[u]; j; j = e[j].nxt)
if(e[j].cap > 0) mindist = min(mindist, dist[e[j].v]);
dist[u] = mindist + 1;
++numbs[dist[u]];
if(u != st)
u = e[revpath[u]].v; // Backtrack
}
}
return totalflow;
}
///================分割线=============///
///最大权闭合图的的权 = 原图中权值为正的点的和 - 最小割(最大流)
int a[maxn],b[maxn];
char s[maxn];
int w[110][110];
int main()
{
int T;
int cas=0;
scanf("%d",&T);
while(T--)
{
int n;
scanf("%d",&n);
//src 0
//wij 1~n*n
//point n*n+1~n*n+n
//0~9 n*n+n+i+1
//sink n*n+n+11
int sink=n*n+n+11;
scanf("%s",s+1);
init(0,sink,sink+3);
for(int i=0;i<10;i++)
scanf("%d%d",&a[i],&b[i]);
for(int i=1;i<=n;i++)
for(int j=1;j<=n;j++)
scanf("%d",&w[i][j]);
int tot=0;
for(int i=1;i<=n;i++)
for(int j=i+1;j<=n;j++)
{
int u=(i-1)*n+j;
if(w[i][j]+w[j][i])add(0,u,w[i][j]+w[j][i]);
add(u,n*n+i,inf);
add(u,n*n+j,inf);
tot+=w[i][j]+w[j][i];
}
int alone[10];
mem(alone,0);
for(int i=1;i<=n;i++)
{
int u=s[i]-'0';
alone[u]=1;
int w=a[u];
add(n*n+i,n*n+n+1+u,inf);
add(n*n+i,sink,w);
}
for(int i=0;i<10;i++)
if(alone[i]){
if(a[i]>b[i])
add(0,n*n+n+1+i,a[i]-b[i]),tot+=a[i]-b[i];
else
add(n*n+n+1+i,n*n+n+11,b[i]-a[i]);
}
int ans=maxflow();
//printf("%d %d\n",tot,ans);
ans=tot-ans;
if(!n)ans=0;
printf("Case #%d: %d\n",++cas,ans);
}
}