SQYBI.com

Change is a part of life, and takes part in finding us who we are.

2008年08月28日
by sqybi
9 Comments

确定性有限状态自动机(DFA)

确定性有限状态自动机,又称确定型有穷自动机,Deterministic Finite Automaton,DFA.
这两天blogbus那个blog上有一个人找我要DFA的代码,这里干脆直接贴出来.加上了一些注释,还有DFA的简单介绍.写的很简略,也很难懂(=_=),请凑合看...

DFA的基本思想就是建立一棵Trie树,然后用BFS求出每个结点的前缀指针,再跑自动机...
我见过的用法,一个是多模式串匹配,另一个是自动机上的DP(见URAL 1158).这里只介绍多模式串匹配...

从最后开始说...假设我们构建了一个DFA,我们怎么跑呢?
首先了解前缀指针:假设有一个节点k,代表的单词(就是从根结点走到k结点经过的边上的字母连起来)是串s1.而k的前缀节点trie[k].pre代表的单词是s2,那么s2一定是trie树中存在能够作为s1后缀的单词中最长的一个...
跑自动机的时候,只要用母串在模式串建立的自动机里,每次对应母串的一个字母移动一格(如果移动不下去就移动到前缀指针并继续移动),然后移动到结点i之后,将i的所有前缀指针和前缀指针的前缀指针标为已访问.
最后只要模式串单词的结束结点被访问过,就说明这个模式串出现在母串中了.
可以发现,如果改成一个模式串,DFA和KMP是完全相同的.

关于Trie树的建立不说了,说一下前缀指针的建立:在Trie上BFS,一旦BFS到新的结点k,且k的父亲到k的边上字母为ch,那么这个结点的前缀指针这样来查找:首先记录i等于k的父亲的前缀指针,然后循环判断i有没有ch这条边指向自己的孩子.如果有,那么k的前缀指针就是i的这个孩子;否则i<-i的前缀指针.
可能这样说不是很清楚...但我想不出怎么在没有图的情况下说清楚...

然后DFA算法就这样...没什么太难的...几十行代码的事情...
还有个很好玩的事情...我一般用RK和DFA分别处理多维和多模式串匹配的问题...所以对于单维单模式串,我一般会从这两个里选一个(大多数情况是RK),对于我KMP就没啥用了...

最后贴代码.

/*
  确定性有限状态自动机(多模式串匹配); DFA
  - sqybi's code
  - 输入格式:
    第一行一个数n,代表母串长度;
    第二行一个字符串a,代表母串;
    第三行一个数m,代表模式串个数;
    接下来m行每行一个字符串b[i],表示每个模式串
*/
#include
#include
#include

using namespace std;

const char chSt = 'a', chEd = 'z';
 //描述一个字符集,输入的串只能用此字符集
const int nn = 100000, mm = 1000, kk = 1000, cc = chEd - chSt + 1;
 //母串长度,模式串数量,模式串长度,字符集大小

struct trieNode
{
    int son[cc]; //trie的结点每条边指向的孩子编号,0为此边无孩子
    int pre; //前缀指针指向的结点编号
    bool visit; //该结点是否被访问
};

int n, m, now, tmp, trieNum, qs, qe;
 //不一一列举了...
string a; //母串
vector< string > b(mm); //模式串
int bLen[mm], stop[mm]; //模式串长度,模式串终止结点编号
trieNode trie[mm*kk+1]; //trie树
int q[mm*kk+1]; //BFS的队列

