May 20

24点 不指定

felix021 @ 2009-5-20 02:08 [IT » 程序设计] 评论(3) , 引用(0) , 阅读(5532) | Via 本站原创
本文包含一个很挫的算法和一个很赞的算法
-----------------------
24点是一个足够古老的游戏了:
给出四个一位数(可能重复),使用加减乘除和括号将这四个数字整合成一个算式,使得算式的结果等于24。
当然,不是所有的四个一位数都可以组合成24的,比如1,1,1,1显然就不能。
有两个经典的组合是 1, 5, 5, 5 和 3, 3, 8, 8 —— 你可以找出有效解吗?

对于任意给出的四个一位数,如果不考虑重复的情况(比如1,5,5,5,把三个5当成不同的数字)
那么可以算出,可能的算式有 (4*3*2*1) * (1*2+1*1+2*1) * (4 * 4 * 4)  = 7680种。
这么大的数字用人脑去算是很不合理的,所以应该写一个程序来处理,嗯。
(中间的 1 * 2 + 1 * 1 + 2 * 1是什么?——下面再说)

这个程序的实现我大一的时候曾经想过,但是那时候对语言掌握都很差,更别说写出具体的回溯等算法来实现。
昨天又想到这个问题,于是拖了这么久,终于在今天把代码写出来了(一题两年出,一跑就郁闷)。
May 18
网上可以搜到很多相关的资料,这里不具体说了,
大致就是,定义struct/class的时候 编译器会把其中的每个成员的存储位置对齐
对齐原则一般是和该成员大小一致,比如int的就从%4=0的地址开始存储,多余的空间就编译器随便填充了。
然后struct还要填充,直到整个struct的大小是最大成员大小的倍数(VC6.0)——为什么呢?
Felix觉得这样是为了保证连续存储的时候下一个struct的最大成员也是对齐的。
此外,编译器还会对struct的存储地址进行对齐。

我实际测试了一下,我发现32bit的gcc在对struct进行填充的时候
如果某成员的大小大于4Bytes(long long, double)的时候,实际上是以4字节对齐的。

如果在程序中指定了
#pragma pack(n)
那么对齐的时候就会把元素的大小和n进行比较,取较小的那个来对齐。

此外:
#pragma pack(push)
#program pack(pop)
可以成对使用,用来保存(push)当前的n和取出(pop)之前的n——当然,这是在编译过程中处理的。

下面是我的一段测试程序:
May 16

字符串的Hash 不指定

felix021 @ 2009-5-16 23:11 [IT » 程序设计] 评论(1) , 引用(0) , 阅读(5386) | Via 本站原创
早上参加了腾讯的笔试,做完以后自我感觉良好,但是后来和sandy讨论了一下,发现还是挫了,因为没用上Hash。
于是中午回去狠查了一些资料,看到了一点东西,充实了些。

看到一些字符串的Hash函数,想测试一下它们的实际性能
于是写了个程序来生成5w个字符串
用不同的hash函数计算hash值模9793(随便写的一个数字)
然后再用 sort 和 uniq 看了一下结果,发现 sdbmhash 是最好的, jshash其次,也很不错
然而很意外的是 elfhash 的性能则相当差,完全达不到可用的标准
——在对5w个数进行hash以后得到的结果里面,居然有700多个0和400多个1。

下面贴一些代码:

gen_data.cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

