【题目】
原题地址
原题说的很清楚了,在这里也贴出来吧。
奶酪店里最近出现了m只老鼠!它们的目标就是把生产出来的所有奶酪都吃掉。奶酪店中一天会生产n块奶酪,其中第i块的大小为pi,会在第ri秒被生产出来,并且必须在第di秒之前将它吃掉。第j只老鼠吃奶酪的速度为sj,因此如果它单独吃完第i快奶酪所需的时间为pi/sj。老鼠们吃奶酪的习惯很独特,具体来说:
(1) 在任一时刻,一只老鼠最多可以吃一块奶酪;
(2) 在任一时刻,一块奶酪最多被一只老鼠吃。
由于奶酪的保质期常常很短,为了将它们全部吃掉,老鼠们需要使用一种神奇的魔法来延长奶酪的保质期。将奶酪的保质期延长T秒是指所有的奶酪的di变成di+T。同时,使用魔法的代价很高,因此老鼠们希望找到最小的T使得可以吃掉所有的奶酪。
【题目分析】
首先这种题目很容易就想到二分答案,然后对时间进行离散,然后跑网络流,但是会发现这个限制十分难处理,而这题给了一种很妙的转换!
【解题思路】
现在先不考虑在一个时刻,每一块奶酪只能被一只老鼠吃。
那么按照事件的发生和结束把事件间隔的时间段离散成一个个点,形象来说就是拆老鼠。
1.每个新点向时间段内存在的每个奶酪连边,容量为inf。
2.源点向每个老鼠的点连边,容量为si*len,si表示老鼠吃奶酪的速度,len表示时间段的长度。
3.每个奶酪向汇点连边,容量为奶酪的体积pi,只要奶酪向汇点连的边满流,那么奶酪就是吃完了。
YY一下可以发现我们这样建图保证了每个老鼠只能在同一时间吃一块奶酪,
因为我们限定了老鼠吃奶酪的最大体积,只要吃的体积满足限制,那么就一定不会同一时间吃多块奶酪,反之不合法。
最难的部分在于如何保证题目中的限制。然后下面是一种不知道怎么想到的方法:差分。这里直接将做法放出来然后口胡一下意义。
我们先把所有的速度降序排序,然后进行差分,把每只老鼠的速度变成v[i]=s[i]-s[i+1]。
1.源点向每只老鼠连边,容量为len*i*v[i]。(这里的限制包含了对变化小于i的老鼠的限制)
2.每只老鼠向奶酪连边,容量为len*v[i]。
3.每块奶酪向汇点连边,容量为p[i]。
这里我们限制了所有老鼠吃奶酪总量的上界,可以发现只要最后最大流满流,显然我们可以找到一种构造流量的方法,把差分之后的速度还原为原来的速度。还原流量的时候从小到大考虑。
这部分还是很难理解的,LG的题解处给出了一种线性基的理解方法:
先考虑每一块奶酪只能被一只老鼠吃。
首先,可以吃的奶酪量可以看做各个老鼠速度的线性组合。
而差分后线性基没有改变,所以张量不变。
其次,由于时间可以无限拆分,那么张量最大值即为老鼠差分后速度之和,即为吃的最快的老鼠原速度,因此张量0~最大值。因此我们只需要限制最大值即可,在第二种建边的时候已经限制,全部满流时最大。
然后,考虑每一只老鼠只能吃一块奶酪。
类似上面所说的,我们可以将老鼠吃不同的奶酪也看做线性组合,同样只需要限制最大值,即为所有老鼠一起吃奶酪,在第三种建边时已经限制,全部满流时最大。
依旧很难理解,不过这篇东西我就放在这里了,等以后不知道什么时候想起来再回来看。
【参考代码】
#include
using namespace std;
const int N=1e5+10;
const double eps=1e-7;
const double INF=1e9;
int n,m,S,T,tot,cnt;
int head[N],dep[N],cur[N];
double L,R,sum;
double p[N],r[N],d[N],sr[N],v[N];
queue<int>q;
int read()
{
int ret=0,f=1;char c=getchar();
while(!isdigit(c)){if(c=='-')f=0;c=getchar();}
while(isdigit(c)){ret=(ret<<1)+(ret<<3)+(c^48);c=getchar();}
return f?ret:-ret;
}
struct Tway
{
int v,nex;
double w;
};
Tway e[N];
bool cmp(double A,double B)
{
return A>B;
}
void add(int u,int v,double w)
{
//printf("%d %d %lf\n",u,v,w);
e[++tot]=(Tway){v,head[u],w};head[u]=tot;
e[++tot]=(Tway){u,head[v],0};head[v]=tot;
}
void init()
{
n=read();m=read();sum=0;
for(int i=1;i<=n;++i)
{
p[i]=read();r[i]=read();d[i]=read();
sum+=p[i];
}
for(int i=1;i<=m;++i)
v[i]=read();
sort(v+1,v+m+1,cmp);
R=1.0*sum/v[1]+1.0;L=0;
for(int i=1;i1];
}
bool bfs()
{
memset(dep,-1,sizeof(dep));
q.push(S);dep[S]=0;
while(!q.empty())
{
int u=q.front();q.pop();
for(int i=head[u];i;i=e[i].nex)
{
int v=e[i].v;
if(dep[v]==-1 && e[i].w>eps)
{
dep[v]=dep[u]+1;
q.push(v);
}
}
}
return ~dep[T];
}
double dfs(int x,double flow)
{
if(x==T || flowreturn flow;
double used=0,w;
for(int &i=cur[x];i;i=e[i].nex)
{
int v=e[i].v;
if(e[i].w>eps && dep[v]==dep[x]+1)
{
w=dfs(v,min(flow-used,e[i].w));
e[i].w-=w;e[i^1].w+=w;used+=w;
if(fabs(flow-used)return flow;
}
}
return used;
}
double dinic()
{
double ret=0;
while(bfs())
{
memcpy(cur,head,sizeof(head));
ret+=dfs(S,INF);
}
return ret;
}
void rebuild(double x)
{
tot=1;memset(head,0,sizeof(head));
for(int i=1;i<=n;++i)
{
add(S,i,p[i]);
sr[i*2-1]=r[i],sr[i*2]=d[i]+x;
}
sort(sr+1,sr+2*n+1);cnt=n;
for(int i=1;i<=m;++i)
{
for(int j=2;j<=2*n;++j)
{
if(sr[j]-sr[j-1]continue;
++cnt;add(cnt,T,i*v[i]*(sr[j]-sr[j-1]));
for(int k=1;k<=n;++k)
{
if(r[k]-sr[j-1]-eps))
add(k,cnt,v[i]*(sr[j]-sr[j-1]));
}
}
}
}
void solve()
{
double tsum=sum;S=0;T=2*m*n+n+1;cnt=0;
while(L+epsdouble mid=(L+R)/2.0;
rebuild(mid);
double mx=dinic();
//printf("tsum:%lf mx:%lf\n",tsum,mx);
if(tsum-mxelse
L=mid;
}
printf("%lf\n",L);
}
int main()
{
freopen("LGP2570.in","r",stdin);
freopen("LGP2570.out","w",stdout);
int cas=read();
while(cas--)
{
init();
solve();
}
return 0;
}
【总结】
逐层思考,最后我会发现我不会做。