【游戏通关】解题报告

背景背景 


  机房里的人都十分认真地在编程,但总有一些人会偷偷玩游戏。。。。。。 


问题描述问题描述 
问题描述问题描述 


   XY 经常在机房里偷偷玩游戏,于是他也经常被CJH 教练批评。但屡次的批评一点作用也 
没有,你看他又开始玩起了游戏。 
  这次XY 可碰上难题了,因为据可靠的线报CJH 教练在不久后就回来机房,但XY 需要完 
成N 个任务才能将这个游戏通关。 
  每个任务完成时限T,就是这个任务必须在时间T 之前完成(你可以认为游戏刚开始的时 
间为1),还有完成这个任务XY 可以获得一定的奖励W。由于XY 娴熟的技术以及任务的简 
单,他可以在一个单位时间将任务完成。 
  XY 想要在CJH 教练到来之前将任务全部完成,同时他也想获得最多的奖励。这个他本来 
可以编程自己完成的,但是为了能马上将游戏通关,他需要全神贯注进去。于是他没有空编 
程计算,于是他希望你能帮助他将问题的答案计算出来。 


输入格式输入格式 
输入格式输入格式 


  输入数据第一行有一个整数N ,表示需要完成的任务数目; 
  接下来N  行,每行两个整数T,W  (中间用一个空格隔开),分别表示完成这个任务的最 后期限和完成这个任务后获得的奖励。 

输出格式输出格式 
输出格式输出格式 

  输出数据有且仅有一行,只包含一个整数S,表示最多获得的奖励。 

样例输入输出样例输入输出 
样例输入输出样例输入输出 

Sample #1 
game.in                                    game.out 
2                                          5 
1 5 
1 4 

Sample #2 
game.in                                    game.out 
5                                          15 
2 3 
1 2 
4 5 
1 3 
3 4 

样例解释样例解释 
样例解释样例解释 

  对于样例2 XY 可以选择完成任务1,3,4 和5,这样他可以获得奖励15。 

数据规模数据规模 
数据规模数据规模 

对于10%的数据,N≤100,Ti≤100,Wi≤2000; 
对于30%的数据,N≤1000,Ti≤5000,Wi≤2000; 
对于50%的数据,N≤10000,Ti≤20000,Wi≤2000; 
对于100%的数据,N≤200000,Ti≤200000,Wi≤2000 。 

时间限制时间限制 
时间限制时间限制 
1s 

这道题一看感觉是一道简单题,就随便想了一个贪心策略来解,结果悲剧地WA0。这是个血的教训啊。听上去讲解的同学,他们的贪心策略都是证明过的。
唉。。。其实考试的时候时间也没有那么紧张,证明一下也何尝不好呢??

我的贪心策略就是,每一时刻选择一个期限为当前时间的最大价值的任务来做。其实这个反例太多,只是我没有去想过。
如四个任务的时间 1 1 2 2,价值1 1 100 100,最大价值为200而不是101。

这道题的好方法有几个。
梁旭罡:
使用了两个接近正解的贪心策略,每次从中取出较小值(这两个贪心策略都是过贪的),结果AC了。这个值得学习。
program seat;
const
  f1=2;
  f2=3;
var

  ex,jj,jz:array[1..2,1..2]of int64;
  n,m:longint;

procedure init;
begin
  assign(input,'seat.in');
  reset(input);
  assign(output,'seat.out');
  rewrite(output);
end;

function f(n:int64):int64;
var
  i:longint;
  nn:int64;
  p:boolean;
