Showing posts with label contest. Show all posts
Showing posts with label contest. Show all posts

Saturday, June 02, 2007

百度之星2007初赛

HOHO,恭喜恭喜,上大的四人都过初赛啦! 我,梁老大,沈还有Jackie Yu。我和Jackie两天都做了,沈做了第一天,梁做了第二天。最后沈和梁晋级,我靠第一天晋级,Jackie靠第二天晋级。我第一天拿了30分(题1全过,题2过一半),比期望的低哦,最后的那道SQL本来希望能过两个CASE的,结果全挂了,555,沈也是用暴力解的,但他过了两个CASE,估计是我哪里细节出了问题,导致全错。第二天很郁闷,中午做题果然没晚上精神爽,直打瞌睡。第二天只做了两道(1,4),最后的结果是第1题只对了一个case, -_-b,超级郁闷,后来发现程序里少了两句判断,555,不然第二天也能晋级啦,现在只有9分,不过还好有第一天保底(只是对不住陈胖子了呀)。

题目可以见某网友的帖子,感谢他保留了百度试题:
http://www.ninstein.com/blog/article.asp?id=199
http://www.ninstein.com/blog/article.asp?id=202

其他的题目就不说了,百度有点评(第二天要准备trie的模板呀),我个人对第一天的第四题SQL的SELECT语句比较感兴趣,所以重新做了一遍:

基本上是做了两点的优化(较暴力法),一个是结构上加了索引,二是计算满足条件的几个集合的交集(在结构的基础上优化的求交算法)。

为了说明结构我画了图,还不错吧 ^_^


简单说明一下,该结构完全是为了针对题目而做的,我考虑下,为了实现Delete, Update还需要修正一下结构,而且结构改动后后面的Select算法也要调整。另外,Select是单向的,即只能由条件找出记录号,不能由记录号找出记录的所有信息,要实现的话应该在Record上再加一些变量。不管了,只是针对AND逻辑的嘛。

Table[i][j]的位置不是存放第 i 记录的第 j 字段值,而是存放一个指针,指向下一个与Table[i][j]值相同的记录号,也就是链表结构。索引表存放的才是字段值,对应一个startId是在Table中的第一个拥有该字段值的记录号,即链表的head。

假设,按图中sample有5条记录,0 1 3的c3字段值为a,2 4的c3为b。那么Select c3为a的所有记录,就是在IndexTable中找c3的字段索引,然后找对应的a的startid,得0。然后回表中开始找出所有记录,Table[0][c3]=1, Table[1][c3]=3, Table[3][c3]=-1。-1为终止,于是有 0 1 3 三条记录。

由上可以求出,满足某字段值的一个集合。第二步是求满足多个字段值的一个交集。由于结构本身提供了一个很有利的条件,即若Table[i][j]的字段值等于Table[i][k]且两者均不为-1,则Table[i][j]<table[i][k]当且仅当 j<k,算法描述如下:

(假设满足求n个condition的记录数count=0)
if condition is empty
count=recordNum, exit

准备一个索引数组tb,tb[i]代表 i 条件下的当前记录号。tb初始全-2。
foreach condition as ci
int tmp= IndexTable中ci条件的startid值,无法满足ci,则tmp=-1
if tb[ci] 已存在,即 !=-2
if tb[ci]!= tmp
count=0, exit
else
next condition
else
tb[ci]=tmp

依某算法(算法很多,自定吧)取字段c为主键,可以理解为取某个condition为主条件

while tb[c]!=-1
foreach condition as ci
if ci==c
continue
else
while tb[ci]<tb[c] && tb[ci]>=0
tb[ci] = Table[tb[ci]][ci] // next record
if tb[ci]>tb[c] || tb[ci]==-1
break;
if all condition matched
count++
else
tb[c]=Table[tb[c]][c]

count is result