char tbl[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+=-?><:';,./][{}\\\"";

int main(){
    char s[1024];
    int i, l, m = strlen(tbl), j;
    srand(732984);
    freopen("str.txt", "w", stdout);
    for (i = 0; i < 50000; ++i){
        l = rand() % 100 + 1;
        for (j = 0; j < l; ++j)
            s[j] = tbl[rand() % m];
        s[j] = 0;
        printf("%s\n", s);
    }
    return 0;
}


hash.cpp
#include <iostream>
#include <cstdlib>
#include <cstring>
using namespace std;

unsigned int elfhash(char *s){
    int hash = 0, x = 0;
    while (*s){
        hash = (hash << 4) + (*s++);
        if(((x = hash) & 0xf0000000l) != 0){
            hash ^= (x >> 24);
            hash &= x;
        }
    }
    return hash & 0x7fffffffl;
}

unsigned int jshash(char *s){
    int hash = 1315423911;
    while (*s){
        hash ^= (hash << 5) + *s++ + (hash >> 2);
    }
    return (hash & 0x7fffffffl);
}

unsigned int sdbmhash(char *s){
    int hash = 0;
    while (*s){
        hash = (hash << 6) + (hash << 16) - hash + *s++;
    }
    return (hash & 0x7fffffffl);
}

int main(){
    char s[1024];
    freopen("str.txt", "r", stdin);
    freopen("elf1.txt", "w", stdout);
    while(true){
        scanf("%s", s);
        if(feof(stdin)) break;
        printf("%d\n", elfhash(s) % 9793);
    }
    return 0;
}


查看结果:
引用
$ sort elf1.txt | less
$ sort elf1.txt | uniq | less
May 13
今天回顾WOJ1398,发现了这个当时没有理解透彻的算法。
看了好久好久,现在终于想明白了。
试着把它写下来,让自己更明白。

最长递增子序列,Longest Increasing Subsequence 下面我们简记为 LIS。
排序+LCS算法 以及 DP算法就忽略了,这两个太容易理解了。

假设存在一个序列d[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。
下面一步一步试着找出它。
我们定义一个序列B,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了

首先,把d[1]有序地放到B里,令B[1] = 2,就是说当只有1一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1

然后,把d[2]有序地放到B里,令B[1] = 1,就是说长度为1的LIS的最小末尾是1,d[1]=2已经没用了,很容易理解吧。这时Len=1

接着,d[3] = 5,d[3]>B[1],所以令B[1+1]=B[2]=d[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候B[1..2] = 1, 5,Len=2

再来,d[4] = 3,它正好加在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候B[1..2] = 1, 3,Len = 2

继续,d[5] = 6,它在3后面,因为B[2] = 3, 而6在3后面,于是很容易可以推知B[3] = 6, 这时B[1..3] = 1, 3, 6,还是很容易理解吧? Len = 3 了噢。

第6个, d[6] = 4,你看它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len继续等于3

第7个, d[7] = 8,它很大,比4大,嗯。于是B[4] = 8。Len变成4了

第8个, d[8] = 9,得到B[5] = 9,嗯。Len继续增大,到5了。

最后一个, d[9] = 7,它在B[3] = 4和B[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。

于是我们知道了LIS的长度为5。

!!!!! 注意。这个1,3,4,7,9不是LIS,它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。虽然最后一个d[9] = 7更新进去对于这组数据没有什么意义,但是如果后面再出现两个数字 8 和 9,那么就可以把8更新到d[5], 9更新到d[6],得出LIS的长度为6。

然后应该发现一件事情了:在B中插入数据是有序的,而且是进行替换而不需要挪动——也就是说,我们可以使用二分查找,将每一个数字的插入时间优化到O(logN)~~~~~于是算法的时间复杂度就降低到了O(NlogN)~!

代码如下:

//在非递减序列 arr[s..e](闭区间)上二分查找第一个大于等于key的位置,如果都小于key,就返回e+1
int upper_bound(int arr[], int s, int e, int key)
{
    int mid;
    if (arr[e] <= key)
        return e + 1;
    while (s < e)
    {
        mid = s + (e - s) / 2;
        if (arr[mid] <= key)
            s = mid + 1;
        else
            e = mid;
    }
    return s;
}

int LIS(int d[], int n)
{
    int i = 0, len = 1, *end = (int *)alloca(sizeof(int) * (n + 1));
    end[1] = d[0]; //初始化:长度为1的LIS末尾为d[0]
    for (i = 1; i < n; i++)
    {
        int pos = upper_bound(end, 1, len, d[i]); //找到插入位置
        end[pos] = d[i];
        if (len < pos) //按需要更新LIS长度
            len = pos;
    }
    return len;
}


update @ 2016-08-21

没想到7年多了还要更新一下……

有几位同学在评论中问到如何给出一个LIS而不仅是计算长度。具体的代码我没有写过,不过大概可以这么实现:更新B[i]的时候,把记下来数字在原来数组中的下标也记下来(被替换的数据保留在一个后备数组中)。等到得出 B[n] 了以后,用贪心算法往前回溯,每次找出B[i-1]对应后备数组中值小于B[i]、下标小于B[i]下标、且在该后备数组中下标最大的那个。

update @ 2017-04-16

补充一下,由于上面那段代码用的是upper_bound,所以实际上求的是最长不下降子序列;如果要求递增子序列,应该改用lower_bound。
May 11

廉颇老矣 (装B一下) 不指定

felix021 @ 2009-5-11 00:35 [IT » 程序设计] 评论(2) , 引用(0) , 阅读(5051) | Via 本站原创
今天不太想做其他事情,所以就跟着acm集训队做了两场在toj上面的个人选拔赛。

早上的那一场做得比较随便,跟师兄聊着聊着就聊过头了,等聊完都过了十几min了。于是没有看statistics,挑了个逆序数的题目做,数据比较大,所以用归并,但是居然WA了。到WOJ的1046去测试了一下,是AC的啊,郁闷。于是去看Ranklist,发现另外一道简单题大部分人都过了,于是去写,很快1AC。然后回到那题,发现它对付超大数据有问题,于是改成long long,AC。然后又看到最后一个简单的题目,大约花了10min(说明我代码速度还是不行啊),1AC。虽然这个时候罚时比较多了,但是出3题的人不多,所以rank还比较靠前。剩下的时间在对付那个概率论的题目。显然[30][30][1000]的数据硬搞是要TLE的,不过对于我这种DPSB而言,还是老老实实先写一个出来再说。看题目就花了不少时间,然后写了个爆搜,算出了题目test case,但是提交上去就MLE了。然后想试试打表,但是在自己机器上都算不完。
比赛结束后发现成绩还是有点囧,Rank17,铜牌之后的两个。。。sigh。

下午的题目比较多,ABCDEFG。都还有点难度,所以大概20多分钟以后才开始写,把那个切割的题目写了(按照二分的模式去模拟就行了),然后看那个tree的问题,其实思路还是比较清晰的,就是DEBUG用了很久,就那么三四十行的代码啊。。。sigh。然后过了三组test case,提交WA。囧啊囧啊囧。想了很久,发现原来node[1]不一定是root。邪恶的测试数据。。再改一下,就AC了。这时候只剩下一个小时。然后去看A的那个题目。那是去年暑训的题目,当时就完全没有思路,这一次稍微有点思路,枚举+列方程求解,但是最后还是没有搞定。赛后看了jieyu的A和E的代码,然后没什么想法,继续搁置那题。。。。。。
比赛结束后Rank18,除掉那个测试帐号,和上午一样,恨。。。

sigh。我发现我始终还是只把acm当作娱乐项目,就这么混过了3年。
May 10

TIC初赛:3AC 2放弃 不指定

felix021 @ 2009-5-10 01:12 [IT » 程序设计] 评论(2) , 引用(0) , 阅读(4596) | Via 本站原创
对于一个felix能做出4题的比赛,我想这次的题目实在是够简单了。毕竟是初赛。
A题,服务器173上的A题(据测试,至少有4台独立的服务器170~173跑着POJ的程序)
就是那个求和的程序,那显然应该速战速决1AC——这是我机器上14:01:02写完的程序:
#include<iostream>
using namespace std;

int main(){
    int n, i, t, sum = 0;
    scanf("%d", &n);
    for (i = 0; i < n; ++i){
        scanf("%d", &t);
        sum += t;
    }
    printf("%d\n", sum);
    return 0;
}


173上的B题,也就是企鹅豆豆的那题,让我想起了打豆豆的笑话。
一眼看过去,觉得是一个超简单的题目,sort一遍,然后O(n^2)遍历求出一个i,比penguin[i]高且力气大的企鹅是最多的。
通过了test case,然后提交,WA。
暂时忽略之,然后看了CDE题的题意,发现还是应该先搞B。
于是回过头来,仔细想了一下,发现自己SB了:
以H(Height)和S(Strength)建立一个直角坐标系,把所有的企鹅放进去
然后对任意两个企鹅a, b: 如果La < Lb 且 Sa < Sb,那么画一条从a到b的有向线段,于是就构成了一张图,或者是一个有交叉的森林,或者又可以叫做拓扑图?反正就是那么一个东西,然后有那么几个点只有出度没有入度(暂且称之为源点),有那么几个点只有入度没有出度(暂且称之为终点),我们要找一条从某一个源点到某一个终点的最长的路线。顺着这个思路,于是就想到了BFS:对每一个点都BFS过去,最后遍历所有的点,找出最大的深度,就是所求结果了。很快code完,提交,TLE。囧。看了一下,发现自己非常SB地在每次BFS以后都把所有的点的depth初始化了。注释掉这一句,提交,AC,14:32:58。
Apr 29
超赞。有了形象的动画,配上伪代码,算法学起来应该会简单一点吧?

http://www.sorting-algorithms.com
Mar 29
10题,Boluor负责看ABC,Sandy负责看DEF,Felix负责看HIJK。
其实是给自己找理由,拖时间,然后等Board看拿题可以做,嗯。
一番折腾以后开始写。。额。。没题可以写=.=
Boluor看了A题,推导公式,推了半天没思路(怪不得他家教的高中生立体几何听不懂=.=)
于是我接过来,把公式从头推导了几遍,写阿写阿写,写,wa了n次。
发现样例数据是圆柱,出题人太狠了=.= 都不给个圆台的测试一下。。。
然后发现,rRHV是float,不是integer =.= Felix的错,嗯。
后来找了几组比较好算的数据测试,比如1 2 2 19/12PI之类的,都OK,但是还是WA。
而且加了一句 if(hx > H) hx = H; 但是还是WA,囧。
好吧,Sandy看了看,E题是个简单的模拟,于是他上。
写出来代码还是比较顺利的,跑样例也是lose/win/lose,很好很强大,可惜就是WA。
然后多亏了我家可爱宠物加菲的名字比较短,xay,算了一下,xx, 50, xx
然后对照了一下Sandy的程序,xx, 22, xx,嗯。囧阿。
查了一下,原来是累乘器初值是零,改之,交之,AC之,Happy之,2Hour了已经=.=
然后继续A,Boluor以为是精度的问题(因为需要开三次方)
于是根据我的思路,把我一步一步的计算合并,化简
然后囧囧囧囧地测试,发现都OK阿,圆柱的也对,圆台的也对,终于咬牙交了一次。
嗯,结果果然是WA。
好吧,Sandy说,测一组极端数据,于是100 100 100 1000000000,答案是3800+
然后Felix终于反应过来,这TM不是溢出了么?——开水溢出了,嗯。
好吧,我承认我又做挫事了,把上面那句if(hx > H)放在if(r == R)的else里面了=.=
提出来,交之,AC之,Happy之,193min,5次提交-.-
然后Sandy看I题,想到了O(n^2)的算法,想RP之,于是去敲代码
我则拿起J题,好吧,又是推公式,解析几何,我郁闷。
................................坚持不懈地,终于退出了公式
把Sandy换下来,敲代码,到末尾发现有个地方没想明白——算根的时候取正号还是符号?开方的时候呢?
于是回头又仔细想了下,并重新推了公式,发现原先的公式推错了,于是再次把sandy换下来
敲代码,编译,测试,WA掉。
然后和Boluor讨论了下,让他看着我把公式重新推一遍,发现我的公式确实都是正确的
但是代码思路有点混乱,于是重新去改了下,清晰了,测试
然后囧囧囧囧地发现,答案是1.56xxxx,我们的答案是0.0097xx
加起来正好是PI/2,心情激动阿,差点就学后面的同学吼出来了。
然后一查,发现我没有加acos =.=
加上acos,第一组测试数据正确了,但是后面两组在输出-1以后跟上了一堆乱码,我囧。
其实是我的cmp(double,double)把符号写反了,delta<0每次都是false,
于是对小于零的double也开方了,但是输出的乱码又是-1开头的,这个东西相当有迷惑性阿,
不得不佩服裁判出的输出规则,你说要是没解你输出个No Solution多好阿,浪费俺们时间么=.=
然后在Sandy的帮助下找到了这个答案——原来我又做挫事了,sigh。247min
最后的时间都给sandy做他的I题,本来我是帮他看代码的,但是思绪有点混乱,看不下去
Boluor帮他出了几组小数据,在几次RE以后都没问题,交之,很不出意料地TLE了
这个时候还有8min,于是我就去找我家宠物feli玩了,嗯。
--
我发现我们队是慢热型的,去年的校赛他们两个也是这样的,今年的预赛也是这样的。
所以我们实在是不适合参加激烈的ACM比赛,不过还是有一点非常赞的——
俺们队灰常河蟹,嗯。
尽管这一次Boluor没有写出AC的代码,但是A题和J题少了他,也是做不出来的。
同样,在DEBUG那A和J两题的时候,Sandy也给出了关键性的意见。
当247min看到Judge返回J的那个YES以后,我们几乎是同时喊出一声YES!这种感觉真好:D
这次比赛,根据预赛的情况,Felix的期望是三等奖,RP爆发或许有二等奖
最后出了3题,在校内11名,达到了预期目标,三等奖(第三),很满足了(就是奖金有点少=.=)
此外我发现,BFS在做比赛的时候,一直是很开心的~就像去年去NUDT一样~ ^_^
我觉得这才是最重要的,有处得好的队友,一起努力,开心做题。
分页: 12/22 第一页 上页 7 8 9 10 11 12 13 14 15 16 下页 最后页 [ 显示模式: 摘要 | 列表 ]