题解
作业调度方案
https://vijos.org/p/1314
难点提要:1 题意。 2 无。
题意:
好好看题就可理解。
n个工件 m个机器 每个工件有m个工序,每个工件的每个工序需要不同的机器来做(也就是所谓的工序的机器号)
对于安排顺序。只要每个工件的工序顺序对了即可。
也就是说一号工件的工序1完成后才能完成一号工件的工序2
2 3
当一个操作插入到某台机器的某个空档时(机器上最后的尚未安排操作的部分也可以看作一个空档),可以靠前插入,也可以靠后或居中插入。为了使问题简单一些,我们约定:在保证约束条件(1)(2)的条件下,尽量靠前插入。并且,我们还约定,如果有多个空档可以插入,就在保证约束条件(1)(2)的条件下,插入到最前面的一个空档。于是,在这些约定下,上例中的方案一是正确的,而方案二是不正确的。
做这道题关键在于理解题意。这道题让我想起了流水作业调度的Johnson算法,虽然与这道题无关。
提示几点
- 每个工件的每个工序对应着不同的机器。
- 一个工件必须按照加工顺序加工。
- 把每个机器看成一个时间轴,每个时间对应着加工一个工件,或者为空闲状态。
- 题中的算法是给定的贪心策略,不需要构造,只要模拟。
由于数据很小,把每个机器的时间轴用布尔数组表示,true为该时间有工件在加工,false为空闲。
按照给定的顺序,一件一件得往时间轴上插入,每个工件插入的位置必须在前面的工序都完成以后的时间断插入。每次插入扫描一遍时间轴数组,找到最前面一个。
其实题中所给的图已经一目了然了。理解题意以后,会发现这是NOIP2006最简单的题。
滑雪场评级
http://www.cnblogs.com/jznoi/p/4071616.html
难点提要:1 思想(最小生成树的一个性质) 2 并查集的维护
1 思想:
将所有点标号,并将高度差绝对值作为边长。
先来想这样一个问题:
要使得一棵t个节点的树,max{边权} 最小 即使得任意两点间路径上的max{边权}最小
最小生成树中 任意两点间路径上的max{边权}是最小是a的。为什么?
反证 假设有一条更小的边b 加进生成树 必形成一环 在做最小生成树的时候 是按边权从小到大加入
肯定先加入 b再加a的时候 判环 a不会加入 与假设矛盾。
回到原题
按边权从小到大加入 如果一个连通块有t个数了 那么 这个连通块里所有的起点的D都为新加进来的边权
如果 某个两个连通块要合并
其中一个连通块已经>=t了 那么 这个连通块中都不用再走了 (不可能被更新)
如果另一个<t 那么 走哇 否则 也不走哇。
o(n*m*2条边+dfs的时间复杂度(每个点只走一次,n*m))
那么
function find(x:int64):int64;
begin
if x=fa[x] then exit(x);
fa[x]:=find(fa[x]);
exit(fa[x]);
end;
function su(x,y:longint):int64;
begin
exit((x-1)*m+y);
end;
procedure build(x,y,x1,y1:int64);
begin
inc(tot);
a[tot].x:=su(x,y);
a[tot].y:=su(x1,y1);
a[tot].w:=abs(b[x,y]-b[x1,y1]);
end;
procedure dfs(x,w:int64);
var i,y:longint;
begin
flag[x]:=true;
{dfs走的图必定是未去过的新图,所以要用flag}
if f[x] then begin inc(ans,w); f[x]:=false; end;
for i:=1 to h[x,0] do
begin
y:=h[x,i];
if not flag[y] then dfs(y,w);
end;
end;
begin
readln(n,m,t);
for i:=1 to n do
begin
for j:=1 to m do read(b[i,j]);
readln;
end;
for i:=1 to n do
begin
for j:=1 to m do
begin
read(x);
if x=1 then f[su(i,j)]:=true;
end;
readln;
end;
for i:=1 to n do
for j:=1 to m do
begin
if i<n then build(i,j,i+1,j);
if j<m then build(i,j,i,j+1);{标号建原图}
end;
for i:=1 to n*m do begin size[i]:=1; fa[i]:=i; end;
sort(1,tot);
i:=0; num:=0;
while (i<tot) and (num<n*m-1) do
begin
inc(i);
set1:=find(a[i].x); x:=a[i].x; y:=a[i].y;
set2:=find(a[i].y);
w:=a[i].w;
if set1=set2 then continue;
inc(num);
if (size[set1]<t) and (size[set1]+size[set2]>=t) then dfs(set1,w);{只有未赋过值的连通块才要赋值}
if (size[set2]<t) and (size[set1]+size[set2]>=t) then dfs(set2,w);
if size[set1]<size[set2] then begin k:=set1; set1:=set2; set2:=k; end;
//安置合并,使得size更大的连通块深度跟小
{一个size为500 另一个 2 如果 2的father作为500的father ,搜500连通块里数的father要多搜500次哦}
size[set1]:=size[set1]+size[set2];
fa[set2]:=set1;
inc(h[x,0]); h[x,h[x,0]]:=y;{新图,其实如果连通块已经在以前的循环中赋过值了,不必去走,也不要赋边了呀}
inc(h[y,0]); h[y,h[y,0]]:=x;[-1..n*m,-1..10]最多十条边呀
end;
writeln(ans);
end.
队伍平衡
http://www.cnblogs.com/jznoi/p/4072697.html
搜索也有好换之分 尽量使层数减小 即 次数减少 底数增大 更优
枚举 人在哪个组 4的12次
枚举 哪个组第几个位置是哪个人 12的12次
滑雪录像
http://www.cnblogs.com/jznoi/p/4072697.html
贪心 按右边界排序 加入两个队列
加入原则:
假如两个队列都能加,加原队列右边界更远的
-----| ---|
---|
还是
-----|
---| ---|
对于这种情况无影响
-----| ---|
---| -----|
-----| -----|
---| ---|
但是
-----| ---|
---| --------|
-----|--------|(重合了)
---| ---|
按右边界加入的证明
先看一条队列 两条队列也是一样
贪心即为
---- -----1 ----
---- ------2 ---
应该选谁,如果1 或2 与之前边界重合 那当然选 不重合的
如果两条不重合 那么 选1 之后选进来的数量只增不减
因为需要尽量多的独立的线段,所以每个线段都尽可能的小,
对于同一右端点,左端点越大,线段长度越小。
那么为什么要对右端点进行排序呢?
如果左端点进行排序,那么右端点是多少并不知道,那么每一条线段都不能对之前所有的线段进行一个总结,那么这就明显
不满足贪心的最优结构了。
滑雪场建设
http://www.cnblogs.com/jznoi/p/4072697.html
难点:思路
每次找到一个最大的正方形 最大的正方形必定是最后一步覆盖的 而前一步 这个正方形中可以为任意数,将它赋为任意数(-1)重复步骤
而边长更大的正方形必能被边长更小的正方形完全覆盖。反之 不成立。
ans:=min{ 最大正方形边长} bytheway用动归或二分求最大的正方形。
还有一点 当整个长方形全相同或最大正方形=1时退出
旅行家的预算
https://vijos.org/p/1253
n=0特判
如果 存在两油站之间距离超过加满一次油所需时间 -1
走 找到在当前油站k加满一次油 能走到 油价p[i]最便宜的油站i
if p[i]>p[k] 那么在当前油站加满油再走 else 加油,走到那个油站刚好停下的油
守望者的逃离
https://vijos.org/p/1431
一个s记录只用魔法能走到的距离
另一个smax记录用最远能走到的距离
因为守望者跑步、闪烁或休息活动均以秒(s)为单位,且每次活动的持续时间为整数秒。
如果能用魔法就必定用,用一次1s 走路一次也是一秒。 如果无法恢复 就只能用走。。
代码还是有点巧妙
d:=0; dmax:=0;
for i:=1 to t do
begin if m>=10 then begin dec(m,10); inc(d,60); end else m:=m+4; if dmax+17>d then dmax:=dmax+17 else dmax:=d; if dmax>=s then begin writeln('Yes'); writeln(i); close(input); close(output); exit; end; end;
积木大赛
noip2013