首页 > 代码库 > 【BZOJ1229】【USACO 2008 Nov Gold】 4.Toys sadstory 三分+贪心

【BZOJ1229】【USACO 2008 Nov Gold】 4.Toys sadstory 三分+贪心

sad story:我们自己oj的数据貌似有点问题。标程WA了5%


题解:

复制去Google翻译翻译结果

首先引一下VFK神犇的证明来证明一下这道题是三分。。

{

我来告诉你世界的真相 = =

因为这题能最小费用最大流

每次最短路长度不降

所以是单峰的

最短路长度就是差分值。。

所以一阶导不降。。

是不是简单粗暴


你要证函数是单峰的。

当然是证斜率什么的

}

三分完初始买了多少个玩具,然后就是贪心。

首先我想说这个贪心真动规。虽然它真的是贪心。


首先先说一种错误的贪心。

就是从前往后扫,优先用快的洗,满足后面时间节点的玩具需求。

然后可以发现

A B  2 3 

2 3  a b

快速洗衣店1天

慢的洗衣店3天,

就可以卡掉。很轻松。。。好吧,总之这种贪心是错的。

附此错误代码:

 

#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
#define N 60
#define M 101000
#define inf 0x3f3f3f3f
using namespace std;
int need[M];
int n,d1,d2,c1,c2,m;

int l1,l2;
int nd[M],rnd[M];
void init(int mid)
{
	int i,j,k;
	for(i=1;i<=n;i++)nd[i]=rnd[i]=need[i];
	for(i=1;i<=n;i++)
	{
		if(mid<=nd[i])
		{
			nd[i]-=mid;
			return ;
		}
		else mid-=nd[i],nd[i]=0;
	}
	return ;
}
long long check(int mid)
{
	long long ret=0;
	int i,j,k;
	init(mid);
	l1=1+d1,l2=1+d2;
	for(i=1;i<n;i++)
	{
		while(l2<=n)
		{
			if(rnd[i]<=nd[l2])
			{
				nd[l2]-=rnd[i];
				ret+=rnd[i]*c2;
				break;
			}
			else {
				ret+=nd[l2]*c2;
				rnd[i]-=nd[l2];
				nd[l2++]=0;
			}
		}
		if(rnd[i])while(l1<=n)
		{
			if(rnd[i]<=nd[l1])
			{
				nd[l1]-=rnd[i];
				ret+=rnd[i]*c1;
				rnd[i]=0;
				break;
			}
			else {
				ret+=nd[l1]*c1;
				rnd[i]-=nd[l1];
				nd[l1++]=0;
			}
		}
	}
	for(i=1;i<=n;i++)ret+=nd[i]*(c1-c2);
	return ret+mid*m;
}
int main()
{
	freopen("test.in","r",stdin);
	int i,j,k;
	int l=0,r=1,mid,midmid,temp=0;

	scanf("%d%d%d%d%d%d",&n,&d1,&d2,&c1,&c2,&m);
	for(i=1;i<=n;i++)scanf("%d",&need[i]),r+=need[i];
	if(d1>d2)swap(d1,d2),swap(c1,c2);if(c1<c2)c2=c1;
	for(i=1;i<d1;i++)temp+=need[i];
	for(i=d1;i<=n;i++)
	{
		temp+=need[i];
		temp-=need[i-d1];
		l=max(l,temp);
	}
	long long ans=inf;
	while(1)
	{
		if(r-l<=2)
		{
			for(i=l;i<r;i++)ans=min(ans,check(i));
			break;
		}
		mid=l+(r-l)/3,midmid=l+2*(r-l)/3;
		long long reta=check(mid);
		long long retb=check(midmid);
		if(reta<retb)r=midmid;
		else l=mid;
	}
	cout<<ans<<endl;
	return 0;
}

然后就有了正确的思想(标程的思想):

同样很简单、

就是维护几个队列神马的。

优先采用快的洗衣店,然后扫之前采用过的快的洗衣店的次数以及时间

发现有一些可以选择慢的洗衣店,然后当前选择快的洗衣店来代替。


这样进行答案的修改。

最后可以return一个当前玩具数量的函数值。

最后三分求个单峰就好了。

附个usaco的标程。

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

using namespace std;

#define MAX (100005)
#define INF (1000000000)

int T[MAX];
int queue[MAX], num[MAX];
int sn,sm,so,en,em,eo;

int D,N1,N2,C1,C2,Tc;

inline void add_new(int x,int q){
	queue[en]=x;
	num[en++]=q;
}

int f(int t){
	sn=sm=so=en=em=eo=0;
	int r = (Tc-C2)*t;

	add_new(-200000,t);
	for(int d=0;d<D;d++){
		while(sn!=en && d-queue[sn] >= N1) { num[em]=num[sn]; queue[em++] = queue[sn++]; }
		while(sm!=em && d-queue[sm] >= N2) { num[eo]=num[sm]; queue[eo++] = queue[sm++]; }
		int i = T[d];
		while(i > 0){
			if(so!=eo){
				if(num[eo-1] > i){
					r += C2*i;
					num[eo-1]-=i;
					break;
				}
				else {
					r += C2*num[eo-1];
					i -= num[eo-1];
					eo--;
				}
			}
			else if(sm!=em){
				if(num[em-1] > i){
					r += C1*i;
					num[em-1]-=i;
					break;
				}
				else {
					r += C1*num[em-1];
					i -= num[em-1];
					em--;
				}
			}
			else return INF;
		}
		add_new(d,T[d]);
	}
	return r;
}

