SGi's Blog

A long run.

滑雪解题报告

类归于: Informatics — SGi at 10:30 下午 on 星期二, 十月 21, 2008

题目描述

source:vijos1011,pku1088,SHTSC2002

Michael喜欢滑雪百这并不奇怪,因为滑雪的确很刺激。可是为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底,你不得不再次走上坡或者等待升降机来载你。Michael想知道载一个区域中最长底滑坡。区域由一个二维数组给出。数组的每个数字代表点的高度。下面是一个例子
1  2  3  4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
一个人可以从某个点滑向上下左右相邻四个点之一,当且仅当高度减小。在上面的例子中,一条可滑行的滑坡为24-17-16-1。当然25-24-23-…-3-2-1更长。事实上,这是最长的一条。

输入

输入的第一行表示区域的行数R和列数C(1 <= R,C <= 100)。下面是R行,每行有C个整数,代表高度h,0<=h<=10000。

输出

输出最长区域的长度。

分析

这题直接用DFS裸搜肯定TLE,明显可以用DP来解决。
用opt[i,j]记录从点node[i,j]出发的最短路径
状态转移方程很容易写出来
opt[i,j]=max{opt[i+1,j],opt[i-1,j],opt[i,j+1],opt[i,j-1] } +1
但是这道题的状态并不容易表示,所以计算opt的时候需要类似DFS的方法,这就是DP的本质——记忆化搜索。

下面是过Vijos 1011的Pascal代码:

?View Code PASCAL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
const
  dx : array [1..4] of longint = (-1,0,1,0);
  dy : array [1..4] of longint = (0,1,0,-1);
 
var
  r,c,max,i,j,value : longint;
  node,opt : array[0..1000,0..1000] of longint;
 
function check(i,j : longint):boolean;
begin
  exit((i &gt;= 1) and (i &lt;= r) and (j &gt;= 1) and (j&lt;=c));
end;
 
function dp(i,j : longint):longint;
var
  k : longint;
begin
  if opt[i,j] &gt; 0 then exit(opt[i,j]);
  for k := 1 to 4 do
    if check(i+dx[k],j+dy[k]) then
      if (node[i+dx[k],j+dy[k]] &lt; node[i,j]) then
        if opt[i,j] &lt; dp(i+dx[k],j+dy[k]) + 1 then
          opt[i,j] := dp(i+dx[k],j+dy[k]) + 1;
  exit(opt[i,j]);
end;
 
begin
  max := -maxlongint;
  fillchar(opt,sizeof(opt),0);
  readln(r,c);
  for i := 1 to r do begin
    for j := 1 to c do
      read(node[i,j]);
    readln;
  end;
 
  for i := 1 to r do
    for j := 1 to c do begin
      value := dp(i,j);
      if value &gt; max then max := value;
    end;
  writeln(max+1);
end.

最小与最大解题报告

类归于: Informatics — SGi at 10:48 下午 on 星期三, 十月 8, 2008

问题描述

做过了乘积最大这道题,相信这道题也难不倒你。

已知一个数串,可以在适当的位置加入乘号(设加了k个,当然也可不加,即分成k+1个部分),设这k+1个部分的乘积(如果k=0,则乘积即为原数串的值)对m 的余数(即mod m)为x;

现求x能达到的最小值及该情况下k的最小值,以及x能达到的最大值及该情况下的k的最小值(可以存在x的最小值与最大值相同的情况)。

输入格式

第一行为数串,长度为n 满足2<=n<=1000,且数串中不存在0;

第二行为m,满足2<=m<=50。

输出格式

四个数,分别为x的最小值 和 该情况下的k,以及x的最大值和 该情况下的k,中间用空格隔开。

样例输入

4421

22

样例输出

0 1 21 0

分析

 

首先,需要知道一个数学定理:(a*b) mod m = ((a mod m) * (b mod m)) mod m。

