[AGC022C] Remainder Game [贪心][dp/搜索/floyd]

[ L i n k \frak{Link} Link]


给出序列ab
一次操作可以选择一个k,把当前序列中的某些数模k
求把a变为b的最少操作次数。
1≤n≤50; 0≤a,b≤50.


(想要直接特判的话)无解: 2 b i + 1 > a i      a n d      b i ≠ a i \frak{2b_i+1>a_i\;\;and\;\;b_i\ne a_i} 2bi+1>aiandbi̸=ai

数据范围允许复杂度达到 Θ ( n 4 ) \frak{\Theta(n^4)} Θ(n4)
考虑暴力。

题目的要求相当于转移使初状态变为末状态。可以通过mod 1~50来转移。
由于mod运算的性质,取模顺序不影响结果。可以构造一个n x n的矩阵来转移。
预处理1~50的每个数mod 1~50的结果填进表格里面。
于是可以用dijkstra跑单源最短路。转移一次的时间复杂度为 Θ ( n ! ) \frak{\Theta(n!)} Θ(n!)
状态数大约n!,每个点有n条边。
总的时间复杂度大约是 Θ (    ( n ! ) 2 n l o g ( n ! )    ) \frak{\Theta(\;(n!)^2nlog(n!)\;)} Θ((n!)2nlog(n!))
总之这肯定承受不来。


重新考虑。之前的算法复杂度爆炸的重要原因就是状态过多,其中很多状态无效。
事实上不应该整个序列放一起考虑,这样是不优的。
因为这样要考虑每次要操作哪些数,而且数放一起转移就变成了不同的数都影响整体、转移会被大大拖累。

注意到数转移的过程是独立的。尝试考虑局部
可以这么转化:对于a序列里面的每一个数,它能否转移到b序列里面对应的数?
这样的话重复状态会大大减少。

所以可以求出所有能让 a i → b i \frak{a_i\to b_i} aibi可行的操作方案,并且要在里面找到最小代价。
当然操作方案也不能整体来, 2 n \frak{2^n} 2n的复杂度也不是讲着玩的
怎么独立考虑?
⋮ \vdots
好像也不好独立考虑然后一步到位,能不能拆成两部分呢?
先判可行再判最小?



考虑“可行”和“最小”。
 可行:用不大于k的数能否让a转移到b可行
 最小:用不大于k的数让a转移到b的最小代价? 
首先对于每个k,floyd判连通。 Θ ( n 4 ) \frak{\Theta(n^4)} Θ(n4)
最小怎么判断?很容易看出不是二分就是dp。不过好像二分不了,那就用dp吧。
 如果设 f ( i , j , k ) \frak{f(i,j,k)} f(i,j,k)表示对于数i,前j个数让它剩下k的最小代价?
 如果 f ( i , j , k ) \frak{f(i,j,k)} f(i,j,k)不是inf,向后转移:
 初始化 f ( i , j + 1 , k ) = f ( i , j , k ) \frak{f(i,j+1,k)=f(i,j,k)} f(i,j+1,k)=f(i,j,k)。(如果前面j-1个可以,实际上没必要选j,选j代价一定只会变大。不过想搞个取min也随意啦)
 更新: f ( i , j + 1 , k % ( j + 1 ) ) = m i n { f ( i , j + 1 , k % ( j + 1 ) ) , f ( i , j , k ) + 2 j } \frak{f(i,j+1,k\%(j+1))=min\{f(i,j+1,k\%(j+1)),f(i,j,k)+2^j\}} f(i,j+1,k%(j+1))=min{f(i,j+1,k%(j+1)),f(i,j,k)+2j}
 
但是如果 f ( i , j + 1 , k % ( j + 1 ) \frak{f(i,j+1,k\%(j+1)} f(i,j+1,k%(j+1)已经可行的话好像也没有必要转移?因为 2 j > ∑ i = 1 j 2 i 2^j>\sum\limits_{i=1}^j2^i 2j>i=1j2i
那好像可以改一改定义了。而且方程里面i也一直没有动过。
 枚举不同的 a i \frak{a_i} ai
 设 f ( k , j ) \frak{f(k,j)} f(k,j)表示前k个数能不能让 a i \frak{a_i} ai剩下j。
 直接转移可行性。
 
咦这好像跟floyd没关系了啊

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstdlib>
#include<cstring>
#include<cctype>
#include<ctime>
#include<cmath>
using namespace std;
int N;
int A[55]={};
int B[55]={};
int R[55]={};
long long E[55]={};
bool F[55][55]={};
long long ans=0;
bool check(int x)
{
	for(int i=1;i<=N;++i)
	{
		memset(F,0,sizeof(F));
		F[1][A[i]]=1;
		for(int j=1;j<=R[0];++j)
		{
			for(int k=0;k<=50;++k)
			{
				if(F[j][k]) 
				{
					F[j+1][k]=1;
					F[j+1][k%R[j]]=1;
				}
			}
		}
		if(!F[R[0]+1][B[i]])return 0;
	}
	return 1;
}
int main()
{
	scanf("%d",&N);
	for(int i=1;i<=N;++i)
	{
		scanf("%d",&A[i]);
	}
	for(int i=1;i<=N;++i)
	{
		scanf("%d",&B[i]);
		if(B[i]+B[i]+1>A[i]&&B[i]!=A[i])
		{
			printf("-1");
			return 0;
		}
	}
	for(int i=50;i>=1;--i)
	{
		R[0]=0;
		for(int j=1;j<=E[0];++j)R[++R[0]]=E[j];
		for(int j=i-1;j>=1;--j)R[++R[0]]=j;
		if(!check(i))E[++E[0]]=1ll*i;
	}
	for(int i=1;i<=E[0];++i)
	{
		ans+=1ll<<E[i];
	}
	printf("%lld",ans);
	return 0;
}

另一种方式更简单,建图,从大到小每次删掉一个模数判可行,如果从可行变成不可行了就加回去,如果还是可行就去掉。
判可行可以搜索。 上面提到的floyd也可以。
代码不写了(


实际上不用想得这么复杂。最基础的部分推出来然后可以直接猜状态:
对于某个数,已经从大到小考虑过前几种方案,能不能让余数是某个数。就完了。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值