题目大意

$m_i=\underbrace{1\cdots1}_{i\text{ times}}$ ,即十进制下 $i$ 个连续的 $1$

现在给定一个正整数 $n$ ,找到一个无限长的整数序列 $(x_1,x_2,\cdots)$ 满足:

  1. $\sum_{i=1}^\infty m_ix_i=n$
  2. 最小化 $\sum_{i=1}^\infty i|x_i|$

$n\leq10^{50}.$

思路

本题是 CCPC 2020 Final Number Theory 弱化版。加强版的范围 $n\leq10^{5000}$ 。因为我的优化过不了加强版,而且我还为此花费了两天时间,所以我要为这道题送上我最儒雅赞叹。本篇以 $|n|$ 表示 $n$ 的长度。

$f_{y,x}$ 表示加减所有 $m_i(i>y)$ 剩下 $x$ 的最小代价。

对于 $f_{y,x}$ 中的每一个 $x$ ,在转移至下一位中至多有两种选择:假设 $x$ 为非负的,这两种选择为 $x-km_y$ $x-(k+1)m_y$ ,其中 $x-km_y$ 非负, $x-(k+1)m_y$ 非正。否则 $|x-km_y|>m_y$ 总可以减掉(加上)一个 $m_y$ 使得代价更小。

可以证明从 $f_y$ 转移到 $f_{y-1}$ 最多创造 $\mathcal{O}(1)$ 个多出来的 $x$ ,则共有 $\mathcal{O}(|n|)$ 个不同的 $x$ 。这样可以得到 $\mathcal{O}(|n|^3\log|n|)$ 的复杂度。

考虑优化掉高精度运算的 $\mathcal{O}(|n|)$ :把数字都乘上 $9$ ,则 $m_i=\underbrace{9\cdots9}_{i\text{ times}}=1\underbrace{0\cdots0}_{i\text{ times}}-1$ 。那么每次高精度运算只用加减两位,修改位数是均摊 $\mathcal{O}(1)$ 的。

直接这么做还有一个问题:由于 $x-km_y<m_y$ ,则计算 $x-(k+1)m_y$ 就不是 $\mathcal{O}(1)$ 的了。

发现 $m_i\bmod m_{i-1} = 1$ ,可以以此引出一条性质:在 $i > 2$ 层中,由 $x$ 以正负划分可以组成两组连续的数列(如果不乘 $9$ ),乘上 $9$ 则是两个公差为 $9$ 的等差数列。如下(未乘 $9$ ):

           3453                 i=1
   120            -991          i=2
9      -102     8     -103      i=3
9 -2   -3  8   8 -3   -4 7      i=4

那么我们随便取一个数 $x$ $\mathcal{O}(|n|)$ $m_y-x$ ,然后用哈希存储。

这样我们消掉了高精度运算的 $\mathcal{O}(|n|)$ ,总时间复杂度是 $\mathcal{O}(|n|^2\log|n|)$ ……吗?并不。注意到 map $\log|n|$ 以外,比较两数大小也需要 $|n|$ ,因此时间复杂度还是 $\mathcal{O}(|n|^3\log|n|)$ 。似乎用 pb_ds 也只能简单消掉一个 $\log|n|$ 。接下来的超出我的能力了……

我的代码

const int N = 5010;

inline ll Read() {
	ll x = 0, f = 1;
	char c = getchar();
	while (c != '-' && (c < '0' || c > '9')) c = getchar();
	if (c == '-') f = -f, c = getchar();
	while (c >= '0' && c <= '9') x = (x << 3) + (x << 1) + c - '0', c = getchar();
	return x * f;
}

namespace Main {
	char s[N];
	int n;

	struct Number {
		public:
		vector<int> v;

		Number() {}
		Number(int x) {
			for (; x--; v.push_back(0));
			v.push_back(1);
		}

		void CharToNumber(char *s) {
			int len = strlen(s);
			for (int i = 0; i < len; i++)
				v.push_back(s[i] - '0');
			reverse(v.begin(), v.end());
		}
		
		int& operator[](int x) { return v[x]; }
		int length () { return v.size(); }
	};