用f[i,j]表示前i个数字组成的数要达到余数为j所需要加的最少乘号个数,b[i,j]表示第i个数到第j个数成的数 mod m 的值,其中len为数字串长度。

那么,f[k,s]=min{f[i,j]+1|k = i+1 to len,i = 1 to len,j = 0 to m-1} 其中 s=b[i+1,k] * j mod m。

注意:因为输入的数字串长度可能会大于255,如果用字符串操作的话要用ansistring。

解答程序Pascal语言:

?View Code PASCAL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var
  a:array[1..1000]of longint;
  b:array[0..1000,0..1000]of longint;
  f:array[1..1000,0..49]of longint;
  i,j,k,n,l,s,m,t,min,max:longint;
  c:char;
begin
  while not eoln do begin
    inc(l);
    read(c);
    a[l]:=ord(c)-48;
  end;
  readln;
  readln(m);
  fillword(f,sizeof(f) div 2,1000);
 
  // minimized number of add symbols from i to j mod m
  t:=0;
  for i:=1 to l do begin
    t:=(t*10+a[i])mod m;
    f[i,t]:=0;
  end;
 
  //the module value of the number consists of i-th to j-th digits
  //len : length of the number
  //f[k,s]=min{f[i,j]+1|k = i+1 -&gt; len,i = 1 -&gt; len,j = 0 -&gt; m-1}
  fillchar(b,sizeof(b),0);
  for i:=1 to l do
    for j:=i to l do
      b[i,j]:=(b[i,j-1]*10+a[j]) mod m;
 
  for i:=1 to l-1 do
    for j:=0 to m-1 do
      if f[i,j]&lt;1000 then begin
        t:=0;
        for k:=i+1 to l do begin
          s:=b[i+1,k]*j mod m;
          if f[i,j]+1&lt;1000 then begin
      if i &gt; max then max := i;
      if i &lt; min then min := i;
    end;
  writeln(min,' ',f[l,min],' ',max,' ',f[l,max]);
end.

土地购买(acquire)

类归于: Informatics — SGi at 2:58 下午 on 星期天, 九月 7, 2008

农夫John准备扩大他的农场,他正在考虑N (1 <= N <= 50,000) 块长方形的土地. 每块土
地的长宽满足(1 <= 宽 <= 1,000,000; 1 <= 长 <= 1,000,000).
每块土地的价格是它的面积,但FJ可以同时购买多快土地. 这些土地的价格是它们最大的长乘以
它们最大的宽, 但是土地的长宽不能交换. 如果FJ买一块3×5的地和一块5×3的地,则他需要
付5×5=25.
FJ希望买下所有的土地,但是他发现分组来买这些土地可以节省经费. 他需要你帮助他找到最小
的经费.

题名: acquire

输入格式:
* 第1行: 一个数: N
* 第2..N+1行: 第i+1行包含两个数,分别为第i块土地的长和宽

样例输入 (acquire.in):
4
100 1
15 15
20 5
1 100

输入解释:

共有4块土地.

输出格式:
* 第一行: 最小的可行费用.

样例输出 (acquire.out):
500

输出解释:
FJ分3组买这些土地: 第一组:100×1, 第二组1×100, 第三组20×5 和 15×15 plot. 每组
的价格分别为100,100,300, 总共500.

分析:

今天花了点时间把这道题目研究了一下,我觉得这道题目还是有点难度的,O(n^2)的算法很好想也很容易实现,但是50000的数据非得让我想出O(nlogn)的算法。

首先,先用一个显而易见的优化,对于任意一个(xi, yi), 如果存在(xj, yj)满足xi<=xj且yi<=yj, 那么(xi, yi)是完全没有意义的,可以删去。
然后,将所有的地块按x升序排列,因为所有满足第一个优化的地块已经不存在了,所以此时y必然按照降序排列。
容易做出一个O(n^2)的DP,f[i]表示买前i个需要的最小代价, 则有f[i] = min{f[j-1] + yj * xi} (j<=i)