int ternary_search(int s,int e){
	while(1){
		if(e-s <= 5){
			int m = f(s);
			for(int i=s+1;i<e;i++) m = min(m,f(i));
			return m;
		}
		int x = s+(e-s)/3, y = s+2*(e-s)/3;
		int a = f(x);
		if(a!= INF && a <= f(y)) e=y;
		else s=x;
	}
}

int main(){
	scanf("%d%d%d%d%d%d",&D,&N1,&N2,&C1,&C2,&Tc);
	if(N1 > N2){
		N1 ^= N2; N2 ^= N1; N1 ^= N2;
		C1 ^= C2; C2 ^= C1; C1 ^= C2;
	}
	if(C1 < C2){
		C2 = C1;
	}
	int tsum = 0;
	for(int i=0;i<D;i++) {scanf("%d",&T[i]); tsum += T[i]; }
	printf("%d\n",ternary_search(0,tsum+1));
	return 0;
}

再附个原版、

#include <stdio.h>
#include <stdlib.h>
#include <algorithm>

using namespace std;

#define MAX (100005)
#define INF (1000000000)

int T[MAX];
int queue[MAX], num[MAX];
int sn,sm,so,en,em,eo;
/* These variables control the queue and point to the start 
 * and end of new, middle, and old toys, respectively. 
 * New toys are ones washed less than N1 days ago, old toys are 
 * washed at least N2 days ago, and middle toys are all the rest.
 * We essentially keep three queues in a single array, but the manner
 * in which we add/remove elements guarantees that we never have to
 * worry about memory overlap.
 */

int D,N1,N2,C1,C2,Tc;

inline void add_new(int x,int q){
  queue[en]=x;
  num[en++]=q;
}

void flush_new(int x){
  while(sn!=en && x-queue[sn] >= N1) { num[em]=num[sn]; queue[em++]
= queue[sn++]; }
}

void flush_mid(int x){
  while(sm!=em && x-queue[sm] >= N2) { num[eo]=num[sm]; queue[eo++]
= queue[sm++]; }
}

int f(int t){ //find the minimum cost given that we use t toys
  sn=sm=so=en=em=eo=0;
  int r = (Tc-C2)*t;
  /*
   * In the following algorithm, we pay even to wash the initial toys, so 
   * we have to subtract this out of our initial cost estimate for 
   * purchasing the toys. This is valid as long as we use all of the toys 
   * we purchase (which is why we start the ternary search at tsum+1 
   * instead of 5000001).
   */
  add_new(-200000,t);
  for(int d=0;d<D;d++){
    flush_new(d); flush_mid(d); //move any toys toys whose status changes 
      //from being new to middle or middle to old to the appropriate queue
    int i = T[d];
    while(i > 0){ //we deal with the toys in batches to make 
      //the runtime of this function O(N) instead of O(sum ti)
      if(so!=eo){ //if there are any old toys
        if(num[eo-1] > i){ //if this batch has more toys than we need, we
can stop here
          r += C2*i;
          num[eo-1]-=i;
          break;
        }
        else { //otherwise, use all toys in this batch
          r += C2*num[eo-1];
          i -= num[eo-1];
          eo--;
        }
      }
      else if(sm!=em){ //else if there are any middle toys
        if(num[em-1] > i){
          r += C1*i;
          num[em-1]-=i;
          break;
        }
        else {
          r += C1*num[em-1];
          i -= num[em-1];
          em--;
        }
      }
      else return INF; //if there are no available toys, 
        //we can't find a solution with this many toys
    }
    add_new(d,T[d]); //put the toys we used today back into the queue of toys
  }
  return r;
}

int ternary_search(int s,int e){
  while(1){
    if(e-s <= 2){ //when e-s is small enough that our ternary search 
      //can get stuck, just handle the end manually
      int m = f(s);
      for(int i=s+1;i<e;i++) m = min(m,f(i));
      return m;
    }
    int x = s+(e-s)/3, y = s+2*(e-s)/3; //sample values 1/3 and 2/3 
      //of the way through the remaining interval
    int a = f(x); //f(x) = -1 if x was too few toys to have enough toys each day
    if(a!= INF && a <= f(y)) e=y;
    else s=x;
  }
}

int main(){
  FILE *fin = fopen("toy.in","r");
  FILE *fout = fopen("toy.out","w");
  fscanf(fin,"%d%d%d%d%d%d",&D,&N1,&N2,&C1,&C2,&Tc);
  if(N1 > N2){ //set N2 to be greater than N1
    N1 ^= N2; N2 ^= N1; N1 ^= N2;
    C1 ^= C2; C2 ^= C1; C1 ^= C2;
  }
  if(C1 < C2){ //if faster way is cheaper
    C2 = C1; //then set its cost to that of the slower way, 
      //since we could always just use the slower way instead
  }
  int tsum = 0;
  for(int i=0;i<D;i++) { fscanf(fin,"%d",&T[i]); tsum += T[i]; }
  fprintf(fout,"%d\n",ternary_search(0,tsum+1));
  fclose(fin); fclose(fout);
  return 0;
}


最后是该网址

http://cerberus.delosent.com:794/TESTDATA/NOV08.toy.htm


【BZOJ1229】【USACO 2008 Nov Gold】 4.Toys sadstory 三分+贪心