	bool operator< (Number a, Number b) {
		if (a.length() != b.length()) return a.length() < b.length();
		for (int i = a.length() - 1; i >= 0; i--)
			if (a[i] != b[i]) return a[i] < b[i];
		return 0;
	}
	Number operator+ (Number a, Number b) {
		if (b < a) swap(a, b);
		for (int i = 0; i < a.length() || (i < b.length() && b[i] > 9); i++) {
			if (i < a.length()) b[i] += a[i];
			if (b[i] > 9) {
				if (i + 1 < b.length()) b[i + 1] += b[i] / 10;
				else b.v.push_back(b[i] / 10);
				b[i] %= 10;
			}
		}
		return b;
	}
	Number operator- (Number a,Number b) {
		if (b < a) swap(a, b);
		for (int i = 0; i < b.length(); i++) {
			if (i < a.length()) b[i] -= a[i];
			if (b[i] < 0) {
				b[i] += 10;
				b[i + 1]--;
			}
		}
		for (int i = b.length() - 1; i >= 1; i--)
			if (b[i] == 0) b.v.resize(i);
			else break;
		return b;
	}
	Number PlusInt (Number a, int x = 1) {
		a[0] += x;
		for (int j = 0; a[j] >= 10; j++) {
			if (j + 1 >= a.length()) a.v.push_back(a[j] / 10);
			else a[j + 1] += a[j] / 10;
			a[j] %= 10; 
		}
		return a;
	}
	Number MinusBit (Number a, int x, int val = 1) {
		a[x] -= val;
		for (int j = x; a[j] < 0; j++) {
			a[j] += 10, a[j + 1]--;
		}
		for (int i = a.length() - 1; i >= 1; i--)
			if (a[i] == 0) a.v.resize(i);
			else break;
		return a;
	}

	map<Number, int> f[N];

	int main () {
		for (int t = 1; t--; ) {
			for (int i = 0; i <= 5000 + 1; i++) f[i].clear();
			scanf ("%s", s);
			Number n, m;
			n.CharToNumber(s);
			m = n;
			for (int i = 1; i <= 8; i++) n = n + m;
			f[n.length() + 1][n] = 0;
			for (int i = n.length() + 1; i; i--) {
				map<Number, Number> hash;
				auto fir = *f[i].begin();
				Number key = fir.first;
				Number antiKey;
				if (key.length() == 0) key.v.push_back(0);
				while (PlusInt(key).length() >= i + 1) {
					key = PlusInt(key);
					key = MinusBit(key, i);
				}
			
				antiKey = Number(i) - PlusInt(key);
			
				if (!(key.length() == 1 && key[0] == 0)) {
					key = MinusBit(key, 0, 9);
					antiKey = PlusInt(antiKey, 9);
				}
				hash[key] = antiKey;
				hash[antiKey] = key;

				Number tmp1 = key, tmp2 = antiKey;
				for (int j = 0; j <= f[i].size() / 2 && !(antiKey.length() == 1 && antiKey[0] == 0); j++) {
					key = PlusInt(key, 9);
					antiKey = MinusBit(antiKey, 0, 9);
					if (hash.find(key) != hash.end()) break;
					hash[key] = antiKey;
					hash[antiKey] = key;
				}
				key = tmp1, antiKey = tmp2;
				for (int j = 0; j <= f[i].size() / 2 && !(key.length() == 1 && key[0] == 0); j++) {
					antiKey = PlusInt(antiKey, 9);
					key = MinusBit(key, 0, 9);
					if (hash.find(key) != hash.end()) break;
					hash[key] = antiKey;
					hash[antiKey] = key;
				}

				for (auto u: f[i]) {
					Number tmp = u.first;
					int val = u.second;
					while (PlusInt(tmp).length() >= i + 1) {
						tmp = PlusInt(tmp);
						tmp = MinusBit(tmp, i);
						val += i;
					}
					if (f[i - 1].find(tmp) == f[i - 1].end()) f[i - 1][tmp] = val;
					else f[i - 1][tmp] = min(f[i - 1][tmp], val);
					tmp = hash[tmp];
					if (f[i - 1].find(tmp) == f[i - 1].end()) f[i - 1][tmp] = val + i;
					else f[i - 1][tmp] = min(f[i - 1][tmp], val + i);
				}
			}
			Number Zero;
			Zero.CharToNumber((char*)"0");
			printf ("%d\n", f[0][Zero]);
		}
		return 0;
	}
}

int main () {
//	freopen("ones.in", "r", stdin);
//	freopen("ones.out", "w", stdout);
	Main::main();
	return 0;
}

加强版满分代码

MoQZ 莫队的代码

EOF

评论

暂无评论

发表评论

可以用@mike来提到mike这个用户,mike会被高亮显示。如果你真的想打“@”这个字符,请用“@@”。

博客信息

作者
Jayun
时间
2023-09-17 16:22:39
博客类型