复杂度分析: 空间复杂度上是不会亏的啦,字段值没有重复保存过,索引用的都是数字。设记录数n,字段数c,构造DB时的复杂度O(nc)。设某个查询的条件是q条,查询的复杂度min(q,c)+sum(count(qi)) min(q,c)是条件数和字段数取小者,qi表示第i个条件,count是满足i条件的记录数(可以缓存一下的),sum()为求和函数。

其他建议:取主键的算法很多(甚至可以不取,直接假设第一个条件对应的键为主键),可以单纯地用缓存过的满足条件的记录数来决定记录数最少的那个为主键。但对于复杂度不会有提高,因为如果按我上述的算法,复杂度是固定的,主键不影响复杂度。

存在不同主键的情况下会影响复杂度的算法,假设已经求到Table[i]是满足所有条件的记录,那么求下max(Table[i][cj]) cj为每个条件对应的字段,只有最大值对应的记录才有可能为下一条满足所有条件的记录,即使他不是满足所需要的记录,那么只要循着该字段一定能找到真正的下一条满足所有条件的记录。这种跳跃式的算法,可以有效减小复杂度。

缺点:结构只是针对select and做了优化,并没考虑太多,可能会不方便其他操作。优化中将记录标号了,虽然现在大部分表都喜欢加个id,但数据库本意是记录无序的。如我图中所画,DB有许多Table,而只有一个IndexTable,我的想法是一个IndexTable可以管理所有有关联的Table的所有字段,以优化做select and操作。但还没设计完,现在的IndexTable显然是不行的,以后有机会再说吧。

偶的代码(C++),大致实现了上面的结构和算法,测试数据使用MySQL 的sakila sample 的payment表,修正后的数据以及代码点此下载 (放在上大ACM论坛,-_-b荒废好久的论坛啊,只能用来摆摆文件了):



#include <iostream>
#include <string>
#include <vector>
#include <map>
#include <utility>
#include <sstream>
#include <algorithm>
using namespace std;

// DB structs
// first is startId, second is len(len may be tempory used for other target)
typedef pair<int,int> Index;
typedef map<string,Index> FieldIndex;
typedef vector<FieldIndex> IndexTable;
typedef map<string,int> ColumnName;
typedef vector<int> Record;

struct DB{

// a table (since only one table provided)
string name;
vector<Record> records;
int recordNum,columnNum;
ColumnName nameMap;

IndexTable it;

DB(int n,int c):recordNum(n),columnNum(c){
records.resize(n);
it.resize(c);
for(int i=0;i<n;++i){
records[i].resize(c);
}
}
};

// Query structs
typedef pair<string,string> Expression;
typedef vector<Expression> Query;

// called while constructing the DB and IndexTable
int addValue(FieldIndex& index, string& value,int curIndex){
map<string,Index>::iterator iter=index.find(value);
int lastIndex=-1;

if(iter==index.end()){
index[value]=Index(curIndex,curIndex);
}else{
lastIndex=index[value].second;
index[value].second=curIndex;
}

return lastIndex;
}

void initDB(DB& db){
string buf;
// parse db name
cin>>db.name;
getline(cin,buf);

// parse column name
getline(cin,buf);
stringstream ss(buf);
string name;
for(int i=0;i<db.columnNum;++i){
ss>>name;
db.nameMap[name]=i;
}

// parse db records
for(int i=0;i<db.recordNum;++i){
getline(cin,buf);
stringstream ss(buf);

for(int j=0;j<db.columnNum;++j){
string value;
ss>>value;
db.records[i][j]=-1;
int lastId=addValue(db.it[j],value,i);
if(lastId>=0){
db.records[lastId][j]=i;
}
}
}
}

bool isExpression(string& str){
int len=str.length();
for(int i=0;i<len;++i){
if(str[i]=='=')return true;
}
return false;
}

bool validChar(char c){
return (c>='0' && c<='9') || (c>='a' && c<='z') || (c>='A' && c<='Z');
}

