COCI 2011/2012 Contest#2(TOJ4484 FUNKCIJA)

原题链接:TOJ4484

题意:

题目意思很简单,就是根据所给的嵌套循环,和其相关的限制条件,求其循环次数(对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*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();
}

所以说,动态规划和记忆化搜索都是差不多的,重点都在于记录搜索中的关键数据。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值