[NOIP2010] 引水入城 贪心 + 记忆化搜索
题解:
首先我们需要知道一个性质。
如果所有干旱城市都可以被支援到,那么对于任意水库而言,它可以支援的干旱城市必然是连续的
证明如下:
我们假设一个水库支援到了2个不连续的城市,但中间有个城市支援不到。如上图蓝色线所示,那么由于所有干旱城市都可以被支援到,因此必然有另一个水库可以支援到中间那个现在没被支援到的城市。但是我们可以发现,蓝线已经将中间包围, 因此如果有另一个水库支援进来的话,必然会经过蓝色边界。既然可以从蓝色边界引出一条路到达我们之前支援不到的城市,那这个城市显然属于原来那个水库可以支援到的城市,与我们的假设矛盾,因此结论成立。
所以我们在dfs找 l 和 r 的时候,直接记录哪些城市是已经到达的,dfs完后扫一遍,看是否有城市无法到达,如果有,那么说明我们找到的 l 和 r 是不对的,同时说明任务无法完成,于是按题意输出即可
否则就贪心的做线段覆盖。
同时注意dfs需要记忆化(也相当于DP了)
先把区间按照 l 为第一优先级,r为第二优先级排序,然后在l可以触碰到当前位置的时候,优先选择r靠后的。
这个的正确性是显然的。
因此我们就可以得到答案了
代码中我懒得想,所以用暴力处理了一下n = 1的情况。。。。
1 #include<bits/stdc++.h> 2 using namespace std; 3 #define R register int 4 #define AC 510 5 #define inf 10000000 6 #define getchar() *o++ 7 #define D printf("line in %d\n", __LINE__); 8 char READ[5001000], *o = READ; 9 int n, m, tot; 10 int s[AC][AC], a[5] = {0, 0, -1, 1}, b[5] = {-1, 1, 0, 0}; 11 bool z[AC * AC], vis[AC]; 12 13 struct node{ 14 int l, r; 15 }k[AC * AC]; 16 17 inline int read() 18 { 19 int x = 0; char c = getchar(); 20 while(c > '9' || c < '0') c = getchar(); 21 while(c >= '0' && c <= '9') x = x * 10 + c - '0', c = getchar(); 22 return x; 23 } 24 25 void pre() 26 { 27 n = read(), m = read(); 28 for(R i = 1; i <= n; i++) 29 for(R j = 1; j <= m; j++) s[i][j] = read(); 30 } 31 32 inline int get(int x, int y) 33 { 34 return (x - 1) * m + y; 35 } 36 37 node dfs(int x, int y) 38 { 39 int _x, _y, id;node tmp, now; 40 id = get(x, y); 41 if(z[id]) return k[id]; 42 tmp.l = inf, tmp.r = 0; 43 if(x == n) 44 { 45 vis[y] = true; 46 tmp.l = tmp.r = y; 47 } 48 z[id] = true; 49 for(R i = 0; i <= 3; i++) 50 { 51 _x = x + a[i], _y = y + b[i]; 52 if(s[_x][_y] >= s[x][y]) continue; 53 if(_x > 0 && _x <= n && _y > 0 && _y <= m) 54 { 55 now = dfs(_x, _y); 56 if(now.l < tmp.l) tmp.l = now.l; 57 if(now.r > tmp.r) tmp.r = now.r; 58 } 59 } 60 k[id] = tmp; 61 return tmp; 62 } 63 64 inline bool cmp(node a, node b) 65 { 66 if(a.l != b.l) return a.l < b.l; 67 else return a.r < b.r; 68 } 69 70 void work() 71 { 72 int tmp = 0; 73 for(R i = 1; i <= m; i++) if(!vis[i]) ++tmp;//error!!!是m啊 74 if(tmp) 75 { 76 printf("0\n%d\n", tmp); 77 exit(0); 78 } 79 int ans = 0, l = 1;//记录当前使用段数 + 80 sort(k + 1, k + m + 1, cmp); 81 //for(R i = 1; i <= m; i++)//只能用第一行 82 int i = 1; 83 while(1) 84 {//error 之前那样写会死循环的。。。。 85 //printf("%d\n", i); 86 tmp = 0;//记录最远能到哪里 87 while(k[i].l <= l + 1 && i <= m) 88 { 89 if(k[i].r > tmp) tmp = k[i].r; 90 ++i; 91 } 92 l = tmp, ++ans; 93 if(l == m) break; 94 } 95 printf("1\n%d\n", ans); 96 } 97 98 void special()//如果沙漠城市临海 99 { 100 int ans = 0, cnt = 0, tmp = 1, l, r; 101 while(tmp < m && s[1][tmp + 1] < s[1][tmp]) ++tmp; 102 k[++cnt].l = 1, k[cnt].r = tmp; 103 tmp = m; 104 while(tmp > 1 && s[1][tmp] > s[1][tmp - 1]) --tmp; 105 k[++cnt].l = tmp, k[cnt].r = m; 106 for(R i = 2; i < m; i++)//因为只有一行,最多就500个城市,所以暴力即可 107 { 108 if(s[1][i] >= s[1][i - 1] && s[1][i] >= s[1][i + 1]) 109 { 110 l = r = i; 111 while(r < m && s[1][r + 1] < s[1][r]) ++r; 112 while(l > 1 && s[1][l - 1] < s[1][l]) --l; 113 k[++cnt] = (node) {l, r}; 114 } 115 } 116 l = 1; 117 sort(k + 1, k + m + 1, cmp); 118 int i = 1; 119 while(1) 120 {//error 之前那样写会死循环的。。。。 121 //printf("%d\n", i); 122 tmp = 0;//记录最远能到哪里 123 while(k[i].l <= l + 1 && i <= m) 124 { 125 if(k[i].r > tmp) tmp = k[i].r; 126 ++i; 127 } 128 l = tmp, ++ans; 129 if(l == m) break; 130 } 131 printf("1\n%d\n", ans); 132 } 133 134 int main() 135 { 136 freopen("in.in", "r", stdin); 137 fread(READ, 1, 5000000, stdin); 138 pre(); 139 if(n == 1) 140 { 141 special(); 142 exit(0); 143 } 144 for(R i = 1; i <= m; i++) dfs(1, i); 145 work(); 146 //printf("time used ... %lf\n", (double) clock()/CLOCKS_PER_SEC); 147 fclose(stdin); 148 return 0; 149 }