title
\(~\)
BZOJ 1449
LUOGU 4307
Description
在一个篮球联赛里,有 \(n\) 支球队,球队的支出是和他们的胜负场次有关系的,具体来说,第 \(i\) 支球队的赛季总支出是\(C_i\times x^2+D_i \times y^2,D_i \le C_i\) 。(赢得多,给球员的奖金就多嘛) 其中 \(x,y\) 分别表示这只球队本赛季的胜负场次。现在赛季进行到了一半,每只球队分别取得了 \(a_i\) 场胜利和 \(b_i\) 场失利。而接下来还有 \(m\) 场比赛要进行。问联盟球队的最小总支出是多少。
Input
第一行 \(n,m\)
接下来 \(n\) 行每行 \(4\) 个整数 \(a_i,b_i,C_i,D_i\)
再接下来 \(m\) 行每行两个整数 \(s\),\(t\) 表示第 \(s\) 支队伍和第tt支队伍之间将有一场比赛,注意两只队间可能有多场比赛。
Output
一个整数表示联盟里所有球队收益之和的最小值。
Sample Input
3 3
1 0 2 1
1 1 10 1
0 1 3 3
1 2
2 3
3 1
Sample Output
43
HINT
对于20%的数据\(2 \le n \le 10,0 \le m \le 20\);
对于100%的数据\(2 \le n \le 5000,0 \le m \le 1000,0 \le D_i \le C_i \le 10,0 \le a_i,b_i \le 50\)。
analysis
- 假设每场比赛两队都是 负,算出初始收益;
- 考虑每场比赛,其中一队 胜;
- 从 \(源点s\) 向 \(这场比赛i\) 连容量 1,费用 0 的边;
- 然后考虑费用的增量:多赢一场比赛产生的收益。即 \((C*(w+1)^2+D*(l-1)^2)-(C*w^2+D*l^2)=2w*C-2l*D+C+D\)。
- 对于某支球队,假设后 \(M\) 场中其参加 \(x\) 场,那么最初 \(w=win,l=lose+x\),之后每赢一场 \(w++,l–~–\)。
- 我们从 \(这支球队\) 向 \(汇点t\) 连 \(x\) 条边,分别代表其赢了 \(j\) 场比赛时相对赢 \(j-1\) 场时收益的增量。
- 由于增量递增,所以可以保证正确性。
- \(ans=\) 所有队伍最初收益 \(+\) 最小费用最大流费用。
- 数组的范围要考虑清楚,否则收获意想不到的评测结果。
code
#include<bits/stdc++.h>
using namespace std;
const int maxn=5010,maxm=6010,maxe=1e5+10,inf=0x3f3f3f3f;
char buf[1<<15],*fs,*ft;
inline char getc() { return (ft==fs&&(ft=(fs=buf)+fread(buf,1,1<<15,stdin),ft==fs))?0:*fs++; }
template<typename T>inline void read(T &x)
{
x=0;
T f=1, ch=getchar();
while (!isdigit(ch) && ch^'-') ch=getchar();
if (ch=='-') f=-1, ch=getchar();
while (isdigit(ch)) x=(x<<1)+(x<<3)+(ch^48), ch=getchar();
x*=f;
}
char Out[1<<24],*fe=Out;
inline void flush() { fwrite(Out,1,fe-Out,stdout); fe=Out; }
template<typename T>inline void write(T x)
{
if (!x) *fe++=48;
if (x<0) *fe++='-', x=-x;
T num=0, ch[20];
while (x) ch[++num]=x%10+48, x/=10;
while (num) *fe++=ch[num--];
*fe++='\n';
}
int ver[maxe],edge[maxe],Next[maxe],cost[maxe],head[maxm],len=1;
inline void add(int x,int y,int z,int c)
{
ver[++len]=y,edge[len]=z,cost[len]=c,Next[len]=head[x],head[x]=len;
ver[++len]=x,edge[len]=0,cost[len]=-c,Next[len]=head[y],head[y]=len;
}
int s,t;
int dist[maxm];
bool vis[maxm];
inline bool spfa()
{
memset(dist,0x3f,sizeof(dist));
memset(vis,0,sizeof(vis));
queue<int>q;q.push(s);
dist[s]=0,vis[s]=1;
while (!q.empty())
{
int x=q.front();
q.pop();
vis[x]=0;
for (int i=head[x]; i; i=Next[i])
{
if (!edge[i]) continue;
int y=ver[i];
if (dist[y]>dist[x]+cost[i])
{
dist[y]=dist[x]+cost[i];
if (!vis[y]) q.push(y),vis[y]=1;
}
}
}
if (dist[t]==inf) return false;
else return true;
}
int ans;
inline int get(int x,int low)
{
vis[x]=1;
if (x==t) return low;
int tmp=low;
for (int i=head[x]; i; i=Next[i])
{
int y=ver[i];
if (edge[i] && dist[y]==dist[x]+cost[i] && (!vis[y] || y==t))
{
int a=get(y,min(tmp,edge[i]));
if (a>0)
{
ans+=a*cost[i];
edge[i]-=a;
edge[i^1]+=a;
if (!(tmp-=a)) break;
}
}
}
return low-tmp;
}
inline void NetFlow()
{
while (spfa())
{
vis[t]=1;
while (vis[t])
{
memset(vis,0,sizeof(vis));
get(s,inf);
}
}
}
typedef int iarr[maxn];
iarr win,lose,c,d,In;
int main()
{
int n,m;read(n);read(m);
s=0,t=n+m+1;
for (int i=1; i<=n; ++i) read(win[i]),read(lose[i]),read(c[i]),read(d[i]);
for (int i=1,x,y; i<=m; ++i) add(s,i,1,0),read(x),read(y),add(i,x+m,1,0),add(i,y+m,1,0),++In[x],++In[y];
for (int i=1; i<=n; ++i) lose[i]+=In[i];
for (int i=1; i<=n; ++i) ans+=win[i]*win[i]*c[i]+lose[i]*lose[i]*d[i];//直接计算初始收益
for (int i=1; i<=n; ++i)
for (int j=1; j<=In[i]; ++j) add(i+m,t,1,2*c[i]*win[i]+c[i]+d[i]-2*d[i]*lose[i]),--lose[i],++win[i];
NetFlow();
write(ans);
flush();
return 0;
}