void parseExpression(string& str, Expression& exp){
int i=0,st=0,len=0;

while(!validChar(str[i]))++i;
st=i;
while(validChar(str[i]))++i;
len=i-st;

exp.first=str.substr(st,len);

while(!validChar(str[i]))++i;
st=i;
while(validChar(str[i]))++i;
len=i-st;

exp.second=str.substr(st,len);
}

int doQuery(DB& db,string& q){
int cnt=0;
stringstream ss(q);
string buf;
Query query;

while(ss>>buf){
if(isExpression(buf)){
Expression exp;
parseExpression(buf,exp);
query.push_back(exp);
}
}

if(query.empty())return db.recordNum;

int* tb=new int[db.columnNum];
fill(tb,tb+db.columnNum,-2);

for(Query::iterator iter=query.begin(); iter!=query.end(); ++iter){
int col=db.nameMap[iter->first];
map<string,Index>::iterator mapIter=db.it[col].find(iter->second);
if(mapIter!=db.it[col].end()){
int st=(mapIter->second).first;
if(tb[col]>=0 && st!=tb[col]){
return 0;
}else if(tb[col]<0){
tb[col]=st;
}
}else tb[col]=-1;
}

int lastValue=-1;
while(true){
int i=0,curValue=-1;
for(;i<db.columnNum;++i){
if(tb[i]==-2)continue;
if(lastValue>=0 && tb[i]==lastValue){
tb[i]=db.records[tb[i]][i];
}
if(tb[i]==-1)break;

if(curValue==-1){
curValue=tb[i];
}else{
while(tb[i]<curValue && tb[i]>=0){
tb[i]=db.records[tb[i]][i];
}

if(tb[i]>curValue || tb[i]<0){
break;
}
}
}
lastValue=curValue;
if(tb[i]==-1 || curValue==-1)break;
if(i==db.columnNum){
++cnt;
}
}

delete[] tb;
return cnt;
}

// main entry
int main(){
int c,n,q;
cin>>c>>n>>q;
DB db(n,c);
initDB(db);

string line;
while(q--){
getline(cin,line);
cout<<doQuery(db,line)<<endl;
}
}


Tuesday, May 29, 2007

07年5月29日 在复旦大学的topcoder比赛

先是匹萨,棒约翰的,还不错,我和肖各吃了两块,就饱了,其实是不太好意思吃多 ^_^

然后是比赛,感觉脑子的转速要比在家里做快得多,时间也貌似比一般的一个半小时长得多(因为单位时间的思考能力强了,所以感觉时间就多了)。算是三校联赛吧,被复旦踩是必然的,目标是踩东华。最后338.31,是room2的第7,divsion的第13,还是被两个东华的踩了。

很久没写tc报告了,因为很久不关心算法。但今天还是满有意思的,写一下,纪念一下:

总得来说DIV2的题目就是比较简单的呀。

problem 250:
一个Set包括0-9个数字,其中6和9可以互用,隐含的意思就是可以表示 66 或 99 或 69。
然后给个门牌号,问至少买多少Set能够摆出这个门牌号。

算法很简单,统计一下门牌号中出现过的数字个数。因为6和9可以互用,所以把6的个数和9的个数平均一下,有两个写法:


// method one, after counting
count[6]=count[9]=(count[6]+count[9]+1)/2; // forget add one will be chanllenged

// method two, in counting iteration
if(count[6]<count[9]) count[6]++;
else count[9]++;


看到两个人用第一种写法忘记+1,cha之~,HOHO,赚100分。

problem 500:
有金G银S铜B三种钱币,去银行转换的规则如下:
11 S -> 1 G
11 B -> 1 S
1 G -> 9 S
1 S -> 9 B
问从G1 S1 B1换到 G2 S2 B2的最少交换次数,或-1表示不可能

