HDU 5772 String problem

比赛的时候觉得是最大权完全子图,其实最大权闭合图就够了。建模不够优越。
最大权闭合图。如果你不懂这个东西定义可以去看《最小割模型在信息学竞赛中的应用》。

官方题解的意思我就不重复了,说一下自己的理解。
这样建模解决了所跑流一定是完全图的问题。产生正权的边在闭合图的最上层,所以就有这样的性质:如果该边(点)的后继点已经选了,那么加上该正权边既保证了所选图还是闭合的,也一定使闭合图的权增大。所以,只要令负权的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);
	}
}


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值