begin
  jz[1,1]:=0;
  jz[1,2]:=1;
  jz[2,1]:=1;
  jz[2,2]:=1;
  p:=true;
  nn:=n-2;
  while nn>0 do begin
    if nn and 1=1  then begin
      if not p then begin
        ex[1,1]:=(jj[1,1]*jz[1,1]+jj[1,2]*jz[2,1])mod m;
        ex[1,2]:=(jj[1,1]*jz[1,2]+jj[1,2]*jz[2,2])mod m;
        ex[2,1]:=(jj[2,1]*jz[1,1]+jj[2,2]*jz[2,1])mod m;
        ex[2,2]:=(jj[2,1]*jz[1,2]+jj[2,2]*jz[2,2])mod m;
        jj:=ex;
      end
      else begin jj:=jz;p:=false end;
    end;
    ex[1,1]:=(jz[1,1]*jz[1,1]+jz[1,2]*jz[2,1])mod m;
    ex[1,2]:=(jz[1,1]*jz[1,2]+jz[1,2]*jz[2,2])mod m;
    ex[2,1]:=(jz[2,1]*jz[1,1]+jz[2,2]*jz[2,1])mod m;
    ex[2,2]:=(jz[2,1]*jz[1,2]+jz[2,2]*jz[2,2])mod m;
    jz:=ex;
    nn:=nn shr 1;
  end;

  case n of
    1:begin f:=f1;exit;end;
    2:begin f:=f2;;exit;end;
  end;
  f:=(f1*jj[1,2]+f2*jj[2,2])mod m;
end;

procedure main;
var
  t,i:longint;
begin
  readln(t,m);
  for i:=1 to t do begin
    readln(n);
    writeln(f(n));
  end;
  close(output);
end;

begin
  init;
  main;
end.
汪维正+彭靖田
贪心+堆优化
我打出来了这个。
思路:因为正向贪容易推出反例来,因此考虑到了反向来贪心,这是个好思路,值得学习呀。

就是从最大的时间到最小的时间,最后时刻,只能够选择期限为这个时刻的任务,否则就没得做了,因此做这个时刻价值最大的任务,用反证法,如果做其他的任务,解只可能小于或等于最优解。
剩下来的任务保留,或不做,或在前面时刻做,每一个时刻都把期限为当前时刻的任务加入进来,选择最优的,最优的选择同上。
因此考虑使用堆优化。维护一个大根堆。
(常常易忘记的del()函数的返回值)

这个思路不难,也易证明。这个反向来贪心的思路不错!!还有以后时间不紧的情况下一定要证明自己的策略!!
//用堆的贪心方法 

#include <cstdio>
#include <cstdlib>
long n;
struct node
{
	long t;
	long a;
};
const long oo = 0x7fff0000;
long heap[200002];
long size = 0;
long start[200002];
node task[200002];
long ans = 0;

void swap(long a,long b)
{
	long tmp = heap[a];
	heap[a] = heap[b];
	heap[b] = tmp;
}

void adjust_down(long l)
{
	while((l<<=1)<size+1)
	{
		if (l+1<=size&&task[heap[l+1]].a>task[heap[l]].a)l++;
		if (task[heap[l>>1]].a<task[heap[l]].a) swap(l>>1,l);
		else break;
	}
}

void adjust_up(long l)
{
	while (l>1)
	{
		if (task[heap[l]].a>task[heap[l>>1]].a)swap(l,l>>1);
		else break;
		l >>= 1;
	}
}

long del()
{
	if (size<=0) return -1;
	long tmp = heap[1];
	heap[1] = heap[size];
	size--;
	adjust_down(1);
	return tmp;
}

void insert(long l)
{
	size++;
	heap[size] = l;
	adjust_up(size);
}

int bigger(const void *a,const void* b)
{
	long aa = *(long*)a;
	long bb = *(long*)b;
	if (aa>bb)
		return 1;
	else if (aa==bb)
		return 0;
	return -1;
}

int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%ld",&n);
	for (long i=1;i<n+1;i++)
	{
		scanf("%ld%ld",&task[i].t,&task[i].a);
	}
	qsort(task+1,n,sizeof(node),&bigger);
	
	for (long i=n;i>0;i--)
	{
		if (start[task[i].t]==0)
			start[task[i].t]=i;
	}
	
	long index = n;
	
	for (long i=task[index].t;i>0;i--)
	{
		while (index>=0&&task[index].t==i)
		{
			insert(index);
			index--;
		}
		long tmp = del();
		if (tmp>0)
			ans += task[tmp].a;
	}
	printf("%ld",ans);
}

刘德恩:
贪心+并查集
代码比较简短,思路比较巧妙。
仍然是贪心。

