[NOI2012]美食节,P2050,最小费用最大流

本文深入探讨了SCOI2007修车问题的加强版解决方案,通过动态加边策略和SPFA算法优化了流量分配,实现了高效求解。详细讲解了如何构建网络流模型,以及在迭代过程中如何更新网络结构以降低时间复杂度。

正题

      这一题是[SCOI2007]修车的加强版。(先看修车再做这题哦!)

      首先是每种菜可能有多个人需求。

      怎么解决?我们把菜放左边,人放右边,然后左边把流量设成p_i就可以了。

      由于我们每次增广的流量是1,所以时间复杂度是很高的,是O(P^2mk),k为SPFA的不定常数。

      爆炸,而且建边的空间和时间我们也承受不起。

      我们再来考虑动态加边。

      一个厨师先选倒数第1道菜,再选倒数第2道菜。因为倒数第1道菜只用被算1次,倒数第二道菜会被算2次。

      我们可以先把每一种菜连向每个厨师的倒数第一道菜。

      假如a厨师的倒数第1道菜被选了,那么就把a厨师的倒数第二道菜开放。

      以此类推,一直到所有菜都被选完了。

// luogu-judger-enable-o2
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<iostream>
#include<queue>
using namespace std;

int n,m;
int num[810];
int p[50][110];
struct edge{
    int x,y,next,c,cos;
}s[9000010];
int first[90010],d[90010],mmin[90010],last[90010],len=1;
bool tf[90010];
int begin,end;
int tot=0;
queue<int> f;

void ins(int x,int y,int c,int cos){
    len++;s[len]=(edge){x,y,first[x],c,cos};first[x]=len;
    len++;s[len]=(edge){y,x,first[y],0,-cos};first[y]=len;
}

bool SPFA(int&flow,int&cost){
    memset(tf,false,sizeof(tf));tf[begin]=true;
    memset(d,63,sizeof(d));d[begin]=0;
    mmin[begin]=1e9;
    f.push(begin);
    while(!f.empty()){
        int x=f.front();f.pop();tf[x]=false;
        for(int i=first[x];i!=0;i=s[i].next){
            int y=s[i].y;
            if(d[y]>d[x]+s[i].cos && s[i].c>0){
                d[y]=d[x]+s[i].cos;mmin[y]=min(mmin[x],s[i].c);last[y]=i;
                if(!tf[y]){
                    tf[y]=true;
                    f.push(y);
                }
            }
        }
    }
    if(d[end]==d[end+1]) return false;
    flow+=mmin[end];
    cost+=mmin[end]*d[end];
    int now=end;
    while(now!=begin){
        s[last[now]].c-=mmin[end];s[last[now]^1].c+=mmin[end];
        now=s[last[now]].x;
    }
    now=s[last[end]].x;
    int x=(now-n)/tot+1,y=(now-n)%tot+1;
	if((now-n)%tot!=0) for(int k=1;k<=n;k++) ins(k,now+1,1,p[k][x]*y);
    return true;
}

void MCMF(){
    int flow=0,cost=0;
    while(SPFA(flow,cost));
    printf("%d",cost);
}

int main(){
    scanf("%d %d",&n,&m);
    int x;
    for(int i=1;i<=n;i++){
        scanf("%d",&x);
        tot+=x;
        ins(begin,i,x,0);
    }
	end=n+tot*m+1;
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) scanf("%d",&p[i][j]);
    for(int j=1;j<=m;j++) for(int k=1;k<=n;k++) ins(k,n+(j-1)*tot+1,1,p[k][j]*1);
    for(int i=n+1;i<end;i++) ins(i,end,1,0);
    MCMF();
}

      

### NOI2012 三重镇问题分析 在NOI2012竞赛中,"三重镇"问题是一个典型的组合数学与动态规划结合的题目。题目要求计算在特定条件下,某些组合的数量。解题的关键在于对组合数学和动态规划状态设计的深入理解。 #### 问题概述 题目要求在一个由 $n$ 个点构成的中,将这些点连接成若干边,使得中满足以下条件: 1. 每条边的权重是 $1$ 或 $2$。 2. 每个点的度数(即与该点相连的边的数量)必须是 $3$。 3. 所有点的度数总和必须等于 $2m$,其中 $m$ 是边的数量。 目标是计算满足这些条件的的数量。 #### 解题思路 问题的核心是组合数学与动态规划的结合。通过分析,可以将问题拆解为以下几个关键步骤: 1. **度数分配**:首先,需要确定每个点的度数为 $3$。由于每个边的权重为 $1$ 或 $2$,因此需要将度数分配到边中,并确保总和为 $2m$。 2. **边权重分配**:对于每一条边,其权重可以是 $1$ 或 $2$。需要确保所有边的权重总和等于 $m$。 3. **组合计算**:通过动态规划的方法,计算满足条件的的数量。 #### 动态规划状态设计 动态规划的状态设计是问题的核心。可以定义一个二维数组 $dp[i][j]$,其中: - $i$ 表示已经处理了前 $i$ 个点。 - $j$ 表示当前边的权重总和为 $j$。 状态转移方程为: - 对于每个点 $i$,其度数必须为 $3$。假设当前点的度数分配为 $k$,则需要从剩余的边中选择 $k$ 条边。 - 对于每条边,其权重可以是 $1$ 或 $2$。因此,状态转移需要考虑不同的权重分配情况。 #### 代码实现 以下是一个简化的动态规划实现示例,用于计算满足条件的的数量: ```python # 初始化动态规划数组 MOD = 10**9 + 7 max_points = 100 # 假设最多有100个点 max_weight = 200 # 假设最大权重总和为200 dp = [[0] * (max_weight + 1) for _ in range(max_points + 1)] dp[0][0] = 1 # 初始状态:0个点,权重总和为0 # 动态规划状态转移 for i in range(1, max_points + 1): for j in range(max_weight + 1): # 对于每个点,度数必须为3 for k in range(1, 4): # 假设k为当前点的度数 if j - k >= 0: dp[i][j] = (dp[i][j] + dp[i-1][j-k]) % MOD # 输出结果 print(dp[max_points][max_weight]) ``` #### 复杂度分析 该动态规划算法的时间复杂度为 $O(n \cdot m \cdot k)$,其中 $n$ 是点的数量,$m$ 是边的权重总和,$k$ 是每个点的度数可能的取值范围。通过优化状态转移,可以进一步降低复杂度。 #### 总结 NOI2012 "三重镇" 问题通过组合数学与动态规划的结合,展示了如何处理复杂的约束条件。关键在于对度数分配和边权重分配的细致分析,以及动态规划状态的设计与实现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值