int main()
{
    ifstream fin("dfa.in");
    ofstream fout("dfa.out");

    //input
    fin >> n >> a;
    fin >> m;
    for (int i = 0; i != m; ++ i)
    {
        fin >> b[i];
        bLen[i] = b[i].size();
    }

    //build Trie
    //注意这里有一个0号结点,其实只是为了前缀指针构建比较方便.
    //如果一个结点没有前缀,那么它的前缀指针就会通过0指向1.
    trieNum = 1; //trie的结点数
    for (char ch = chSt; ch != chEd + 1; ++ ch)
    {
        trie[0].son[ch-chSt] = 1; //0号结点的所有孩子都指向1(注意这些边没有实际意义)
        trie[1].son[ch-chSt] = 0; //1号结点厨师没有孩子
    }
    for (int i = 0; i != m; ++ i) //对每一个模式串
    {
        now = 1; //从1号结点开始建边
        for (int j = 0; j != bLen[i]; ++ j) //每次向下伸展一个结点
        {
            if (trie[now].son[b[i][j]-chSt] == 0)
            {
                ++ trieNum;
                trie[now].son[b[i][j]-chSt] = trieNum;
            }
            now = trie[now].son[b[i][j]-chSt];
        }
        stop[i] = now; //记录第i个模式串的终止结点编号
    }

    //BFS (build pre)
    qs = 0; //队列首位置
    qe = 0; //队列尾位置
    q[qs] = 1; //开始队列只有一个1号结点
    trie[q[qs]].pre = 0; //它的前缀指针为0
    while (qs <= qe) //如果队列不为空
    {
        now = q[qs]; //处理当前结点
        for (char ch = chSt; ch != chEd + 1; ++ ch)
            if (trie[now].son[ch-chSt] != 0) //对于它的所有孩子
            {
                ++ qe;
                q[qe] = trie[now].son[ch-chSt]; //加入队列
                tmp = trie[now].pre;
                while (trie[tmp].son[ch-chSt] == 0) tmp = trie[tmp].pre;
                trie[q[qe]].pre = trie[tmp].son[ch-chSt]; //以上三句为建立pre的过程
            }
        ++ qs;
    }

    //main (run DFA)
    now = 1; //从1号结点开始跑DFA
    for (int i = 0; i != n; ++ i) //每次跑母串的一个字符
    {
        while (trie[now].son[a[i]-chSt] == 0) now = trie[now].pre;
         //如果当前结点没有这个孩子那么就顺着前缀指针跑并继续尝试
        now = trie[now].son[a[i]-chSt]; //向下移动到这个孩子
        tmp = now;
        while (tmp != 1 && (! trie[tmp].visit))
        {
            trie[tmp].visit = true;
            tmp = trie[tmp].pre;
        }
         //将这个孩子和它所有前缀指针和前缀指针的前缀指针指向的结点标记为已经访问
    }

    //output
    for (int i = 0; i != m; ++ i)
        fout << trie[stop[i]].visit << endl;
}

2008年08月27日
by sqybi
8 Comments

如何用好C#的Paint重绘

看了wincss的校内日志,觉得写的很不清楚...加上校内比较封闭...决定写一个详细些的放到这里.

首先,我们要做到的是,在窗口上画矩形.
鼠标按下并拖动的时候,矩形会跟着鼠标的移动变化大小;等到松开鼠标的时候,矩形就画上了.

首先想到的是在BitMap上绘画并放到PictureBox里,然后MouseMove触发重绘.但这样还要记录一个当前这次鼠标按下之前的图,然后重绘的时候需要在那个图的基础上重绘,比较麻烦,而且因为重绘次数很多,速度很慢.
有没有别的办法呢?

我们可以看一下PictureBox的Paint方法.
Paint方法在每次PictureBox需要重绘(Invalidate方法或Refresh方法被调用)的时候触发,用于重绘窗体.
BitMap不用写到Paint里就能被重绘是因为它会被自动重绘.而我们上面的那个重绘方式每次正是需要在原来图的基础上重绘.这样我们是不是可以在MouseUp之前不改变BitMap呢?
答案是肯定的,我们可以在PictureBox的Paint方法里调用e.Graphics.DrawRectangle方法进行重绘.这样重绘出来的东西会放在BitMap上面,相当于在ImageBox里的BitMap上新建了一个透明层...很神奇的.
然后等到MouseUp被触发,再将矩形绘制到BitMap里并重绘即可.

注意在Paint方法中调用了e.Graphics.DrawRectangle而不是pictureBox1.CreateGraphics().DrawRectangle,后者不可行,它画出的矩形会一闪即逝.

整个程序的代码点这里下载.
代码由wincss编写,本人做了些许更改(主要是从VS08的格式转到VS05,因为我没有08)并加了注释.在VC#2005下编译并运行测试通过.

感谢wincss对本人提供的帮助...
大概有了一个想法 和jl一起编一个有关追逐的游戏 但还不是很成熟 先不放出来了

2008年08月27日
by sqybi
4 Comments

TopCoder SRM 415

彻彻底底被屠了.
challenged + challenged + unopened = 0 pts...

