[JSOI2008] 小店购物

题面

小店购物

题解

这道题建边方式很套路。

定义有向边 ( u , v , w ) (u,v,w) (u,v,w) 表示买了 u u u 之后,能以边权 w w w 的价格买 v v v

对于原价的物品,建一个超级源点,分别连接每个物品。对于特价的物品,根据以上的定义,连接两个有依赖关系的物品。

因为通过这样的定义方式,每个物品 v v v 能被买,当且仅当 u u u 被买,为了花费最小,所以我们肯定要选取其中边权最小的边。

所以最后就会发现,要买完所有的物品,其实就是求这幅有向图的最小树形图。

根据贪心的思想,因为每个物品都要买 (对于不用买的物品,我们可以看做是以 0 0 0 的价格买入),而以能以它的最低价格买就以它的最低价格买肯定是最优的 (即 u u u 是要买的),所以我们在满足这个条件的前提下,记录最小的价格,先提前算出买 t o t i − 1 tot_i - 1 toti1 个物品的价格,然后跑最小树形图算出买剩余的一个的价格。

最小树形图自然就是用朱刘算法啦。

小细节

  • 对于买 u u u 优惠 v v v u u u 不用买的情况,边权应该为无穷大。

代码

#include<iostream>
#include<cstdio>
#include<cstring>
using namespace std;
const int INF = 1e9;
const int N = 105;
const int M = N * N + 5;
double in[N],val[N],w;  
int pre[N],tot[N],vis[N],id[N];
struct edge{
    int from,to;
	double dis;
}a[M];
double work(int n,int m,int root) {
	double ans = 0;
	while(true){
		for(int i = 1;i <= n; i++) in[i] = INF;
		for(int i = 1;i <= m; i++){
			int u = a[i].from,v = a[i].to;
			if(u != v && a[i].dis < in[v])
				in[v] = a[i].dis,pre[v] = u;
		}
		int cnt = 0;
		memset(vis,0,sizeof(vis));
		memset(id,0,sizeof(id));
		for(int i = 1;i <= n;i++){
			if(i == root)continue;
			ans += in[i];
			int v = i;
			while(vis[v] != i && !id[v] && v != root)
				vis[v] = i,v = pre[v];
			if(!id[v] && v != root){
				id[v] = ++cnt;
				for(int u = pre[v];u != v;u = pre[u])
					id[u] = cnt;
			}
		}
		if(cnt == 0) break;
		for(int i = 1;i <= n;i++)
			if(!id[i]) id[i] = ++cnt;
		for(int i = 1;i <= m;i++){
			int u = a[i].from,v = a[i].to;
			a[i].from = id[u],a[i].to = id[v];
			if(id[u] != id[v]) a[i].dis -= in[v];
		}
		root = id[root];
		n = cnt;
	}
	return ans;
}
int main(){
    int n,m;
    scanf("%d",&n);
    for(int i = 1; i <= n; i++) {
    	scanf("%lf%d",&val[i],&tot[i]);
    	a[i].from = n + 1;
		a[i].to = i; a[i].dis = tot[i] == 0 ? 0 : val[i];
	}
    scanf("%d",&m);
	for(int i = 1,u,v; i <= m; i++) {
		scanf("%d%d%lf",&u,&v,&w);
		if(tot[u] == 0) {
			a[n + i].from = u;
			a[n + i].to = v; a[n + i].dis = INF;
		}
		else if(tot[v] == 0) {
			a[n + i].from = u;
			a[n + i].to = v; a[n + i].dis = 0;
		} 
		else {
			a[n + i].from = u;
			a[n + i].to = v; a[n + i].dis = w;
			val[v] = min(val[v],w);
		}	
	} double res = 0;
	for(int i = 1; i <= n; i++)
		if(tot[i]) res += (tot[i] - 1) * val[i];
    res += work(n + 1,n + m,n + 1);
    printf("%.2lf\n",res);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值