IDAstar

I D A ∗ IDA^* IDA

前置芝士:迭代加深

有这样一类题,每次我们可以对对象进行某些操作,求将对象变成目标所需最小操作数.

我们可以考虑深度优先搜索和广度优先搜索.

深度优先搜索的策略是每次在搜索树上选定一个分支,然后从该分支逐渐深入往下扩展,直至搜索至目标答案或边界时回溯.这种无差别式的搜索策略具有一定的缺陷:假若我们的目标在搜索树另一分支上较浅的层次,但我们却沿着选择的错误分支深入往下扩展,毋庸置疑会增加许多搜索量.

为解决此问题,我们可以考虑对搜索进行改造。

仍然按照原策略在搜索树上进行扩展,但是进行多次搜索.每次限定一个搜索层数,达到限定层时无论是否找到答案都不再继续扩展。若未找到目标,则将限定层数增加 1 1 1然后重新搜索.

用这种方法虽然看似具有许多重复计算量,但跟原先埋头往下搜,搜至深层次时冗余的无效计算,可以算是小巫见大巫了.

例题见下方埃及分数.

I D A ∗ IDA^* IDA

在迭代加深的基础上,我们引入估价函数 f ( x ) f(x) f(x)(估价函数了解详见 A*算法浅谈).仍然满足 f ( x ) ≤ g ( x ) f(x)\leq g(x) f(x)g(x),其中 g ( x ) g(x) g(x)代表从当前状态 x x x到达目标状态所需的实际最小代价.若当前迭代剩余层数为 n o w now now,则若 f ( x ) > n o w f(x)>now f(x)>now,必有 g ( x ) ≥ f ( x ) > n o w g(x)\geq f(x)>now g(x)f(x)>now,故 x x x必定不可能在本次迭代中搜索至答案,因此可以提前排除.

同样的, I D A ∗ IDA^* IDA算法的难点在于估价函数的设计,我们进一步在题目中感受.

P1763 埃及分数

题意: 详见链接

思路: 考虑到最终答案需要满足分解个数最小,因此可以用迭代加深.

由于选取的单位分数不能相同,因此我们可以按单位分数从大到小,即分母从小到大开始枚举.

考虑剪枝减少搜索量,在每一层枚举中,若剩余分数为 d c \frac{d}{c} cd,上次枚举分母选取 p r e pre pre,那么这一层枚举起点就是 p r e + 1 pre+1 pre+1,枚举终点设为 p p p,迭代剩余层数为 l e n len len.若本层选取了 p p p,那么本层及接下来所有层次的单位分数之和必定小于 m x = l e n p mx=\frac{len}{p} mx=plen,若 m x < = d c mx<=\frac{d}{c} mx<=cd,那即使接下来全部选取最大单位分数也无法凑齐 d c \frac{d}{c} cd,于是有 m x = l e n p > d c mx=\frac{len}{p}>\frac{d}{c} mx=plen>cd,亦即 p < ⌈ l e n ⋅ c d ⌉ p<\lceil \frac{len\cdot c}{d}\rceil p<dlenc,以此作为枚举终点 e d ed ed即可.

code


const ll N=100000;

ll a, b;
ll _stack[N], _t;
ll ans[N];
bool flag;
ll goal;

inline void dfs(ll C, ll D, ll pre, ll now) {
	if (C<0 || D<0) return;
	ll gcd=__gcd(C, D);
	C/=gcd, D/=gcd;
	if (now==goal) {
		if (C!=1) return;
		if (D==_stack[_t]) return;
		if (flag && D>=ans[goal]) return;
		for (R ll i=1; i<goal; i++) ans[i]=_stack[i];
		ans[goal]=D;
		flag=true;
		return;
	}
	ll end=ceil((double)D*(goal-now+1)/C);
	for (R ll i=pre+1; i<=end; i++) {
		_stack[++_t]=i;
		dfs(C*i-D, D*i, i, now+1);
		--_t;
	}
}

int main() {
	read(a); read(b);
	if (a==1) {
		writeln(b);
		return 0;
	}
	while (!flag) {
		++goal;
		dfs(a, b, 0, 1);
	}
	for (R ll i=1; i<=goal; i++) writesp(ans[i]); putchar('\n');
}

P2534 [AHOI2012]铁盘整理

题意:

n ( n ≤ 16 ) n(n\leq 16) n(n16)个铁盘从上到下叠放,大小各不相同。我们每次可以选取最上面若干个铁盘进行翻转(如 1 , 2 , 3 , 4 1,2,3,4 1,2,3,4翻转为 4 , 3 , 2 , 1 4,3,2,1 4,3,2,1).问最少翻转几次可以实现铁盘从上到下按从小到大排列.

思路:

首先进行离散化,因此最终需要序列满足 n u m [ i ] = i num[i]=i num[i]=i

观察最终序列,我们考虑每相邻两层间的关系,满足 n u m [ i + 1 ] − n u m [ i ] = i + 1 − i = 1 num[i+1]-num[i]=i+1-i=1 num[i+1]num[i]=i+1i=1

我们思考每次操作对序列带来的影响。若选取第 k k k层,实际上对 1   t o   k 1 \ to\ k 1 to k的影响仅是翻转,相邻差不变,但却改变了 k + 1 k+1 k+1 k k k间的差值。因此可以得出一个结论,每次操作至多改变一个差值. 然后目标状态为 n u m [ i + 1 ] − n u m [ i ] = 1 num[i+1]-num[i]=1 num[i+1]num[i]=1,因此我们可以统计当前 a b s ( n u m [ i + 1 ] − n u m [ i ] ) ≠ 1 abs(num[i+1]-num[i])\not=1 abs(num[i+1]num[i])=1 i i i个数,作为状态估价函数 f ( x ) f(x) f(x).

然后进行剪枝,显然如果两次操作选择同一位置是无意义的,因此记录前驱 p r e pre pre排雷。

code

const ll N=17;

ll n;
ll num[N], rem[N];
const ll f() {
	ll sum=0;
	for (R ll i=1; i<n; i++) {
		sum+=(abs(num[i]-num[i+1])!=1);
	}
	return sum;
}

bool flag;

inline void dfs(ll x, ll pre) {
	if (x==0) {
		for (R ll i=1; i<=n; i++) {
			if (num[i]!=i) return;
		}
		flag=true;
		return;
	}
	if (flag) return;
	ll end=n;
	while (num[end]==end) --end;
	for (R ll i=2; i<=end; i++) {
		if (i==pre) continue;
		ll l=1, r=i;
		while (l<r) {
			swap(num[l], num[r]);
			++l; --r;
		}
		if (f()<=x) dfs(x-1, i);
		l=1, r=i;
		while (l<r) {
			swap(num[l], num[r]);
			++l; --r;
		}
	}
}

int main () {
	read(n);
	for (R ll i=1; i<=n; i++) read(num[i]), rem[i]=num[i];
	sort(rem+1, rem+n+1);
	for (R ll i=1; i<=n; i++) {
		num[i]=lower_bound(rem+1, rem+n+1, num[i])-rem;
	}
	ll goal=0;
	while (!flag) {
		++goal;
		dfs(goal, 0);
	}
	writeln(goal);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值