250pts,纯水,贪心就行.可我竟然忘记了处理-1的情况...然后Run System Test才发现两个-1的点TLE了.
这道题也花费了我太长的时间...C++用的本来就不熟悉,然后TopCoder的编程环境是那么的别扭(在Code::Blocks里写更别扭)...

然后500pts,据说可以分半DP,我写的搜索,可惜250费的时间太多,500到最后样例还是WA.就是一个V很大n很小(32)的01背包.

1000pts,比赛结束之后我才开了看.模糊记得以前似乎见到过一道这样的题目,是自动机上的DP吗?求大牛解答.

屠惨了...还剩1400多分.蓝了.
其实250如果过了的话应该还可以黄的...

2008年08月24日
by sqybi
18 Comments

TopCoder入门教程 -- sqybi完善版

本文根据经典的TC教程完善和改编。
TopCoder:
http://www.topcoder.com/

基本规则
TopCoder的比赛类型很多,最常见的是周赛SRM(Single Round Match),另外还有TCHS SRM(TopCoder High School SRM,题目和SRM一样,仅限中学生参加,参赛者水平较低,据说涨rating很容易),马拉松(Marathon Matchs),还有TCOpen(每年两次的大比赛)之类的比赛。因为大多数人都在做SRM,所以本文介绍的TC比赛即为SRM。
SRM的规则总结起来就是一句话:75分钟做完3道难度递增的题。
TC的每个用户(handle)都有自己的积分(rating),从0-3000+不等。成绩越好,分数越高。
积分与颜色的对应为:白色——未参赛(unrated);灰色——0~899;绿色——900~1199;蓝色——1200~1499;黄色——1500~2199;红色——2200+。另外排名最高的几个人在TC客户端中会变成红色靶子图标。
比赛分为两个Division,Div I和Div II。白色灰色和绿色的参加Div II,蓝色黄色和红色的参加Div I。Div I的题要比Div II难许多,一般DivII的最后一题和Div I的第一或第二题是一样的。无论是Div I或Div II。三道题目的Score一般为250, 500和1000。
TC的计分规则很诡异,可以见http://www.topcoder.com/wiki/display/tc/Algorithm+Competition+Rating+System,但基本是没人看的懂。不过,TC积分规则的基本思想很简单。
首先是SRM每道题的计分规则。题目从打开开始计时,随着时间的流逝,你这道题目可能得到的分数也越来越少。不过分数减少的速率会逐渐变慢(有人说是先快后慢再快再慢,我不清楚到底哪个是对的,不过总体趋势是越来越慢),一般1000分的题目在降低到300分的时候就基本不会再下降太多了。每道题点击Submit才算正式提交,如果Submit之后发现错误,还可以再次点击Submit修改提交,不过这样会扣除这道题一定的分数。
其次是TC的计分规则。复杂的数学公式很难看懂,但大概的计分思想是:根据此次比赛的得分算出一个这次比赛的rank,然后和以前的rank做比较,求出一个期望的rank,再根据这个期望的rank调整rating。而有时也会出现考得很砸但依然涨rating的情况,不过总体来说TC的计分公式是十分稳定的。

运行环境
TC的客户端是一个Java程序,所以需要JRE(Java Runtime Environment)或者JDK(Java Development Kit)来运行。如果平时不写Java程序的话,装JRE就可以了。毕竟JDK比JRE大一个数量级,下载慢。安装照着提示完成就行了。推荐使用1.4.1以后的版本,因为带了java web start,可以快速登陆。具体方法下一部分讲。
JRE下载地址(中文):
http://www.java.com/zh_CN/download/index.jsp

注册
原文在注册的地方没有详细说明,但很多人似乎对注册都有疑问。所以这里我来注册一个小号,并通过整个过程讲解如何注册。
首先打开http://www.topcoder.com/tc(本文后面TopCoder的主页都指这个网址),然后点击右上角的Register Now(没看到?你可能看到了一个Login,目光向下挪一点,那个红底白字的“Register Now”就在下面)。接下来会弹出http://www.topcoder.com/reg这个页面,因为我们要参加SRM,所以选择第一个,Competition Registration。如果要参加TCHS可以选择第二个High School (Secondary School) Registration。这些以后都可以更改(这里没有选的如果以后要选上,只需要更新个人设置并挑勾;如果选上的要撤销选择则需要点一个“Unregister”的链接)。这里选择项目的多少和后面的页面需要填写内容的多少相关,本文以只选择第一项为例。
需要填写的项目和对应的中文翻译如下:

