【ARC112F】Die Siedler(根号分治)(bfs)

Die Siedler

题目链接:ARC112F

题目大意

有 n 种牌,2i 个第 i 种牌可以变成一个第 i+1 种牌。
特别的 2n 个第 n 种牌会变成一个第 1 种牌。
然后有 m 种牌包,里面每种牌都有一定的数量,然后牌包数量无限,随便你用。
然后告诉你你一开始有的牌,你可以随意用牌随便变牌,问你手上多少会有多少牌。

思路

考虑到我们肯定是能换就换,因为一定会让牌数变少。
那我们考虑全部不换,甚至退回去,我们就看全部退回成第一种牌,有多少张。

对原来有的牌转化得到数量 R R R,每个卡包也转化得到 b i b_i bi
那首先会发现一个特别的就是从 n n n 转化到 1 1 1,发现少了 2 n n ! − 1 2^nn!-1 2nn!1
那最后牌数我们可以表示成这个:
v = R + ∑ i = 1 m b i x i − y ( 2 n n ! − 1 ) v=R+\sum\limits_{i=1}^mb_ix_i-y(2^nn!-1) v=R+i=1mbixiy(2nn!1)
其中 x i , y x_i,y xi,y 是让你选的。

v − R = ∑ i = 1 m b i x i − y ( 2 n n ! − 1 ) v-R=\sum\limits_{i=1}^mb_ix_i-y(2^nn!-1) vR=i=1mbixiy(2nn!1)
然后通过斐蜀定理,我们有 v − R v-R vR 得是 gcd ⁡ ( b 1 , b 2 , . . . , b m , 2 n n ! − 1 ) \gcd(b_1,b_2,...,b_m,2^nn!-1) gcd(b1,b2,...,bm,2nn!1) 的倍数。

那假设上面 gcd ⁡ \gcd gcd 的结果是 d d d
考虑根号分治:

d > 2 n n ! d>\sqrt{2^nn!} d>2nn!
那这个时候我们可以试着暴力枚举倍数,然后直接转换会序列,算出答案。

d ⩽ 2 n n ! d\leqslant\sqrt{2^nn!} d2nn!
这个时候我们可以试着用 f i f_i fi 表示价值为 i i i 的最小换成的牌数。
然后转移是 f ( i + 2 k − 1 ( k − 1 ) ! )   m o d   d = min ⁡ ( f ( i + 2 k − 1 ( k − 1 ) ! )   m o d   d , f i + 1 ) f_{(i+2^{k-1}(k-1)!)\bmod d}=\min(f_{(i+2^{k-1}(k-1)!)\bmod d},f_i+1) f(i+2k1(k1)!)modd=min(f(i+2k1(k1)!)modd,fi+1)
然后初始化是 f 2 k − 1 ( k − 1 ) !   m o d   d = 1 f_{2^{k-1}(k-1)!\bmod d}=1 f2k1(k1)!modd=1,这个因为每次都是 + 1 +1 +1 我们可以 bfs 出结果,然后要的就是 f R   m o d   d f_{R\bmod d} fRmodd

时间复杂度都是:
O ( 2 n n ! n ) O(\sqrt{2^nn!}n) O(2nn! n)

发现好像复杂度是 1 e 9 1e9 1e9 级别的。

但是呢,你发现 2 n n ! − 1 2^nn!-1 2nn!1 这个数,它的质因子,比较的有特点。
(因为这个数是固定有的,而且只有 16 16 16 种情况)

看质因子可以点我

发现要么很大(这个大是贴近 2 n n ! 2^nn! 2nn! 不是 2 16 16 ! 2^{16}16! 21616!),要么很小。
会发现最大的 d d d 大概在 1 e 6 1e6 1e6 的级别上,所以是可以过的。

代码

#include<cmath>
#include<queue>
#include<cstdio>
#include<cstring>
#define ll long long

using namespace std;

const int N = 17;
const int M = 51;
const int B = 5e6 + 1000;
int n, m, a[N], ans, f[B];
ll R, b[M], n2n, GCD, jc[N];
bool in[B];
queue <int> q;

ll change() {
	ll re = 0, sum = 1;
	for (int i = 1; i <= n; i++) {
		re += (1ll << i - 1) * jc[i - 1] * a[i];
	}
	return re;
}

int back(ll x) {
	int re = 0;
	for (int i = 1; i <= n; i++) {
		re += x % (2 * i); x /= 2 * i;
	}
	return re;
}

ll gcd(ll x, ll y) {
	if (!y) return x;
	return gcd(y, x % y);
}

void bfs() {
	while (!q.empty()) {
		int now = q.front(); q.pop();
		for (int i = 1; i <= n; i++) {
			int to = (now + (1ll << i - 1) * jc[i - 1]) % GCD;
			if (f[to] > f[now] + 1) {
				f[to] = f[now] + 1;
				if (!in[to]) {
					in[to] = 1; q.push(to);
				}
			}
		}
	}
}

int main() {
	scanf("%d %d", &n, &m);
	jc[0] = 1; for (int i = 1; i <= n; i++) jc[i] = jc[i - 1] * i;
	for (int i = 1; i <= n; i++) scanf("%d", &a[i]); R = change();
	for (int i = 1; i <= m; i++) {
		for (int j = 1; j <= n; j++) scanf("%d", &a[j]); b[i] = change();
	}
	
	if (!R) {printf("0"); return 0;}
	
	n2n = (1ll << n) * jc[n];
	GCD = n2n - 1; for (int i = 1; i <= m; i++) GCD = gcd(GCD, b[i]);
	
	ans = 2e9;
	if (GCD <= sqrt(n2n)) {
		memset(f, 0x7f, sizeof(f));
		for (int i = 1; i <= n; i++) {
			int k = (1ll << i - 1) * jc[i - 1] % GCD;
			f[k] = 1; in[k] = 1; q.push(k);
		}
		bfs();
		ans = f[R % GCD];
	}
	else {
		for (ll val = R % GCD; val < n2n; val += GCD) {
			if (!val) continue;
			ans = min(ans, back(val));
		}
	}
	printf("%d\n", ans);
	
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值