这时候观察状态转移,可以发现这个状态转移是与两个基于j的变量线性相关的。所以,问题就转化为求给出点集所有任意两点连线在y轴截距的最小值,即最小费用。这个问题可以用经典的凸包算法解决,时间复杂度为O(n),加上排序复杂度O(nlogn),总的时间复杂度为O(nlogn)。
这个方法可以用接下来我说的方法等价实现:

令j<k, 决策j比决策k优的条件是:
f[j-1] + yj * xi < f[k-1] +yk * xi => (f[k - 1] – f[j - 1]) / (yj – yk) > xi.
令g(j,k) = (f[k-1] – f[j-1]) / (yj – yk) > xi,那个只要用队列维护决策p1 < p2 < p3 .. <pk, 使得g(p1, p2) < g(p2, p3) < … < g(pk-1, pk)即可。

测试数据可以在这里下载。

PS:这个题做得很囧,交到USACO上去AC了,在cena上WA了5个点,世界真是太奇妙了,希望NOIP的时候不要发生这种事情..= =

下面是这题C语言的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#define MAX 50005
 
struct rect { long long w, h; };
 
struct line {
  long long m, b;
  line(long long x=0, long long y=0) { m=x; b=y; }
};
 
int N, lstart, lend;
rect plots[MAX];
long long best[MAX];
line lines[MAX];
 
int cmp(const void *a, const void *b) {
  rect p = *(rect *)a, q = *(rect *)b;
  if (p.w &lt; q.w) return -1;
  if (p.w &gt; q.w) return 1;
  if (p.h &lt; q.h) return -1;
  return 1;
}
 
bool bad(line x, line y, line z) {
  // lines x and y intersect at x-coordinate p1
  // lines y and z intersect at x-coordinate p2
  // bad &lt;--&gt; p1 &gt;= p2
  return ((y.m-z.m) * (y.b-x.b) &gt;= (x.m-y.m) * (z.b-y.b));
}
 
int main() {
  FILE *in = fopen("acquire.in", "r");
  fscanf(in, "%d", &amp;N);
  for (int i = 0; i &lt; N; i++) {
    fscanf(in, "%lld%lld", &amp;plots[i].w, &amp;plots[i].h);
  }
  fclose(in);
 
  qsort(plots, N, sizeof(plots[0]), cmp);
  int tN = 0;
  for (int i = 0; i &lt; N; i++) {
    plots[tN] = plots[i];
    while (tN &gt; 0 &amp;&amp; 
	   plots[tN].w &gt;= plots[tN-1].w &amp;&amp; 
	   plots[tN].h &gt;= plots[tN-1].h) {
      plots[tN-1] = plots[tN];
      tN--;
    }
    tN++;
  }
  N = tN;
 
  best[0] = 0;
  lines[0] = line(plots[0].h, best[0]);
  lstart = 0;
  lend = 1;
  for (int i = 0; i &lt; N; i++) {
    // compute best[i+1]
    for (int j = lstart; j &lt; lend; j++) {
      long long tmp = lines[j].m * plots[i].w + lines[j].b;
      if (j == lstart) {
	best[i+1] = tmp;
      }
      else if (tmp &lt; best[i+1]) {
	best[i+1] = tmp;
	lstart = j;
      }
      else {
	break;
      }
    }
 
    if (i &lt; N-1) {
      // compute new line
      lines[lend] = line(plots[i+1].h, best[i+1]);
      lend++;
 
      // erase irrelevant lines
      while (lend-lstart &gt;= 3 &amp;&amp; 
	     bad(lines[lend-3], lines[lend-2], lines[lend-1])) {
	lines[lend-2] = lines[lend-1];
	lend--;
      }
    }
  }
 
  FILE *out = fopen("acquire.out", "w");
  fprintf(out, "%lld\n", best[N]);
  fclose(out);
 
  return 0;
}