[JSOI2009]球队收益 Solution

题意:有 n n n个球队,如果一支球队胜场为 x i x_i xi,负场为 y i y_i yi,那么他们的奖金即为 C i × x i 2 + D i × y i 2 C_i\times x_i^2+D_i\times y_i^2 Ci×xi2+Di×yi2。现在知道了这些球队现在的胜场和负场,以及一些不确定的比赛,问总奖金最少是多少。
那么考虑一个球队多赢了一把奖金的变化。
先假设这个球队输了所有能输的比赛,那么现在他们的胜场是 x x x,负场是 y y y,现在他们赢回来了一局,那么胜场就是 x + 1 x+1 x+1,负场是 y − 1 y-1 y1。奖金的变化就是 ( x + 1 ) 2 × C i + ( y − 1 ) 2 × D i − x 2 × C i − y 2 × D i = ( 2 × x + 1 ) × C i − ( 2 × y − 1 ) × D i (x+1)^2\times C_i+(y-1)^2\times D_i-x^2\times C_i-y^2\times D_i=(2\times x+1)\times C_i-(2\times y-1)\times D_i (x+1)2×Ci+(y1)2×Dix2×Ciy2×Di=(2×x+1)×Ci(2×y1)×Di
所以现在就可以这样建出边了:

  • 把不确定的对局看做点,与源点相连,容量为1
  • 把不确定的对局对应的点和对局双方连边
  • 把所有的球队按照上述方式和源点连最多胜场数量的边。

c o d e : code: code:

#include <bits/stdc++.h>
#define regi register int
int n,m;
int S,T;
int ans;
int mincost;
int dis[101000];
int vis[101000];
int maybe[1000000];
//maybe表示这个队最多能再获胜几把 
int a[1000000],b[1000000];
int c[1000000],d[1000000];
int head[1000000],tot=1;
std::queue<int>q;
struct edge{
	int to;
	int nxt;
	int flow;
	int cost;
}e[1000000];
void add(int x,int y,int flow,int cost){
	e[++tot]={y,head[x],flow,cost};
	head[x]=tot;
	e[++tot]={x,head[y],0,-cost};
	head[y]=tot;
}
bool spfa(){
	memset(dis,0x3f,sizeof dis);
	memset(vis,0,sizeof vis);
	q.push(S);
	dis[S]=0;
	while(!q.empty()){
		int x=q.front();
		q.pop();
		vis[x]=0;
		for(regi i=head[x];i;i=e[i].nxt){
			regi y=e[i].to;
			if(e[i].flow&&dis[y]>dis[x]+e[i].cost){
				dis[y]=dis[x]+e[i].cost;
				if(!vis[y]){
					vis[y]=1;
					q.push(y);
				}
			}
		}
	}
	return dis[T]!=0x3f3f3f3f;
}
int dfs(int x,int min){
	vis[x]=1;
	if(x==T)
		return min;
	int flow=0;
	for(regi i=head[x],w;i;i=e[i].nxt){
		int y=e[i].to;
		if((!vis[y]||y==T)&&e[i].flow>0&&dis[y]==dis[x]+e[i].cost)
			if(w=dfs(y,std::min(min-flow,e[i].flow))){
				mincost+=w*e[i].cost;
				e[i].flow-=w;
				e[i^1].flow+=w;
				flow+=w;
			}
	}
	return flow;
}
void dinic(){
	while(spfa()){
		vis[T]=1;
		while(vis[T]){
			memset(vis,0,sizeof vis);
			dfs(S,0x3f3f3f3f);
		}
	}
}
main(){
  scanf("%d%d",&n,&m);
  S=1,T=2;
  for(regi i=1;i<=n;++i)
    scanf("%d%d%d%d",&a[i],&b[i],&c[i],&d[i]);
  for(regi i=1,x,y;i<=m;++i){
  	scanf("%d%d",&x,&y);
  	maybe[x]++;
  	maybe[y]++;
  	b[x]++;
  	b[y]++;
  	//先假设他们全败了 
  	add(S,i+2,1,0);
  	add(i+2,2+m+x,1,0);
  	add(i+2,2+m+y,1,0);	
  }
  for(regi i=1;i<=n;++i){
    ans+=a[i]*a[i]*c[i]+b[i]*b[i]*d[i];
    //先计算所有团队能败的全败需要的资金 
    for(regi j=1;j<=maybe[i];++j){
    	add(2+m+i,T,1,(2*a[i]+1)*c[i]-(2*b[i]-1)*d[i]);
    	++a[i];
    	--b[i];
    	//记录现在的胜场和负场,按照之前的建边方式建边 
    }
  }
  dinic();
  printf("%d\n",ans+mincost);
	return 0;
}
/*代码实现:
记录每个球队最多能赢的胜场,然后建边。 
*/ 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
根据引用[1],dp[u][j]表示在u子树中选取恰好j个人时能获得的最大价值。而根据引用,该问题的时间复杂度为O(log2​104×nm)。 对于洛谷P2143 [JSOI2010] 巨额奖金问题,我们可以使用动态规划来解决。具体步骤如下: 1. 首先,我们需要构建一棵树来表示员工之间的关系。树的根节点表示公司的总经理,其他节点表示员工。每个节点都有一个权值,表示该员工的奖金金额。 2. 接下来,我们可以使用动态规划来计算每个节点的dp值。对于每个节点u,我们可以考虑两种情况: - 如果选择节点u,则dp[u][j] = dp[v][j-1] + value[u],其中v是u的子节点,value[u]表示节点u的奖金金额。 - 如果不选择节点u,则dp[u][j] = max(dp[v][j]),其中v是u的子节点。 3. 最后,我们可以通过遍历树的所有节点,计算出dp[u][j]的最大值,即为所求的巨额奖金。 下面是一个示例代码,演示了如何使用动态规划来解决洛谷P2143 [JSOI2010] 巨额奖金问题: ```python # 构建树的数据结构 class Node: def __init__(self, value): self.value = value self.children = [] # 动态规划求解最大奖金 def max_bonus(root, j): dp = [[0] * (j+1) for _ in range(len(root)+1)] def dfs(node): if not node: return for child in node.children: dfs(child) for k in range(j, 0, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-1] + node.value) for child in node.children: for k in range(j, 0, -1): for l in range(k-1, -1, -1): dp[node.value][k] = max(dp[node.value][k], dp[node.value][k-l-1] + dp[child.value][l]) dfs(root) return dp[root.value][j] # 构建树 root = Node(1) root.children.append(Node(2)) root.children.append(Node(3)) root.children[0].children.append(Node(4)) root.children[0].children.append(Node(5)) root.children[1].children.append(Node(6)) # 求解最大奖金 j = 3 max_bonus_value = max_bonus(root, j) print("最大奖金为:", max_bonus_value) ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值