与其说是贪心,不如说是逻辑题,只要依存简单的逻辑,就能既保证代码清晰,又可保证准确率:
step 1. 如果B不够,只能用S换B
step 2. 如果G不够,只能用S换G
step 3. B和G都够了,若S不够,先用G换S,再用B换S。因为G换S一次可以满足的S多,所以先G换S。

5555,前面的代码都是好好的,可惜最后在B换S的地方,忘记减去B,加上S了。。。好粗心啊 >.<
一开始沈发现了,但他给的cha数据没能把我cha掉~,洋洋得意地笑沈时,忽然某人把我cha了~ 瞬间郁闷

problem 1000:
比赛的时候想了n套方案,结果还是没能决定用什么方法解之。
题目意思是给出一个序列,要求用最小的cost排序,将i放到某个值后面/前面,花费的cost是i。

大大的程序千奇百怪,有STL库牛人的,有搜索解的,有DP的,不过最欣赏的是这个解法:
可以把问题转换为, 求顺序的最大cost的序列,然后用总cost减一下就行了。

例如sample 中的 6 4 5 3 8 2 7 2 11 2 2,顺序序列可以是 4 5 8 11,也可以是4 5 7 11,对于一个顺序序列,剩余的部分便是待移动的数,为了保证待移动的数的cost尽可能小,就是保证顺序序列的cost尽可能大,所以取 4 5 8 11 的cost和为28,所以只需要用最少52-28=24来完成排序。按照这个思路,有程序:


public int calcMinimalCost(int[] arr){
int total=0,len=arr.length,maxCost=0;
int[] cost=new int[len];

for(int i=0;i<len;++i){
cost[i]=arr[i];
total+=arr[i];

for(int j=0;j<i;++j){
if(arr[j]<=arr[i] && cost[j]+arr[i]>cost[i]){
cost[i]=cost[j]+arr[i];
}
}
maxCost=Math.max(cost[i],maxCost);
}

return total-maxCost;
}

Tuesday, April 03, 2007

TCO Marathon Online Round #2

TCO Marathon OR2 finished. I got 65/445 rank, 11331.73 score, and advance to round 3 successfully. My friend, FinalLaugh, got rank 191, while he has got 200 rank when the submission parse finished, just by a finger's breadth. The path to Las Vegas is becoming more and more narrow, next time , we will fight for 50 contestants position.

The top 2 players' program scares me a lot...... So many codes that looks like "AA>s;oI2A". As we, FinalLaugh and I, discussed, it's a technology of compress.

I dislike this problem, two reasons: 1st, it's a gambling game; 2nd, it's solution is not so hard, but with a lot of work. My solution is so easy to implement , somewhat like saarixx (rank 7) did.

First of all, list all card combinations: {4,4} {3,3} ... 15 cases from high to low. Then, provide a simple solution to newcard(), round1(), draw() then take them into an array, each owns 15 elements, just like this:


 private int[] newc = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 };
private int[] rd1 = { 2, 2, 2, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0 };
private int[] draw = { 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2 };



For these three method, I think, a fixed strategy will be ok. As to round2(), it's more complexity, but as I'm so lazy.... I realize it like this:


   public int round2(int card1, int card2, int bets1, int bets2, int drew) {
int idx = getCardIdx(card1, card2);

// pre solution, like the too little card will return 0 immediately

switch (drew) {
case 2:
// some solution
case 1:
// some solution
case 0:
// some solution
}
return -1;
}



That's all, so simple but strong enough to advance to the next round. Wish my good luck next round!

Friday, March 30, 2007

TCO Marathon Online Round #1

TCO Marathon OR1 and OR2 has finished today, which consume my thoughts for two weeks.

To OR1, I got 1309.01 at submission phase and 4990.66 at end, final rank 67/532. See the problem here(need login), which is a really interesting and challenge game. As my solution, the image could be considered as a onion, the target of this prob is to peel the onionskin. So, here is the steps of my algorithm:

