題目
題意
你有個士兵,每個士兵需要安排一個職位(法師或戰士),另給定對士兵,對於其中的一對,如果都選戰士,戰隊的攻擊力會提高點();如果都選法師,攻擊力提高點();如果一個戰士一個法師,攻擊力提高點(),問戰隊攻擊力最多提升多少點。
分析
這類“二元分配,組合加成”的問題,一般可以用總和(即所有、、之和)減最小割完成,建圖如下:
我們的目的是使不同割法對應不同選法的損失。
圖中的、是兩個有加成關係士兵,割表示選戰士,割表示選法師;割表示選戰士,割表示選法師。於是有這幾種情況:
- 割和,則損失的戰鬥力是,所以;
- 割和,則損失的戰鬥力是,所以;
- 割和或者割和,這時由於的存在,圖還是聯通的,所以必須再割,所以。
注意你可能覺得還有其他割法,但是,由於邊權必須是正數(最大流最小割不能跑負容量),所以顯然割集一定比優(),所以只有上述幾種情況。
聯立得到(不定)方程組:
其中、、是常數。
找到一組解:
於是按這個建邊即可,注意先乘使邊權爲整數。
(題目中的奇怪的表達式應該只是讓吧= =)
代碼
#include<queue>
#include<cstdio>
#include<algorithm>
using namespace std;
int read(){
int x=0;bool f=0;char c=getchar();
while(c<'0'||c>'9') f|=c=='-',c=getchar();
while(c>='0'&&c<='9') x=x*10+(c^48),c=getchar();
return f?-x:x;
}
int N,M;
#define MAXN 50000
#define MAXM 1000000
#define INF int(1e9)
#define LL long long
struct Edge{
int v,w,Next;
}E[MAXM+5];
int Adj[MAXN+5],ecnt;
void AddEdge(int u,int v,int w){
E[ecnt].v=v,E[ecnt].w=w,E[ecnt].Next=Adj[u],Adj[u]=ecnt++;
E[ecnt].v=u,E[ecnt].w=0,E[ecnt].Next=Adj[v],Adj[v]=ecnt++;
}
int Cur[MAXN+5];
int Dist[MAXN+5];
bool bfs(int T){
for(int i=0;i<=T;i++)
Cur[i]=Adj[i],Dist[i]=-1;
queue<int> Q;
Q.push(0),Dist[0]=0;
while(!Q.empty()){
int u=Q.front();Q.pop();
for(int i=Adj[u];i!=-1;i=E[i].Next){
int v=E[i].v;
if(E[i].w&&Dist[v]==-1){
Dist[v]=Dist[u]+1;
Q.push(v);
}
}
}
return Dist[T]>=0;
}
int dfs(int u,int T,int Min){
if(!Min||u==T)
return Min;
int Rest=Min,Now;
for(int &i=Cur[u];i!=-1;i=E[i].Next){
int v=E[i].v,w=E[i].w;
if(Dist[v]==Dist[u]+1&&(Now=dfs(v,T,min(Rest,w)))){
Rest-=Now,
E[i].w-=Now,
E[i^1].w+=Now;
if(!Rest)
break;
}
}
return Min-Rest;
}
LL Dinic(int T){
LL ret=0;
while(bfs(T))
ret+=dfs(0,T,INF);
return ret;
}
int main(){
while(~scanf("%d%d",&N,&M)){
ecnt=0;
for(int i=0;i<=N+1;i++)
Adj[i]=-1;
LL Sum=0;
for(int i=1;i<=M;i++){
int u=read(),v=read(),A=read()*2,B=read()*2,C=read()*2;
Sum+=A+B+C;
AddEdge(0,u,(A+B)/2),AddEdge(0,v,(A+B)/2);
AddEdge(u,N+1,(B+C)/2),AddEdge(v,N+1,(B+C)/2);
AddEdge(u,v,(A+C)/2-B),AddEdge(v,u,(A+C)/2-B);
}
printf("%lld\n",(Sum-Dinic(N+1))/2);
}
return 0;
}