UVALive - 4787 ICPC WF 2010 Tracking Bio-bots【dp】
WF题果然不一样,本来想暴力搜索,数据太大了,数组都开不了。看题解也不太懂,记录一下书上的题解,以后再看:
此题是给出N*M的格子,有些地方是墙,不可走。求所有不能只通过向上或者向右走而走到右上角的格子。
通过观察数据,可以发现房间的规模很大(1<=m,n<=1e6),而墙数很小(0<=w<=1000),所以可以先进行离散化。离散化的时候,采用动态规划进行处理,计算的顺序为由上而下、由右而左。设F[i][j]为格子(i,j)能否走到右上角的标志。状态转移方程:F[i][j]={false,(i,j)格为墙;F[i+1][j]||F[i][j+1],(i,j)格非墙}
最后统计F[i][j]为false且(i,j)非墙的格子数。
1 #include<cstdio> 2 #include<cstring> 3 #include<algorithm> 4 using namespace std; 5 const int MAXN = 2010;//墙数上限 6 struct Twall//墙的结构类型 7 { 8 int x1, x2, y;//行坐标为y,两个端点的列坐标在lx表的指针为x1,x2 9 }; 10 int n, m, w, n1, m1;//房间规模为n*m,墙数为w 11 Twall a[MAXN];//墙序列 12 int lx[MAXN];//存储墙端点的x坐标,表长为n1 13 long long ans;//被困的格子 14 bool f[MAXN];//状态转移方程:当前可从lx[i]走至右上角的标志为f[i] 15 bool g[MAXN];//g[j]=false,表明当前行第j列为被困格 16 17 void init()//输入墙的信息 18 { 19 ans = (long long)n*m;//初始时所有格子未被困 20 n1 = 0;//lx表长初始化 21 for (int i = 1; i <= w; i++) {//每输入堵墙的两个端点坐标 22 scanf("%d%d%d%d", &a[i].x1, &a[i].y, &a[i].x2, &a[i].y); 23 ++a[i].x2; 24 ans -= a[i].x2 - a[i].x1;//被困格子数的初始值为房间中除墙外的格子 25 lx[++n1] = a[i].x1; lx[++n1] = a[i].x2;//存储第i墙的两个端点的x坐标 26 } 27 lx[++n1] = 0; lx[++n1] = n; 28 } 29 30 void discrete(int a[], int &len)//递增排序数组a,并剔去重复元素 31 { 32 sort(a + 1, a + 1 + len); 33 int j = 1; 34 for (int i = 2; i <= len; i++) 35 if (a[j] != a[i]) 36 a[++j] = a[i]; 37 len = j; 38 } 39 40 int bin(int a[], int len, int k)//在a数组中二分查找值为k的元素下标 41 { 42 int l = 1, r = len, m; 43 while (l<=r) 44 { 45 m = (l + r) >> 1; 46 if (a[m] == k) return m; 47 if (a[m] < k) l = m + 1; 48 else r = m - 1; 49 } 50 return -1; 51 } 52 53 bool cmp_Twall(Twall a, Twall b)//返回同行的a堵墙在b堵墙右方,或者a堵墙在b堵墙上方的标志 54 { 55 if (a.y == b.y) return a.x2 > b.x2; 56 else return a.y > b.y; 57 } 58 59 void deal_f(int k)//计算行宽为k时可通过行的格子数,调整被困的格子数 60 { 61 long long t = 0; 62 for (int i = 1; i < n1; i++)//统计可通行的列宽 63 if (f[i]) //lx中的第i个x坐标和i+1个x坐标间为一条通向右上角的通道 64 t += lx[i + 1] - lx[i]; 65 ans -= (long long)t*k; //调整被困的格子数 66 } 67 68 void solve() //计算和输出被困的格子数 69 { 70 discrete(lx, n1); //离散化:递增排序lx数组,并剔去重复元素 71 for (int i = 1; i <= w; i++)//计算每堵墙两端的x坐标在lx中的下标位置 72 { 73 a[i].x1 = bin(lx, n1, a[i].x1); 74 a[i].x2 = bin(lx, n1, a[i].x2); 75 } 76 sort(a + 1, a + 1 + w, cmp_Twall);//按照由右而左、由上而下排序每堵墙 77 int last = m; //从顶行出发 78 memset(f, 0, sizeof(f)); //状态转移方程初始化 79 f[n1 - 1] = true; 80 int st = 1, ed; //从a序列的第1堵墙开始分析 81 while (st<=w) //自上而下分析每堵墙 82 { 83 ed = st; //a[st...ed]中的墙位于同一行,且与a[ed+1]不同行 84 while ((ed < w) && (a[ed + 1].y == a[ed].y)) ++ ed; 85 if (a[st].y != last - 1) { //若当前行与前面分析的墙并非相邻行,则计算状态转移方程 86 for (int i = n1 - 2; i >= 1; i--) 87 f[i] = f[i] || f[i + 1]; 88 deal_f(last - a[st].y - 1); //累计行宽为(last-a[st].y-1)时被困的格子数 89 } 90 memset(g, true, sizeof(g)); 91 for (int i = st; i <= ed; i++) //计算a[st..ed]中墙的并集,即当前行被困的格子 92 for (int j = a[i].x1; j < a[i].x2; j++) 93 g[j] = false; 94 f[n1 - 1] = f[n1 - 1] && g[n1 - 1]; //计算状态转移方程 95 for (int i = n1 - 2; i >= 1; i--) 96 f[i] = g[i] && (f[i] || f[i + 1]); 97 deal_f(1); //将当前行被困的格子数计入ans 98 last = a[st].y; //记下当前的行号和下一个不同行的墙序号 99 st = ed + 1; 100 } 101 for (int i = n1 - 2; i >= 1; i--) //计算状态转移方程 102 f[i] = f[i] || f[i + 1]; 103 deal_f(last); //将最后last行中被困的格子数计入ans 104 printf("%lld\n", ans); //输出被困的格子数 105 } 106 107 int main() 108 { 109 int CASE = 0; //测试编号初始化 110 while (scanf("%d%d%d",&m,&n,&w)==1,!(n==0&&m==0&&w==0)) 111 { 112 init(); //输入墙的信息 113 printf("Case %d: ", ++CASE); //输出测试用例编号 114 solve(); //计算和输出被困的格子数 115 } 116 return 0; 117 }