1. Generate the rays, those rays will be the easy calculating ones. (Attention, generate rays does not mean call the measure() method.)
2. Pick out the rays that just cross one unit('x' unit), and store them in a queue.
3 Iterate the selected rays, calculate the density of that unit(call measure() here), and then mark the unit as calculated.
4 Repeat step 2, but the more repetition we used, the more precision we will lost. It should be a bound to control the precision before we start this algorithm.

As to step one, the rays should be selected meet the condition that they will be easy to calculate, otherwise, it will be a great amount work of calculation and simulation. As I considered, each ray will start from one of the four corners of the unit, and the ray should cross exactly n row unit while it crossed 1 col unit, and vice versa. The main reason I ensure this condition is that every length of the row unit( according to my example) that the ray would cross is the same, and that would lower the complexity of the ray calculation. As a result, when n is greater than 30(I forget the exactly number, but 30 is enough), every unit will be covered.

As to step four, the repetition times is a hard decision. The performs of different repetition times would result in a normal distribution, and the peak of the distribution is the one we desired. I choose the repetition according to a great amount of test.

Thursday, February 08, 2007

Topcoder Marathon 11

Marathon的System Test也是Marathon式的,等了好久。


最后的Rank终于出来了,Rank 8, Score 388.10, Rating 1565。
第一名好夸张,有好多Case都是超我一倍的呀!赞~


第一次做Marathon,这次的题目是贪吃蛇,郁闷...是比较没新意的题目...个人感觉贪吃蛇运气因素较大(测试数据多的话,当然可以降低偶然性),没有觉得能在每盘上都赢得最高分的算法,但确实应该有能保证在大多数情况下表现出色的算法。
我的算法可以说是偶然性十足 -_-b,我也很纳闷,但就是没法解释,所以只能说偶然。Example Test 15次,Submittion 4次, Submittion的成绩是 30.76 -> 28.44 -> 28.13 -> 27.43 一个比一个差 -_-b (但我的想法好象是一个比一个周密才对呀)。

我的算法的思路是这样的:

Step 1. 将Snake的一次行为(调用 moveSnake())划分为三个小算法, 即计算路径、选路径、移动。

Step 2. 初始化地图我不算在算法里,因为太简单了。计算路径是指,用搜索类的算法,搜索出从Snake到Food的可能路径。

在这一步,我起初使用的是最简单的bfs(就是我得分最高的时候 -_-b)。优点是找到的豆路径最短,缺点是Snake老是擦豆而过,因为路径只关心最短的问题,如果路径上没有经过某个豆,那么即使该豆可以顺便吃掉,Snake也会置之不理。
然后,我改进了算法,使用pfs,以吃到的豆的个数为优先级别进行路径搜索。这样每次都是从吃豆最多的路径继续往下搜,就可以保证将顺路的豆子都能吃掉。结果是,这个pfs每次比bfs走的步数要少,从虚拟程序上看,Snake聪明了许多,但就是吃豆数不见长。

我这有一个我没去实现的算法(因为后面两天都有事),想法是使用多端bfs:Snake头和各个Food各准备一个队列,然后每个循环,依次对每个队列进行扩展。当某个Food队列扩展的路径碰到了Snake的路径,那么Snake到该Food有路;若是Food队列碰到Food队列,那么这两个Food有路。且所有路径最短。改算法的好处是能求出所有最短路径、而复杂度与bfs相同,只是实现有点复杂。

Step 3.选路径,在我自己测试的时候发觉,选路径对于整体结果影响最大!也可以说是偶然性最大的一块。依据Step2中的bfs或者pfs所得到的结果,应该说Snake到Food的路径是固定的,所以就可以说是选下一个受吃的Food。怎么选Food呢?
我给每个Food设置了三个值:
  1. 吃到该Food的路径上顺便可以吃到的Food
  2. 假设Snake吃该Food,那么吃到Food后,Snake的可移动空间,即一次bfs从Food点开始计算空间(要先模拟好Snake吃到Food的位置)。
  3. 该区域内还有多少未吃的Food,即如果Snake逃不出该Area,可能还有多少Food可以吃。
