HDU4862 这题说的是在一个n*m的格子内 你有K次机会选择起始的点,选择的点不能使以前用过的 ,然后选择后你可以往右 跳几步 也可以往下跳几步 但是只能要不往右 要不就往下 不能两个同时成立比如说 (1,1) 不能跳到(2,3);然后得到了 我们通过 拆点可以将他们分离开来保证只用一次,然后将拆的点的流量置为1 费用置为 负的无穷大,这样是为了勾引流量过来使得k次选择的点能够通过所有的点然后还要外加一条边是从源点流向汇点的这条边的流量为k 费用为0 这条边的目的是为了使得如果选择次数比k次来的小的时候可能达到最大值就不去影响那个最大值因为他保证可以使用少于k次的选择机会
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <cstdio> #include <string.h> #include <queue> using namespace std; const int MOD = 100000; const int INF = 100000000; const int maxn = 1000000; int first[300],next[maxn],u[maxn],v[maxn],w[maxn],num,cost[maxn],ed,st,map[200][200]; int per[300],mark[300],dis[300],n,m; void add(int a, int b, int c, int d) { u[num] = a; v[num] = b; w[num] = c; cost[num] = d; next[num] = first[a]; first[a] = num++; u[num] = b; v[num] = a; w[num] = 0; cost[num] = -d; next[num] = first[b]; first[b]=num++; } int abs(int a){ return a>0?a:-a; } bool SPAF() { for(int i =0; i<= ed+1; ++i) dis[i]=INF,mark[i]=0,per[i]=-1; dis[ed+1]=0; queue<int> Q; Q.push(ed+1); while(!Q.empty()) { int a = Q.front(); Q.pop(); mark[a]=0; for(int e =first[a]; e!=-1; e= next[e]) if(dis[v[e]]>dis[u[e]]+cost[e]&&w[e]>0) { dis[ v[ e ] ] = dis[ u[ e ] ] +cost[ e ]; per[v[e]] = e; if(mark[v[e]]==0) { mark[v[e]] = 1; Q.push(v[e]); } } } return dis[ed]!=INF; } int EK() { int ans_COS=0 ; while(SPAF()) { int flow =INF; for( int e=per[ed]; e!=-1; e=per[ u[e] ] ) if(flow>w[e]) flow=w[e]; for(int e = per[ed]; e!=-1; e=per[u[e]]) { w[e]-=flow; w[e^1]+=flow; } ans_COS+=dis[ed]; } return (-(n*m*MOD+ans_COS))%MOD; } int main() { int t; scanf("%d",&t); for( int cas = 1 ; cas<=t; ++cas ) { int n,m,K; scanf("%d%d%d",&n,&m,&K); ed =n*m*2+1; for(int i =0; i<=ed+1; ++i) first[i]=-1; st=num=0; for(int i =1; i<=n; ++ i) for(int j =1; j<=m; ++j) { scanf("%1d",&map[i][j]); add( st, (i-1)*m+j , 1, 0); add( ( i - 1 )*m+j, n*m+( i - 1 )*m+j, 1, -MOD ); add( n * m +( i - 1 )*m+j, ed, 1, 0 ); } if(K<min(n,m)){ printf("Case %d : -1 ",cas); continue; } for(int i =1; i<=n; ++i) for(int j=1; j<=m; ++j) { for(int k =i+1; k<=n; ++k) add( n*m+(i-1)*m+j , (k-1)*m+j, 1 , abs(i-k)-1-(map[i][j]==map[k][j]?map[i][j]:0) ); for(int k= j+1; k<=m; ++k) add( n*m+(i-1)*m+j , (i-1)*m+k, 1 , abs(j-k)-1-(map[i][j]==map[i][k]?map[i][j]:0) ); } add(ed+1,st,K,0); add(st,ed,K,0); int ans = EK(); printf("Case %d : %d ",cas,ans); } return 0; }
题解是用 二部图做的最小K路径覆盖的模型,用费用流或者KM算法解决,构造二部图,X部有N*M个节点,源点向X部每个节点连一条边,流量1,费用0,Y部有N*M个节点,每个节点向汇点连一条边,流量1,费用0,如果X部的节点x可以在一步之内到达Y部的节点y,那么就连边x->y,费用为从x格子到y格子的花费能量减去得到的能量,流量1,再在X部增加一个新的节点,表示可以从任意节点出发K次,源点向其连边,费用0,流量K,这个点向Y部每个点连边,费用0,流量1,最这个图跑最小费用最大流,如果满流就是存在解,反之不存在,最小费用的相反数就是可以获得的最大能量
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
#include <cstdio> #include <string.h> #include <iostream> #include <queue> using namespace std; const int INF = 100000; const int maxn=100000; int first[300],next[maxn],u[maxn],v[maxn],cost[maxn],w[maxn],ed,num,st,n,m,k; int dis[300],mark[300],per[300],map[300][300]; int abs(int a){ return a>0?a:-a; } void add(int a,int b, int flow, int cos) { u[ num ] = a; v[ num ] = b; w[ num ] = flow; cost[ num ] = cos; next[ num ] = first[a]; first[ a ] = num++; u[ num ]=b; v[ num ]=a; w[ num ] = 0; cost[ num ] = -cos; next[ num ] = first[b]; first[ b ]=num++; } bool SPAF() { for(int i =0; i<=ed+1; ++i) mark[i]=0,per[i]=-1,dis[i]=INF; queue<int>Q; Q.push(st); dis[st]=0; while(!Q.empty()) { int a = Q.front(); Q.pop(); mark[a] =0; for(int e = first[a] ; e!=-1; e=next[e]) if( dis[v[e]]>dis[u[e]]+cost[e] && w[e]>0) { dis[ v[ e ] ] = dis[ u[ e ] ] +cost[ e ]; per[ v[ e ] ] = e; if(mark[v[e]] == 0) { mark[ v[ e ] ] = 1; Q.push(v[ e ]); } } } return dis[ed]!=INF; } int EK() { int Cos_t=0,Cos_flow=0; while(SPAF()) { int flow=INF; for(int e = per[ed] ; e!=-1; e =per[u[e]]) if(flow>w[e]) flow=w[e]; for(int e = per[ed] ; e!=-1; e = per[u[e]] ) { w[e]-=flow; w[e^1]+=flow; } Cos_t+=dis[ed]; Cos_flow+=flow; } if(Cos_flow==n*m) return - Cos_t; else return -1; } int main() { int t; scanf("%d",&t); for( int cas = 1; cas <= t ; cas++ ) { scanf("%d%d%d",&n,&m,&k); st=num=0; ed= n*m*2 +1; for(int i=0; i<=ed+1; ++i) first[i]=-1; for(int i=1; i<=n; ++i) for(int j=1; j<=m; ++j) { scanf("%1d",&map[i][j]); add( st, (i-1)*m+j, 1, 0); add( n*m+(i-1)*m+j, ed, 1, 0); } for(int i =1; i<=n; ++i) for(int j =1; j<=m; ++j) { for(int k =i+1; k<=n; ++k) add( (i-1)*m+j, n*m+(k-1)*m+j, 1,abs(k-i)-1-(map[i][j]==map[k][j]?map[i][j]:0)); for(int k=j+1; k<=m; ++k) add( (i-1)*m+j, n*m+(i-1)*m+k, 1,abs(k-j)-1-(map[i][j]==map[i][k]?map[i][j]:0)); add( ed+1, n*m+(i-1)*m+j, 1, 0); } add(st,ed+1,k,0); int ans=EK(); printf("Case %d : %d ",cas,ans); } return 0; }
的堆得得得得