bzoj1449:[JSOI2009]球队收益/bzoj2895:球队预算
传送门1449
传送门2895
平方很难处理,考虑剩下的比赛所有队全输(只能全输,全赢保证不了费用流的正确性),考虑计算每个队多赢一次的贡献
拆开:
\(c_i*(x+1)^2+d_i*(y-1)^2-c_i*x^2-d_i*y^2\)
\(=2*c_i*x+c_i-2*d_i*y+d_i\)
然后源点向每场比赛连边,容量为1,费用为0,限制每场比赛只能有一场胜利
每场比赛向参赛的队伍连边,容量为1,费用为1,限制只有一个队伍能赢一场
每个队伍向汇点连边,容量为1,费用为上面推的,每连一条边将当前队伍的胜负状况修改一下
然后跑费用流就行了
代码:
#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
#include<queue>
using namespace std;
void read(int &x) {
char ch; bool ok;
for(ok=0,ch=getchar(); !isdigit(ch); ch=getchar()) if(ch=='-') ok=1;
for(x=0; isdigit(ch); x=x*10+ch-'0',ch=getchar()); if(ok) x=-x;
}
#define rg register
const int maxn=3e5+10,inf=1e9;queue<int>q;
int cnt=1,cc[maxn],bb[maxn],v[maxn],w[maxn],ans,num[5010],s,t,a[5010],b[5010],c[5010],d[5010],n,m,pre[maxn*2],nxt[maxn*2],h[maxn];
int sla[maxn],dis[maxn];bool vis[maxn];
void add(int x,int y,int z,int d)
{
pre[++cnt]=y,nxt[cnt]=h[x],h[x]=cnt,v[cnt]=z,w[cnt]=d;
pre[++cnt]=x,nxt[cnt]=h[y],h[y]=cnt,v[cnt]=0,w[cnt]=-d;
}
bool spfa(int s)
{
memset(dis,63,sizeof dis);
q.push(s),dis[s]=0;
while(!q.empty())
{
int x=q.front();q.pop(),vis[x]=0;
for(rg int i=h[x];i;i=nxt[i])
if(v[i]&&dis[pre[i]]>dis[x]+w[i])
{
dis[pre[i]]=dis[x]+w[i],bb[pre[i]]=x,cc[pre[i]]=i;
if(!vis[pre[i]])vis[pre[i]]=1,q.push(pre[i]);
}
}
return dis[t]<inf;
}
int getans()
{
int mn=inf;
for(rg int i=t;i!=s;i=bb[i])mn=min(v[cc[i]],mn);
for(rg int i=t;i!=s;i=bb[i])v[cc[i]]-=mn,v[cc[i]^1]+=mn;
return dis[t]*mn;
}
int main()
{
read(n),read(m),s=0,t=n+m+1;
for(rg int i=1;i<=n;i++)read(a[i]),read(b[i]),read(c[i]),read(d[i]);
for(rg int i=1,x,y;i<=m;i++)
{
read(x),read(y);b[x]++,b[y]++,num[x]++,num[y]++;
add(s,i+n,1,0),add(i+n,x,1,0),add(i+n,y,1,0);
}
for(rg int i=1;i<=n;i++)ans=ans+c[i]*a[i]*a[i]+d[i]*b[i]*b[i];
for(rg int i=1;i<=n;i++)
while(num[i])add(i,t,1,2*c[i]*a[i]-2*d[i]*b[i]+c[i]+d[i]),a[i]++,b[i]--,num[i]--;
while(spfa(s))ans+=getans();
printf("%d\n",ans);
}