综合上述三点,可以计算出一个优先级。我以Area大小和Snake蛇身长度为主要依据,若蛇身长远远超过Area那么Snake应该说可以有更大的活动,也可以说Snake有机会逃出该Area。再依据Snake吃该Food时顺便吃的Food和该Area潜在的Food同样影响这个优先度。若优先度相似,则可能再吃的Food数量加上顺便吃的Food数量为第二优先级。
在这里,Area的关系十分重要,很明显的,因为Area表示Snake存活概率高,只要活着就不怕没Food吃。
比如下面两个情况:

# ###
#O # #
# #O#

很明显,这些Food是DeathFood,优先级要最低,也就是说实在没Food可吃才吃他们。

对于多端bfs,选择的就不是Food了,而是真正的Path。这个就可能和前面的算法完全不同了,我又没实现多端bfs,而且这个貌似更加复杂,就没深入。。。另一方面是被偶然性搞怕了,深怕花了大半天写的程序结果还不如以前的好。

Step 4.移动,即与平台交互,这个只要认真看看平台文档就行了。另外,为了保持Snake和Food位置与平台同步,自己也要模拟Snake的移动。

附加一个想法,由于Step3提到,Snake吃到Food后的Area问题。所以我考虑是否可以通过预广搜一次,标记所有的Area(或者这里称Prison,也就是整个Area有一个格子负责与外界连着,其他格子均与非此Area的格子无连接)。起算法是,由任意一点,开始bfs,但搜索到的格子只有一个目(类似于围棋的目,就是下面这种情况,X是搜索过的格子,#是障碍物,@就是只有一个目):

XX#
XX@
XX#

这种情况下,将@放入一个Door队列(初始为空)。若搜索第二次遇到@,则@不是Door,标记为搜索过的格子。在待搜索队列为空时,若Door个数大于1,则Area不是Prison,可以再扩展;否则该Area为Prison,同时去除该Door。然后任取一个外面一个没有搜索过的格子再bfs,同样,若搜索到某个Door则取消它Door的资格,否则搜索完再计算Door个数。
可能出现这种情况: Prison - Door - 2个Door的Area - Door ,此时,2个Door的Area不是Prison,但它加上里面的Door和Prison就是一个Prison。可以说是有层次关系的。
总之,算法很复杂,当时摆着电脑楞了许久,最后还是决定先暂时把这个想法搁着 -_-b 。

Friday, January 26, 2007

Topcoder SRM336 DIV II

又回来做Topcoder了,用Ubuntu就是爽啊,又快又华丽,破Windows上Topcoder会有问题。最近Vista的漏洞频频被揭,微软信用大大受损,相反的,Solaris一方面与Intel达成战略合作,另一方面Solaris在争取PowerPC的营地。

学校的网络真是一团糟! 我只能找个HTTP代理,用HTTP的管道上topcoder。时不时就掉下线来。

比赛依然是紧张激烈的,这次是在DIV II 里,rating大概是房间里排第三 1157分。 三道满简单的题目,都是在时间内做出来的:
237.65/250 - 383.94/500 - 0.00/900
其中900的那题,我初次提交是580分好象,后来发现漏了一个case,急啊,赶紧在比赛结束前一分钟改过来(汗,真的是结束前一分钟啊,看着秒数动的,那个心跳啊),得292.85。

challenge阶段网络麻烦不断啊,每每打开别人的代码就会断线,浪费了好多时间。而challenge第一次失败了,虽然我找出了别人的bug,但数据没出好,第二次才成功挑战掉。 我challenge的是900的那道中score有重复的情况,这也是我后来发现自己没考虑到的一个case: {3,3,3} 1 4,如果不考虑重复会得结果 -1,但是正确结果是4。

System Test的最后结果是 900分的Failed,郁闷-_-b,不过还好,还是第三。现在rate 1231, -_-b 再接再历啊。