TopCoder SRM288 DIV1 Report
match date:Wednesday, February 8, 2006
report date:Friday, February 10, 2006
Preface:
也许是第一次成绩太好了,第二次被分到了div 1 组,才发觉自己的水平真得不济。只能有时间coding两道题目,而第二题因为没把握所以没提交。第一题也没通过system test。在chanllenge阶段试了3道题目,结果无一成功,真是落泊啊!
总体情况是第一题有阴险数据,所以成功率不高,第二题其实用简单dp就行了,第三题是难题,挑战者也偏少。rating落到了1001,下次还是div 2 啊。马上要开学,机会不多了,但我会抽空来参加的。
Problem 250 - FindTriangle:
是一道几何题,推简单的公式没有花掉我多少时间,但是精度误差一直是困难所在。在比赛中用了BigDecimal,但最后还是没能通过system test。
首先,计算三角形面积的公式有比较著名的"Heron's formula":
A = 1/4 * Sqrt((a + b + c) * (a + b - c) * (a + c - b) * (b + c - a))
但在这题中最好不要用,计算距离a b c需要开方,而最后还要开方一次,这样精度就不够好了。
注意到计算Area还有一个计算公式 A= ½ |u × v| ,u和v是三角形两条边的向量,求u和v只需要加减就可以求得。而叉乘只要根据定义通过加和乘就可以求得。只有在最后取模用到一次开方。因此,精度是可以保证的。
u.x= p1.x-p2.x u.y=p1.y-p2.y u.z=p1.z-p2.z
v.x= p2.x-p3.x v.y=p2.y-p3.y v.z=p2.z-p3.z
|u × v|^2 = (u.y * v.z - u.z * v.y) ^2+ (u.x * v.z - u.z * v.x)^2 +(u.x * v.y - u.y * v.x)^2
找到两到ACM题可以训练: PKU 2079(光会算三角形面积不够,数据量很大会超时),SHANTOU OJ 1061(二维的,简单)
Problem 450- CannonBalls:
哎,看来是经验不足啊,最简单的DP错看成贪心,等醒过来就来不及了。(还要继续学习学习再学习)
要求数列,直接循环累加就行了。关键是如何将参数n划分最少的块。
DP方程如下:
f (n) = min(
k=(1,n) && k is a valid divider f (n-k)+1 ) f (0)=0
然后直接取f (n) 便是结果了。
在赛后分析中,给出了用递归的解决方法(其实满象DFS的,具体是从原初贪心的方向开始逐渐比较找出最优解,其中使用一个v变量剪支),代码上比DP难写得多。
在学习完赛后分析后,我把我的递归程序给出来(去掉了赛后分析的程序一些饶口地方):
// n 是 待划分数, st 当前的划分块的下表 , v是目前最优的可能解(剪支用)
public int dfs(int n, int st, int v) {
if (n == 0) return 0; // 递归的终止,解集树叶子处
if (st == 1) return n; // 无法继续分析,返回最糟解n
int best = n; // 暂时最优解为n
while (n < t[st] && st > 2) // 找小于n的最大的一个划分块
st--;
int div = n / t[st]; // div是最小可能划分数
if (div >= v) // 若当前最小的可能划分数都大于
return n; // 已最优解,那么剪掉
for (int i = div; i >= 0; i--) { // 从大到小的递归类似不断贪心求解
v = Math.min(best, v); // 与前次循环的解比较,更新最优解
best = Math.min(best, i + dfs(n - i * t[st], st - 1, v - i)); // 递归
if (best == 1)
return 1; // best为1 已经是最小的解了,返回
}
return best;
}
Problem 1000- CountryWar:
到底是div 1的最后一题呀,在题目分类中写的是DP+GraphTheory。分析程序中用mask bit来处理两节点相邻的表示,用DP预先计算所有战争的胜率,最后搜索找最优解。(-_-o)
接着我对几个要点说明一下,如果觉得不够的话,可以去看看赛后分析(写得满清晰):
搜索前的准备工作是先DP出所有战争的胜率,prob[attacker][defender][remain] 记录:
几个变量说明 a攻击方值 b防御方值 r 剩下的值(剩余士兵数)
win(a,b) 返回胜率(就是a2/(a2+a*b+b2))
battle[a][b]数组 记录特定情况下a与b战斗的胜率,主要DP在这吧。
注意!battle值对每次求prob都在变的,所以每求一次prob都要重求一遍battle。
ba(i, j-1) += ba(i, j) * win (i, j) ba(i,j)初始0 ba(a,b)=1
ba(i-1, j) += ba(i, j) * (1-win (i, j)) i=[0,a] j=[0,b]
prob[a][b][r] = battle (r , 0) 最后r循环把对于a b打仗所有可能值赋给prob
然后,对输入进行解析,这里用了mask bit来表示两结点的相邻以及敌对关系:
owned 表示拥有的地,比如 0000010010010000,表示占有4、7、10三块地,初始时只有一块地。
enemyMask 表示敌对的编号,那0000010010010000 表示 4 7 10 三个地上的是敌人。
adjacency[i] 表示与i地相邻的土地,0000010010010000 表示4 7 10三地与i地相邻。
分析程序中有个dp数组(很大),其实就是用来保存搜索中求的解的,唯一的作用是判断dfs (owned,armies)(搜索函数)的owned 和armies是否已经求过了,并返回已有的值。本来我想试着用别的小一点的东西替代它,结果没能找到,所以还是用这个dp数组来记录吧:
// owned表示当前结点处已占有的土地, armies是剩余的士兵
public double dfs( int owned , int armies ) {
if (dp[owned][armies] >= 0) // 判断是否已经计算过
return dp[owned][armies];
if (((65535 - owned) & enemyMask) == 0) // 没有可攻击的enemy
return dp[owned][armies] = 1.0;
double best = 0; // 最优解初始化
for (int i = 0; i < tCount; i++) // 对每个土地遍历一遍
// (owned & (1 << i)) == 0 不拥有该土地
// (adjacency[i] & owned) > 0 与该土地邻接
if ((owned & (1 << i)) == 0 && (adjacency[i] & owned) > 0) {
double d = 0;
for (int j = 1; j <= armies; j++){ // 累加概率
// 不同士兵数攻占该地的概率
d += prob[armies][enemy[i]][j] * dfs(owned | (1 << i), j);
}
best = Math.max(best, d); // 求最佳解
}
return dp[owned][armies] = best;
}
Links:
My statistic:
http://www.topcoder.com/stat?c=coder_room_stats&cr=20862220&rd=9809&rm=247580
SRM 288 - Problem Set & Analysis :
http://www.topcoder.com/tc?module=Static&d1=match_editorials&d2=srm288
PKU 2079:
http://acm.pku.edu.cn/JudgeOnline/showproblem?problem_id=2079
ShanTou Online Judge 1061:
http://acm.stu.edu.cn/cgi-bin/problem_cn?id=1061
Triangle Area :
http://mathworld.wolfram.com/TriangleArea.html
CrossProduct :
http://mathworld.wolfram.com/CrossProduct.html