COCI 2011/2012 Contest#2(TOJ4484 FUNKCIJA)

来源:互联网 发布:做班服的软件手机 编辑:程序博客网 时间:2024/06/14 15:08

题目的原意很简单,就是给予你一个函数框架,然后给出不同的限制条件,求这个函数的运行结果。

这个函数具体框架如下所示:


左边用c语言表示,右边用的.....估计是Pascal?

细节不用太在意。。看懂函数功能就行了。

大概意思就是多重循环,然后问你最里层的循环一共跑了多少遍。

因为for循环中用的变量是按层数顺序依次使用a~z,所以一共最多26层循环,每层循环Xn<=<N-th><=Yn,这里的Xn和Yn都是后面的输入给的,可能是之前的变量,也可能是常数,例如 for ( int b = a ; b <= 3 ; ++b )或者for ( int b = 2 ; b <= 3; ++b) 这样的。不过Xn和Yn中至少有一个是常数。

思路:

这种结果要对1000000007求余的纯暴力想都不用想。

估计就是两种大众方法:

1.减枝优化(或者类似于脑筋急转弯啥的)。

2.动态规划(毕竟动态规划用作这个比较常见。。)。

当然这只是大体思路,具体做还是需要收集一下信息的。

嵌套循环的位置是不是能换:

如果Xn和Yn只是两个常量

int sum=0;for(int a=1;a<=3;a++){for(int b=2;b<=5;b++){sum++;}}

那就算换成

int sum=0;for(int b=2;b<=5;b++){for(int a=1;a<=3;a++){sum++;}}
这样也没影响。毕竟只是3*4和4*3的差别。

如果两个循环相干的话,那就不能交换他们顺序了,

例如:

int sum=0;for(int a=1;a<=3;a++){for(int b=a;b<=5;b++){sum++;}}
毕竟b还是依赖于a的,要是跑到a上面....这没有初始化的问题看着就捉急。

那么也就是说,

int sum=0;for(int a=1;a<=3;a++){for(int b=a;b<=5;b++){for(int c=b;c<=2;c++){sum++;}}}
我们设定b和a之间是直接依赖关系,c和a之间是间接依赖关系的话,就有:

1>没有直接或者间接关系 的循环之间交换是不影响结果的。

2>有直接或者间接关系的循环之间一定要保持依赖关系顺序,不能换到父层上方。

因为每层循环至多有一个依赖关系,即每层循环至多只有一个父亲结点(上层)。

根据这个依赖关系比较容易想到树的结构,我们把含有依赖关系的循环做成一棵树。树之间相互不影响,只要计算完每颗树的循环总量,然后相乘求余就可以得到最后的结果。

那么问题就是如何求每颗树的循环总量了。

对于这个问题,想了蛮久的方法。例如对于一个结点,将其所有子结点缩成一个,以方便把树模型再转换成线模型,那就简单多了。。。

然而这种方法最后验证并不能行。


好吧,那就用动态规划试试看。

因为树的结构是固定不变的,不会出现a的子层突然改变这种情况。

然后最关键的部分是,在循环中存在一种线性关系:

假设c的循环限制为a<=c<=10.

当我们代入a=1时,需要计算c在1~10之间的循环量。

当我们代入a=2时,需要计算c在2~10之间的循环量。

.............

也就是说如果有数组dp [ c ] [ i ]代表c结点在i~Y3(Y3代表c的循环限制常量)之间的循环量。

那么我们求dp [ c ] [ i + 1 ]的值只要在dp [ c ] [ i ]的基础加上c=i-1时子层的循环量即可。

当然c的循环限制也可能是10<=c<=a.

这样的话,如同上面的解释,也是存在线性关系的,只不过方向相反而已。

于是乎,我们只要进行DFS,然后把路程中关键的值记录进dp数组,以便之后查询时使用即可。

而对于同一个结点的多个子节点该如何处理呢?

当我们确定当前结点的值时(就是使用其某个值计算子层的循环量),其所有子节点都已经变成常量循环了。例如上图的树中,假设3<=a<=12,我们只要把a=3,a=4,a=5,a=6....a=12时子层的循环量都计算出来,然后相加,就得到结果了。而在这个计算过程中计算某个量时,比如上图树中a=4时的值,c和f的循环限制就都变成常数了,对于这些互不相干的常数循环之间的合计方法,直接相乘就行了。

具体代码如下:

#include <stdio.h>#include <string.h>typedef __int64 LL;const int Size = 27;const int MAXM = 1100;const int Mod = 1000000007;int Num;int Total;int End[Size];int Last[Size];int To[Size];int Kind[Size];int Left[Size];int Right[Size];LL State[Size][MAXM];int Ex(char *use){int sum=0;for(int i=0;use[i];i++){sum=sum*10+use[i]-'0';}return sum;}void Add(int u,int v){To[Total]=v;Last[Total]=End[u];End[u]=Total++;}void Init(){for(int i=0;i<Num;i++){End[i]=-1;}memset(State,-1,sizeof(State));Total=0;}void Read_Case(){scanf("%d",&Num);Init();char u[Size],v[Size];for(int i=0;i<Num;i++){scanf("%s %s",u,v);Kind[i]=-1;if(*u>='0'&&*u<='9'){Left[i]=Ex(u);}else{Kind[i]=0;Left[i]=*u-'a';Add(Left[i],i);}if(*v>='0'&&*v<='9'){Right[i]=Ex(v);}else{Kind[i]=1;Right[i]=*v-'a';Add(Right[i],i);}}}inline LL Get_Child(int now,int limit);LL DFS(int now,int limit){if(~State[now][limit])return State[now][limit];int last=limit;if(!Kind[now]){while(last<=Right[now]&&State[now][last]==-1)last++;if(last>Right[now])State[now][last]=0;last--;while(last>=limit){State[now][last]=(State[now][last+1]+Get_Child(now,last))%Mod;last--;}}else if(Kind[now]==1){while(last>=Left[now]&&State[now][last]==-1)last--;if(last<Left[now])State[now][last]=0;last++;while(last<=limit){State[now][last]=(State[now][last-1]+Get_Child(now,last))%Mod;last++;}}else{State[now][limit]=0;for(int i=Left[now];i<=Right[now];i++){State[now][i]=Get_Child(now,i);State[now][limit]=(State[now][limit]+State[now][i])%Mod;}}return State[now][limit];}inline LL Get_Child(int now,int limit){LL ans=1;int pos=End[now],G;while(~pos){G=To[pos];ans=ans*DFS(G,limit)%Mod;pos=Last[pos];}return ans;}void Deal(){LL Ans=1;for(int i=0;i<Num;i++){if(Kind[i]==-1){Ans=Ans*DFS(i,0)%Mod;}}printf("%I64d\n",Ans);}int main(){Read_Case();Deal();}


说是动态规划,其实更贴近记忆化搜索,但这两者本来就是同一个东西。。所以就不要在意这些细节了。



0 0