背景背景
机房里的人都十分认真地在编程,但总有一些人会偷偷玩游戏。。。。。。
问题描述问题描述
问题描述问题描述
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
#include
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)task[heap[l]].a)l++;
if (task[heap[l>>1]].a>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;i0;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
#include
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?= task[i].t;
}
for (long i=0;i
刘德恩:
#include
#include
#include
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;
}