* Given Name:  名
* Surname:     姓
* Address1:  地址1
Address2:  地址2
Address3:  地址3(如果一行写不开,就在三行中分别写)
* City:   城市
State (US Only):  地区(不在美国就不用选)
Postal Code:  邮编
Province:  省
* Country:  国家
* Country to represent:  代表国家(不知道啥意思,中国人两个都填China就行)
* Timezone:  时区(选Asia/Chongqing)
Phone Number:  电话号码
* Email Address:  电子邮件
* Confirm Email Address:  确认电子邮件地址(就是把电子邮件地址重新打一遍)
Email Notifications:  邮件提醒(就是它给你发邮件提醒什么东西)
- Algorithm Competitions  算法比赛,就是SRM和TCOpen
- Software Development Opportunities  貌似就是有软件开发的项目就告诉你
- Employment Opportunities  工作机会
- TopCoder News & Events  新闻
* Enable Member Contact:  允许成员联系(似乎就是说是不是让别人在TC上能找到你)
* Show / hide earnings:  显示/隐藏收入(大概是说别人是不是能看到你赚了多少钱,TC的比赛可是有钱赚的)
* User Name:  用户名(下面的话提醒你一定不要填错,因为注册多个用户是不符合规定的。据说有人因为别人在TC客户端和他打招呼说“怎么你拿小号上了”,那个人的号就被封了)
* Password:  密码
* Confirm Password:  确认密码
* Secret Question:  密码找回问题(找回密码时需要回答这个问题,注意至少要8个字符长,而一个中文字似乎算一个字符,所以最后可能要打几个问号补齐长度)
* Secret Question Response:  密码找回问题答案
Quote:  座右铭,就是个签名档之类的东西
* Student/Professional:  学生/职业程序员
* = required  带*的项目必填

填写之后点Term of Use下面的I Agree,再点Submit,完成提交。除了用户名别的以后似乎都可以改。
接下来进入Demographics页面,这个大概相当于一个注册用户情况调查。

* Age :  年龄
* Gender :  性别(Male男,Female女)
* Ethnic Background :  民族背景(似乎选Asian or Pacific Islander就行吧……)
* Primary Interest in TopCoder :  在TC的主要兴趣,看不懂的就选第一个吧,那个是说你的兴趣在奖金……
* Shirt Size :  T-Shirt大小(有的比赛会给排名前N的选手发T-Shirt,这里你需要选择适合自己的大小,如果选最后一个说明你不想要T-Shirt,人家也不发你了。TC的T-Shirt还是挺好看的,比AStar的好)
* College Major :  大学的专业
* College Major Description :  这个不知道啥意思,随便填点东西就行……
* Degree Program :  学位(从上到下分别为:准学士,学士,硕士,博士,中学生)
* Graduation Year :  毕业年份
* Graduation Month :  毕业月份
* Clubs / Organizations :  组织(一般选None,可以按住Ctrl点鼠标多选)
Other Clubs / Organizations :  其它组织
* School:  学校(点Choose School选择学校,可以搜索,不过为啥shanghaijiaotong university才2个人注册?!)
* Show / hide my school:  显示/隐藏我的学校
GPA:  不懂的自己百度去……
GPA Scale:  同上
Resume:  简历
* How did you hear about TopCoder?:  你怎么知道的TC,如果选了“Member Referral”的话,需要填写那个人在TC的用户名(欢迎填写sqybi~)
* = required

点Submit,进入Confirm页面,确认信息。如果有误可以点Edit修改,否则点最下面的Confirm提交。
接下来进入Success,提示你已经发送一封邮件到你的邮箱中,你必须去点击里面的链接激活用户。激活之后就可以使用这个用户了。

