COCI 2011/2012 Contest#2(TOJ4484 FUNKCIJA)
2016-03-26 13:55
309 查看
原题链接:TOJ4484
题意:
题目意思很简单,就是根据所给的嵌套循环,和其相关的限制条件,求其循环次数(对1000000007求余)。
这个函数具体框架如下所示:
![](https://img-blog.csdn.net/20160326121356020?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
细节不用太在意,看懂函数功能就行了。
在多重循环中,求最里层的循环一共跑了多少遍。
输入一个N代表循环层数,接下来输入N行,每行两个数字,从上到下依次代表各层的X和Y值,X和Y有可能是1~1000的常数,也有可能是字母,比如循环b的X和Y为a和3,则其循环表达式为for ( int b = a ; b <= 3 ; ++b ),另外输入中保证X和Y中至少存在一个常数。
思路:
直接算的计算量大概在(1000)^N次,时间复杂度优化不可免。
估计是动态规划或者记忆化搜索(虽然说这两者没什么差)。
当然这只是大体思路,具体做还是需要收集一下信息的。
细节:
嵌套循环的计算方法是否能进行优化?
如果是两个常数循环,例如:
那交换循环的顺序,将循环b置于循环a的外层不影响最终循环次数。(类似于3*5和5*3的区别)
且其计算次数时,直接进行相乘即可,不必进入循环逐个计算(一个优化)。
如果两个循环之间存在依赖性的话,例如:
此时,不能将依赖项b置于被依赖项a的外层,否则会导致混乱。
另外,对以下情况:
因为当处于循环a的某一次循环中时,a为某一确定值。那么在a的这次循环中,循环w相当于是一个常数循环,常数循环可和其他循环交换位置。
换种方法来解释,可以认为w是一个"与a相关的常数循环",除了与w有依赖关系的循环,w和其他循环相互不影响。
不过在计算循环次数时,还是采用最原始的方法:a=1,a=2.....多个情况分别计算。
总结为:在循环a的每一次循环中,所有依赖a值的循环均可视为常数循环,可随意交换位置。
同理该结论适用于多层依赖,比如b依赖于a,f依赖于b,d依赖于c,只要保证循环的优先级a>b>f,c>d即可,比如你将循环变为c->a->b->f->d可以,变为c->a->d->b->f也是可以的。
对于上述的例子中,c/d的循环不会对a/b/f的循环产生影响,最终只要计算出c/d的循环次数和a/b/f的循环次数,将其相乘就是c/d/a/b/f五个循环嵌套的总循环次数。
由此可得一种循环计算的优化方法:
无直接或者间接依赖关系的循环之间可直接相乘计算最终结果。
什么叫做无直接或者间接依赖关系?例如下图中:
![](https://img-blog.csdn.net/20160326135637512?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
c和a为直接依赖关系,e和a是间接依赖关系,e和u无依赖关系,c和f也无依赖关系。
无依赖关系的循环之间不会产生影响,直接相乘可以得到两者的总循环结果。
另外,根据这个依赖关系比较容易想到树的结构,我们可以把含有依赖关系的循环做成一棵树,结果可能存在多棵树。树之间相互不影响,只要计算完每颗树的循环总量,然后相乘求余就可以得到最后的结果。
计算过程:
经过上面的分析,我们可以把输入的数据转换为森林模型了。接下来的问题是,如何优化每颗树的计算过程?
对于有依赖关系的循环,比如上面举的例子:
int sum=0;
for(int a=1;a<=3;a++)
{
for(int b=a;b<=5;b++)
{
sum++;
}
}
b的循环限制为a <= b <= 5.
当我们代入a=1时,需要计算b在1~5之间的循环量。
当我们代入a=2时,需要计算b在2~5之间的循环量。
.............
假设二维数组dp中dp [ b ] [ i ]的位置记录了循环b在i~5(i <= 5)之间的循环量,那么我们求dp [ b ] [ i ]的值时,根据求值的顺序,只要在dp [ b ] [ i + 1 ]的基础上加上b = i时的循环量,就可以避免大量重复的计算。
当然b的循环限制也可能是1 <= b <= a,此时dp [ b ] [ i ]代表循环b在1~i(i >= 1)之间的循环量,求dp [ b ] [ i + 1 ]时,在dp [ b ] [ i ]的基础上加上b = i时的循环量即可。
如果b存在两个相关的依赖循环c和d(比如b <= c <= 7,1 <= d <= b),那么b = i时的循环量:b = i时c的循环量,以及b = i时d的循环量,将两者相乘,即为b = i的循环量。其中c和d的循环量一样也可以记录入dp数组,以便之后计算时使用。
根据此方法,最坏的情况下,需要计算dp数组中每一个值,dp[ N ] [ M ](N为循环层数,最大26,M为单次循环的最长次数,最大1000)。
时间复杂度为O(N*M),可接受。
具体代码如下:
所以说,动态规划和记忆化搜索都是差不多的,重点都在于记录搜索中的关键数据。
题意:
题目意思很简单,就是根据所给的嵌套循环,和其相关的限制条件,求其循环次数(对1000000007求余)。
这个函数具体框架如下所示:
细节不用太在意,看懂函数功能就行了。
在多重循环中,求最里层的循环一共跑了多少遍。
输入一个N代表循环层数,接下来输入N行,每行两个数字,从上到下依次代表各层的X和Y值,X和Y有可能是1~1000的常数,也有可能是字母,比如循环b的X和Y为a和3,则其循环表达式为for ( int b = a ; b <= 3 ; ++b ),另外输入中保证X和Y中至少存在一个常数。
思路:
直接算的计算量大概在(1000)^N次,时间复杂度优化不可免。
估计是动态规划或者记忆化搜索(虽然说这两者没什么差)。
当然这只是大体思路,具体做还是需要收集一下信息的。
细节:
嵌套循环的计算方法是否能进行优化?
如果是两个常数循环,例如:
int sum=0; for(int a=1;a<=3;a++) { for(int b=1;b<=5;b++) { sum++; } }
那交换循环的顺序,将循环b置于循环a的外层不影响最终循环次数。(类似于3*5和5*3的区别)
且其计算次数时,直接进行相乘即可,不必进入循环逐个计算(一个优化)。
如果两个循环之间存在依赖性的话,例如:
int sum=0; for(int a=1;a<=3;a++) { for(int b=a;b<=5;b++) { sum++; } }
此时,不能将依赖项b置于被依赖项a的外层,否则会导致混乱。
另外,对以下情况:
int sum=0; for(int a=1;a<=3;a++) { for(int b=a;b<=5;b++) { ..... for(int w=a;w<=3;w++) { sum++; } } }循环w可在循环a以下随意交换位置,包括置于循环b的外层(同理循环b也具备该性质,可随意交换位置)。
因为当处于循环a的某一次循环中时,a为某一确定值。那么在a的这次循环中,循环w相当于是一个常数循环,常数循环可和其他循环交换位置。
换种方法来解释,可以认为w是一个"与a相关的常数循环",除了与w有依赖关系的循环,w和其他循环相互不影响。
不过在计算循环次数时,还是采用最原始的方法:a=1,a=2.....多个情况分别计算。
总结为:在循环a的每一次循环中,所有依赖a值的循环均可视为常数循环,可随意交换位置。
同理该结论适用于多层依赖,比如b依赖于a,f依赖于b,d依赖于c,只要保证循环的优先级a>b>f,c>d即可,比如你将循环变为c->a->b->f->d可以,变为c->a->d->b->f也是可以的。
对于上述的例子中,c/d的循环不会对a/b/f的循环产生影响,最终只要计算出c/d的循环次数和a/b/f的循环次数,将其相乘就是c/d/a/b/f五个循环嵌套的总循环次数。
由此可得一种循环计算的优化方法:
无直接或者间接依赖关系的循环之间可直接相乘计算最终结果。
什么叫做无直接或者间接依赖关系?例如下图中:
c和a为直接依赖关系,e和a是间接依赖关系,e和u无依赖关系,c和f也无依赖关系。
无依赖关系的循环之间不会产生影响,直接相乘可以得到两者的总循环结果。
另外,根据这个依赖关系比较容易想到树的结构,我们可以把含有依赖关系的循环做成一棵树,结果可能存在多棵树。树之间相互不影响,只要计算完每颗树的循环总量,然后相乘求余就可以得到最后的结果。
计算过程:
经过上面的分析,我们可以把输入的数据转换为森林模型了。接下来的问题是,如何优化每颗树的计算过程?
对于有依赖关系的循环,比如上面举的例子:
int sum=0;
for(int a=1;a<=3;a++)
{
for(int b=a;b<=5;b++)
{
sum++;
}
}
b的循环限制为a <= b <= 5.
当我们代入a=1时,需要计算b在1~5之间的循环量。
当我们代入a=2时,需要计算b在2~5之间的循环量。
.............
假设二维数组dp中dp [ b ] [ i ]的位置记录了循环b在i~5(i <= 5)之间的循环量,那么我们求dp [ b ] [ i ]的值时,根据求值的顺序,只要在dp [ b ] [ i + 1 ]的基础上加上b = i时的循环量,就可以避免大量重复的计算。
当然b的循环限制也可能是1 <= b <= a,此时dp [ b ] [ i ]代表循环b在1~i(i >= 1)之间的循环量,求dp [ b ] [ i + 1 ]时,在dp [ b ] [ i ]的基础上加上b = i时的循环量即可。
如果b存在两个相关的依赖循环c和d(比如b <= c <= 7,1 <= d <= b),那么b = i时的循环量:b = i时c的循环量,以及b = i时d的循环量,将两者相乘,即为b = i的循环量。其中c和d的循环量一样也可以记录入dp数组,以便之后计算时使用。
根据此方法,最坏的情况下,需要计算dp数组中每一个值,dp[ N ] [ M ](N为循环层数,最大26,M为单次循环的最长次数,最大1000)。
时间复杂度为O(N*M),可接受。
具体代码如下:
#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*D 4000 FS(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(); }
所以说,动态规划和记忆化搜索都是差不多的,重点都在于记录搜索中的关键数据。
相关文章推荐
- 详解Android应用中屏幕尺寸的获取及dp和px值的转换
- 基于Android中dp和px之间进行转换的实现代码
- Android中dip、dp、sp、pt和px的区别详解
- LFC1.0.0 版本发布
- Android px、dp、sp之间相互转换
- HP data protector软件学习1--基本角色与基本工作流程
- HP data protector软件学习2--软件组成与界面介绍
- android中像素单位dp、px、pt、sp的比较
- Android对px和dip进行尺寸转换的方法
- Android根据分辨率进行单位转换-(dp,sp转像素px)
- android 尺寸 dp,sp,px,dip,pt详解
- DP问题各种模型的状态转移方程
- POJ-1695-Magazine Delivery-dp
- nyoj-1216-整理图书-dp
- TYVJ1193 括号序列解题报告
- 对DP的一点感想
- TYVJ上一些DP的解题报告
- soj1005. Roll Playing Games
- 01背包问题
- LeetCode之Maximum Product Subarray