每次选择价值最大的任务,如果能做(接下来讨论)就做,不能做则放弃并选择次优。
能做即从1时刻到任务的最后时刻有空可以做,
做一个任务,一定要完成,则尽量晚,更多的任务可以有时间完成。只会更优,不会更差。
这就可以得到一个朴素的贪心策略。

考虑优化,每扫描到一个任务,要让它能够完成,并时间尽量晚,则应该把它插入1~当前时刻离当前时刻最近的点。
因此想到了区间,又因此想到了集合,并查集。
每次插入点的操作都是一次合并的操作。区间中的点的父亲都指向最左边的那一个点的左边,即最早有空隙的地方。
考虑1~i全部合并的情况,即为所有的i父亲指向0。则1~i不再有空隙了。

这是一个好方法,但是不容易想到。

这个给我启示!!
有时候思路要打开。例如这里,要能从插入的空隙联想到连续区间,再联想到并查集,再联想到父亲指向空隙!!
这思维还是比较跳跃的。。。

我的代码
 
 
#include <cstdio>
#include <cstdlib>


struct node
{
	long t;
	long a;
};


long fa[200002];


int bigger(const void* a,const void* b)
{
	node* aa = (node*) a;
	node* bb = (node*) b;
	
	if (aa->a>bb->a)
		return -1;
	if (aa->a==bb->a)
		return 0;
	return 1;
}


long getroot(long a)
{
	if (fa[a]==a) return a;
	return fa[a] = getroot(fa[a]);
}


void merge(long a,long b)
{
	fa[getroot(a)] = getroot(b);
}


long n;
node task[200002];
long ans = 0;


int main()
{
	freopen("game.in","r",stdin);
	freopen("game.out","w",stdout);
	scanf("%ld",&n);
	
	long max = 0;
	
	for (long i=1;i<n+1;i++)
	{
		scanf("%ld%ld",&task[i].t,&task[i].a);
		max >?= task[i].t;
	}
	for (long i=0;i<max+1;i++)///一开始写成从0到n了。这是个时间的区间
	{
		fa[i] = i; 
	}
	qsort(task+1,n,sizeof(node),&bigger);
	
	for (long i=1;i<n+1;i++) 
	{
		long ffa = getroot(task[i].t);
		if (ffa!=0)
		{
			ans += task[i].a;
			merge(ffa,ffa-1);
			//我觉得这句话最具有跳跃性。将父亲设为上一个点,
			//如果是一个空隙,则指向它,如果是一个连续区间,
			//则意味着于这个区间合并了并指向最左边
		}
	}
	printf("%ld",ans);
}


刘德恩:
#include<iostream>
#include<cstring>
#include<stdio.h>
using namespace std;

struct ee
{
       int timee,w;
}a[500000];
int n,ans;
int pre[500000];

void init()
{
     freopen("game.in","r",stdin);
     freopen("game.out","w",stdout);
}

int cmp(const void *a,const void *b)
{
    struct ee *c=(ee *)a;
    struct ee *d=(ee *)b;
    if(c->w < d->w) return 1;
    return -1;
}

void readdata()
{
     scanf("%d\n",&n);
     int i;
     for(i=1; i<=n; i++)
     scanf("%d%d\n",&a[i].timee,&a[i].w);
     qsort(a+1,n,sizeof(a[1]),cmp);
}

int getpre(int x)
{
    if(x==pre[x]) return pre[x];
    pre[x]=getpre(pre[x]);
    return pre[x];
}

void merge(int x,int y)
{
     int f,ff;
     f=getpre(x);
     ff=getpre(y);
     pre[f]=ff;
}

void work()
{
     int i,t,maxx;
     maxx=0;
     for(i=1; i<=n; i++)
     maxx=maxx>a[i].timee ? maxx:a[i].timee;
     
     for(i=0; i<=maxx; i++) pre[i]=i;
        
     for(i=1; i<=n; i++)
     {
              t=getpre(a[i].timee);
              if(t!=0)
              {
                      ans+=a[i].w;
                      merge(t,t-1);
              }
              if(getpre(maxx)==0) return;
     }
}

void print()
{
     printf("%d\n",ans);
}

int main()
{
    init();
    readdata();
    work();
    print();
    return 0;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值