登录
登录的方法一般都是使用Java Web Start。
在TopCoder主页(
http://www.topcoder.com/tc)最下方有一段话,第一句是“Load the Arena as an Applet or as a Java Web Start Application”。点“Java Web Start Application”,就会自动下载登陆需要的文件(一个jnlp格式的文件,本机装了JRE/JDK才能打开)。经测试在IE7下这个链接似乎不管用,在Firefox 3下正常。
然后运行下载下来的jnlp文件,就打开了TC客户端。第一次运行和有更新的时候会自动下载安装程序,等待即可,很快。
在我这里有时会提示“语法错误”,但没有任何影响,点“确定”就可以。启动可能会慢一些,耐心等待。
然后输入用户名密码,在Connection的地方选合适的登录方式(一般Direct就行,如果不行的话可以试试别的或者用AUTODETECT自动检测),在PROXY处设置代理,点GO登陆。这时可能还会提示语法错误,再确定就行,这个也没有什么影响。

界面
下面的图们来自原文,很经典,不打算改动了。请使用等宽字体浏览。
主界面:
-----------------------------------------------------------------------
|   Advertisements.............                                       |
-----------------------------------------------------------------------
| Main | Lobbies | Options | Practice Rooms | Active Contests | Help ||
-----------------------------------------------------------------------
|                                                 | Clock |           |
-----------------------------------------------------------------------
| Rating Key | Who's here |                 Chat Area                 |
|     .      |            |                                           |
|     .      |            |                                           |
|     .      |            |                                           |
|     .      |            |                                           |
|     .      |            |                                           |
|------------|            |                                           |
|  MESSAGES  |            |                                           |
|------------|            |                                           |
|LEADER BOARD|            |                                           |
|------------|            |                                           |
|            |            |                                           |
|            |            |-------------------------------------------|
|            |            | >>_______________________________________ |
-----------------------------------------------------------------------
Advertisements 广告。
菜单项:
- Main里可以看在线名单和找人。
- Lobbies基本用不着,因为用户一般都在Chat Room 1。
- Options里是一些选项和颜色设置。
- Practice Rooms里有大量的练习,都是以前比赛的题目
- Active Contests只有有比赛的时候才有用,显示当前正在进行和将要进行的比赛以及比赛注册之类的东西。
- Help里是....不用说了吧。
Rating Key: handle的颜色是随着积分而改变的,这里显示了积分与颜色的关系。
MESSAGES: 比赛的时候这里有注册提示和clarification。
LEADER BOARD: 看每个room的最高分。
Who's here: 当前room里的人。
Chat Area: 聊天。

练习
在Practice Rooms里随便选择一个room就可以进入practice了。
界面与主页面稍有变化,但基本相同,略去不画。主要的变化就是Who's here分成了两块,多了一块Who's assigned。这块显示的是谁被分到了这个room。因为是练习区,所以只要是在这里打开过题的都算是assigned。而在正式比赛中room是由TC分配的。这里显示的是被分配到这个room的人。界面上还有一个变化是Chat Area顶上多了三块。最左边的是一个下拉菜单。里面有三个分值,选择后就可以打开相应的题目。中间的summary可以看这个room里每个人的提交情况。
在practice room里只有coding phase。提交后要判的话需要自己选择Practice Options(多出来的菜单项)里的Run System Test。

比赛
每次比赛(除了1年两次的大赛)都需要在赛前3小时-5分钟之间登陆注册方可参加,注册需要在Active Contest菜单中,选择你要参加的那个比赛,再选择Register。注意比赛前5分钟注册停止,这时候如果没有注册就不能参赛了。而注册了没有打开题目也视为没有参赛,rating不变动。
然后TopCoder开始根据每个人的rating分配room,一般每个room都有高手和菜鸟,只不过如果你的rating高,和高手分在一起的概率高一些(当然也不一定是这样,比如我上次就和yuhch大牛分在了一起……)
分配完成后,Active Contest菜单中Register一项变成Enter。选择后可以直接进入你被分配到的room。Active Contest菜单最下面还有一项暗色背景的Room子菜单,可以进入各个room溜达。
进入自己room的时候一般离开始只有3分钟左右,静一下心就可以直接开始比赛了。coding phase的过程与practice基本相同。注意每题的得分是和用的时间相关(见前面的计分规则),而时间是从你打开该题开始算的。所以一题做完后可以不急着打开下一题,先放松一下。
75分钟的coding后是5分钟的intermission,这段时间是用来休息和聊天的。
然后就是最刺激的15分钟challenge phase,也就是通常说的cha人。打开summary,双击别人的各题Score可以打开那题的程序,如果觉得有错误就可以点左下的Challenge然后输入你认为他会错的输入数据,如果输入数据合法那么系统会用标程的输出和这个程序的输出对比,如果出现不同则cha人成功。成功的话你能得到50分,对方该题分数为0;而如果失败了,你会被减去25分。每个程序只能成功被cha一次,也就是说,如果有人cha掉了这个程序,你就不能再次cha。但是一个人可以cha某个程序很多次,直到这个程序被cha掉或者你放弃。
Challenge结束后就是System Test。这个过程一般比较慢,可以先走开做其他事,过20分钟再回来看结果。System Test中的测试数据有两种:一种是出题者准备的测试数据,一种是成功cha掉别人的数据。所以,TC中很少出现有bug的程序能通过System Test的情况。
结果出来后再过一段时间,就可以看到一系列message,比如rating更新了,新的practice room建好了以及可以通过主页查看这次比赛的数据了。这时比赛就宣告结束。

注意事项
1.在TC主页(
http://www.topcoder.com/tc)上可以看到Next SRM,这是下次SRM的时间。注意我们的时间与他们刚好相差12小时,因此若时间是7月9日9:00 PM的话,这里是7月10日9:00 AM。还有要注意的是美国有夏令时,非夏令时的时候,还要再加1小时,就是7月10日10:00 AM。
2.Practice Rooms里写的程序只要点SAVE就可以保存,下次login的时候还可以看到,但是比赛时候的程序必须Submit才可以在coding phase结束后保存(coding phase结束前还是只要SAVE就可以的)。
3.若想cha别人的程序,自己必须是正分(0分也不行),所以若没有一题有正确的程序但有很好的数据的话,随便交一道看上去正确的程序,然后在challenge的时候快下手,就可以赚到了。
4.客户端自带的编辑器只有基本的编辑功能和编译及测试功能,所以若觉得不方便的话可以使用parser和plugin,TC主页最下面有plugin的连接。每个plugin都有详细说明文档,这里不再赘述。
5.TC的FAQ:
http://www.topcoder.com/?&t=support&c=index
6.最后一条,千万不要作弊,会有严重的后果。

SRM的输入输出
SRM是不用标准或文件输入和输出的,只要写一个类的一个成员函数。也就是说,你需要编写的并不是一个完整的程序,而是一个类。
输入是成员函数的参数,输出用return,所以经常需要STL中的vector和string。
因为TC的系统并不测试标准输出,所以标准输出可以当调试用。
下面以SRM 413 Div 2的1000分题目介绍程序的写法。
题目如下(选择不同的语言,题目描述会略有不同,本文以C++为例):

Problem Statement
NOTE: This problem statement contains subscripts that may not display properly if viewed outside of the applet.

Let's consider an infinite sequence A defined as follows:
A0 = 1;
Ai = A[i/p] + A[i/q] for all i >= 1, where [x] denotes the floor function of x. (see Notes)
You will be given n, p and q. Return the n-th element of A (index is 0-based).

Definition
Class:
InfiniteSequence
Method:
calc
Parameters:
long long, int, int
Returns:
long long
Method signature:
long long calc(long long n, int p, int q)
(be sure your method is public)

Notes
- [x] denotes the floor function of x which returns the highest integer less than or equal to x. For example, [3.4] = 3, [0.6] = 0.

Constraints
- n will be between 0 and 10^12, inclusive.
- p and q will both be between 2 and 10^9, inclusive.

Examples
0)
0
2
3
Returns: 1

A[0] = 1.

1)
7
2
3
Returns: 7

A[0] = 1; A[1] = A[0] + A[0] = 2; A[2] = A[1] + A[0] = 2 + 1 = 3; A[3] = A[2] + A[1] = 3 + 2 = 5; A[7] = A[3] + A[2] = 5 + 3 = 8.

2)
10000000
3
3
Returns: 32768

3)
256
2
4
Returns: 89

4)
1
1000000
1000000
Returns: 2

This problem statement is the exclusive and proprietary property of TopCoder, Inc. Any unauthorized use or reproduction of this information without the prior written consent of TopCoder, Inc. is strictly prohibited. (c)2003, TopCoder, Inc. All rights reserved.

下面是我写的一个错误算法的程序,仅供参考格式用:

#include <string>
#include <vector>
class InfiniteSequence {
public:
long long calc(long long n, int p, int q) {
if (n == 0)
return 1;
else
return calc(n/p, p, q) + calc(n/q, p, q);
}
};