title
简化题意:
若干件物品,每个物品有一个原价,购买某件物品后可以以更优价购买另一件物品。每件物品有一个需求数目,既不能多买,也不能少买(如果需求 0 件就不能买,哪怕可能使得总价最优)。
analysis
这题有一个很棒的贪心算法,对于某件物品,我们怎样使得购买它的代价最小呢?
我们可以贪心的在这件物品所有的可行方案(原价与优惠价)里面取最小的,做一次乘法运算即可得出答案。
关键是,怎样使得这个贪心是正确的呢?我们发现,假如所有物品都被购买了的话,那么就可以保证这个贪心是正确的。
那么我们就可以把这个问题分成两个问题:
- 求出每个物品都买一件的最小代价和;
- 所有物品都购买了一件后,我们贪心算出剩余物品的代价和;
那么问题 1 怎么解决?
首先,我们对于每一个优惠方案 \((a,b,p)\),
- 如果物品 \(a\) 不需要:那么我们从 \(a\) 到连 \(b\) 一条权值为 \(inf\) 的边。道理很好想,既然物品 \(a\) 不能选择,那么选择物品 \(a\) 的优惠方案肯定都是非法的,把权值设为 \(inf\) 就保证这个方案不会被选到;
- 如果物品 \(b\) 不需要:那么我们从aa到 \(b\) 连一条权值为 \(0\) 的边。既然物品 \(b\) 不需要,我们可以看做购买物品 \(b\) 不需要任何代价;
- 如果都需要,那就正常的从 \(a\) 到 \(b\) 连一条权值为 \(p\) 的边。
这样我们就处理完了优惠方案。
对于原价,我们新建一个虚拟节点,并从这个虚拟节点往所有点连一条边,如果需要,权值为这个物品的原价,否则为 \(0\) 。
这样原图就是一个以虚拟节点为根的有向图,然后我们发现这个问题有几条优美的性质:
- 有向图有根节点;
- 所有点都要被选到;
- 边权之和最小;
所以,就是最小树形图了,跑一遍就好了。
code
#include<bits/stdc++.h>
const int maxn=64,maxm=maxn*maxn;
const double inf=1e9;
namespace IO
{
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;
}
}
using IO::read;
template<typename T>inline bool chkMin(T &a,const T &b) { return a>b ? (a=b, true) : false; }
template<typename T>inline bool chkMax(T &a,const T &b) { return a<b ? (a=b, true) : false; }
int n,m,r;
double ans;
struct Orz{int x,y; double z;}e[maxm];
namespace Edmonds
{
int pre[maxn];//fa[y] 当前连到 y 点的最短边的 x
double In[maxn];//In[x] 为当前连到 x 点的最短边的边权
int vis[maxn];//vis[x] 代表 x 所在链的代表元素,类似并查集
int id[maxn];//id[x] 代表 x 节点在第 id[x] 个环中
inline void zhu_liu()
{
while (1)
{
for (int i=1; i<=n; ++i) In[i]=inf;
for (int i=1,x,y; i<=m; ++i)
{
x=e[i].x, y=e[i].y;
if (x^y && e[i].z<In[y]) pre[y]=x, In[y]=e[i].z;//遍历所有边,对每个点找到最小的入边
}
int cnt=0;//cnt 代表当前图环的数量
for (int i=1; i<=n; ++i) vis[i]=id[i]=0;
for (int i=1; i<=n; ++i)
{
if (i==r) continue;
ans+=In[i];
int x=i;
for ( ; x^r && vis[x]^i && !id[x]; x=pre[x]) vis[x]=i;//找环
if (x^r && !id[x])
{
id[x]=++cnt;//把环上的点标记为同一点
for (int y=pre[x]; y^x; y=pre[y]) id[y]=cnt;
}
}
if (!cnt) break;//无环,得到解
for (int i=1; i<=n; ++i)
if (!id[i]) id[i]=++cnt;
for (int i=1,x,y; i<=m; ++i)//建立新图,缩点,重新标记
{
x=e[i].x, y=e[i].y;
if ( (e[i].x=id[x])^(e[i].y=id[y]) ) e[i].z-=In[y];//修改边权
}
n=cnt,r=id[r];
}
}
}
using Edmonds::zhu_liu;
double c[maxn];
int need[maxn],tot;
int main()
{
read(n);
for (int i=1; i<=n; ++i)
{
scanf("%lf",&c[i]), read(need[i]);
e[++tot]=(Orz){n+1,i,!need[i] ? 0 : c[i]};
}
read(m);
for (int i=1,a,b; i<=m; ++i)
{
read(a);read(b);
double p;scanf("%lf",&p);
if (!need[a]) e[++tot]=(Orz){a,b,inf};
else if (!need[b]) e[++tot]=(Orz){a,b,0};
else chkMin(c[b],p), e[++tot]=(Orz){a,b,p};
}
for (int i=1; i<=n; ++i) ans+=(need[i]-1)*c[i];
r=n+1, m+=n, ++n;
zhu_liu();
printf("%.2lf\n",ans);
return 0;
}
原文地址:https://www.cnblogs.com/G-hsm/p/11407617.html
时间: 2024-11-11 17:08:43