Atcoder Regular Contest 060 题解

ARC060C. Tak and Cards *1583

简单题。考虑一个非常非常常见的 Trick。把区间平均值为 \(k\) 转化为区间和为 \(0\) 只需要将每个数都减去 \(k\) 即可。然后就是一个朴素的背包求和为 \(0\) 方案数。注意处理负数下标就好了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 55; 
LL n, a, A[MAXN], DP[MAXN][5005], delta = 2500;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> a;
	for(LL i = 1; i <= n; i ++) 
	{
		cin >> A[i];
		A[i] -= a;
	}
	DP[0][delta] = 1;
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 0; j <= 5000; j ++)
		{
			if(j >= A[i]) DP[i][j] = DP[i - 1][j] + DP[i - 1][j - A[i]];
			else DP[i][j] = DP[i - 1][j];
		}
	}
	cout << DP[n][delta] - 1 << '\n';
	return 0;
} 

ARC060D. Digit Sum *2261

非常神的题,但是我自己想到了。

根号分治。首先如果 \(b \leq \sqrt{n}\),那么直接暴力枚举求得数位和是否为 \(s\)

如果 \(b > \sqrt{n}\),我们注意到有一个结论,其最小的可能的进制就是 \(\frac{n-s}{i}+1\)。注意这里要保证 \(n-s\)\(i\) 的倍数。记 \(p=\frac{n-s}{i}+1\),最后还要判断一下 \(f(p,n)\) 是否等于 \(s\)。然后我们就做完了。注意要特判一下,如果这里的 \(p=1\),那么我们直接扔掉。不扔掉的话会死循环寄掉。

然后做完了,代码不长但根号分治一般都很神。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n, s;
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> s;
	if(n < s)
	{
		cout << -1 << '\n';
		return 0;
	}
	for(LL i = 2; i <= (LL)sqrt(n); i ++)
	{
		LL sum = 0, nn = n;
		while(nn > 0)
		{
			sum += (nn % i);
			nn /= i;
		}
		if(sum == s) 
		{
			cout << i << '\n';
			return 0;
		}
	}
	LL minnum = 1e15 + 7;
	if(s == n) minnum = min(minnum, n + 1);
	for(LL i = 1; i <= sqrt(n); i ++)
	{
		if((n - s) % i != 0) continue;
		LL b = (n - s) / i + 1;
		if(b == 1) continue;
		LL sum = 0, nn = n;
		while(nn > 0)
		{
			sum += (nn % b);
			nn /= b;
		}
		if(sum == s) minnum = min(minnum, b);
	}
	if(minnum == 1e15 + 7) cout << -1 << '\n';
	else cout << minnum << '\n'; 
	return 0;
}

ARC060E. Tak and Hotels *2154

倍增优化动态规划,我应该还是第一次理解这个东西。朴素的暴力就是暴力枚举,这里不做过多讲解。

考虑正解,设 \(f_{i,j}\) 为从 \(i\)\(2^j\) 步能跳到的最远点。然后每次 Query 的时候你都往上调,基本就类似最近公共祖先 LCA 的那种求法随便倍增。然后就做完了。注意一下处理 \(f_{i,0}\) 的细节。还有数组空间别开小(我就因为这个赛时没过 E 题)

然后就做完了。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 100005;
LL n, A[MAXN], L, Q, DP[MAXN][25];
LL Query(LL a, LL b)
{
	LL now = a, ans = 0;
	for(LL i = 24; i >= 0; i --)
		if(DP[now][i] < b) 
		{
			ans += (1 << i);
			now = DP[now][i];
		}
	return ans + 1;
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n;
	for(LL i = 1; i <= n; i ++) cin >> A[i];
	cin >> L;
	memset(DP, 0x3f, sizeof(DP));
	for(LL i = 1; i <= n; i ++)
	{
		LL Pos = upper_bound(A + 1, A + n + 1, A[i] + L) - A - 1;
		DP[i][0] = Pos;
	}
	for(LL j = 1; j <= 24; j ++)
		for(LL i = 1; i <= n; i ++)
			DP[i][j] = DP[DP[i][j - 1]][j - 1];
	cin >> Q;
	while(Q --)
	{
		LL a, b;
		cin >> a >> b;
		if(a > b) swap(a, b);
		cout << Query(a, b) << '\n';
	}
	return 0;
}

ARC060F. Best Representation *2804

Subtask 1:\(|s| \leq 4000\)

考虑一个 \(O(n^2)\) 的做法。看到划分想到动态规划。这个应该是简单的,设 \(f_i\) 表示前 \(i\) 个能划分的最少子串个数。\(g_i\) 表示划分成最少子串个数的方案数。但是我们需要一个 \(Q_{i,j}\) 表示 \(i\)\(j\) 的子串是否是好串。

注意到好串的定义就是之前题目里面的 Power Strings。然后就递推跑 KMP,应该是跑 \(n\) 遍 KMP,每次跑 \(i\)\(n\) 这个子串,预处理出 \(Q_{i,j}\)。动态规划的时间复杂度是 \(O(n^2)\),跑 \(n\) 遍 KMP 的时间复杂度也是 \(O(n^2)\),所以总复杂度就是 \(O(n^2)\),然后就做完了。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 4005;
const int MOD = 1e9 + 7;
string s;
int n, KMP[MAXN], Q[MAXN][MAXN], f[MAXN], g[MAXN];
int main()
{
	cin >> s; n = s.length(); s = ' ' + s; 
	for(int i = 1; i <= n; i ++)
		for(int j = i; j <= n; j ++)
			Q[i][j] = 1;
	for(int i = 1; i <= n; i ++)
	{
		memset(KMP, 0, sizeof(KMP)); 
		int k = 0;
		for(int j = 2; j <= n - i + 1; j ++)
		{
			while(k > 0 && s[j + i - 1] != s[k + i]) k = KMP[k];
			if(s[j + i - 1] == s[k + i]) k ++;
			KMP[j] = k;
			if(j % (j - KMP[j]) == 0 && j / (j - KMP[j]) >= 2) Q[i][i + j - 1] = 0;
		}
	}
	memset(f, 0x3f, sizeof(f));
	f[0] = 0;
	for(int i = 1; i <= n; i ++)
	{
		for(int j = 0; j < i; j ++)
		{
			if(Q[j + 1][i] == 0) continue;
			if(f[j] + 1 == f[i]) g[i] = (g[i] + 1) % MOD;
			else if(f[j] + 1 < f[i])
			{
				f[i] = f[j] + 1;
				g[i] = 1;
			}
		} 
	}
	cout << f[n] << '\n' << g[n] << '\n'; 
	return 0;
}

Subtask 2:\(|s| \leq 5\times 10^5\)

首先特判:

  1. 如果字符串内的元素全部相等,那么必然要划分成 \(n\) 个串,个数就是 \(1\) 个。
  2. 如果字符串没有长度 \(>1\) 的周期或者 \(|s|=1\),那么可以不用划分,最小划分个数就是 \(1\),个数也是 \(1\)

后面有一个非常强的结论,我不想证。就是如果存在周期长度 \(>1\),那么一定可以从第一个字符后面切开,使这个字符串变为两个串,那么这两个串一定不是 Power Strings。这点通过对于暴力 \(f\) 数组的打表也可以发现,所以这种情况的最小划分个数就是 \(2\)

然后怎么计数呢?因为是划分成两段,所以我们很自然的想到枚举分界点,然后看前面一段是不是 Power Strings,然后看后面一段是不是 Power Strings,因为 KMP 跑出的 border 具有对称性,所以可以通过正反串两次 KMP 得出答案。最后判断是 \(O(1)\) 的,上面讲过怎么判断 Power Strings。

#include <bits/stdc++.h>
using namespace std;
const int MAXN = 500005;
string s, t;
int n, KMP[MAXN], KMP2[MAXN];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> s; n = s.length(); s = ' ' + s; 
	bool Flag = true;
	for(int i = 2; i <= n; i ++) if(s[i] != s[i - 1]) Flag = false;
	if(Flag)
	{
		cout << n << '\n' << 1 << '\n';
		return 0;
	}
	for(int i = n; i >= 1; i --) t += s[i];
	t = ' ' + t;
	for(int i = 2, j = 0; i <= n; i ++)
	{
		while(j > 0 && s[i] != s[j + 1]) j = KMP[j];
		if(s[i] == s[j + 1]) j ++;
		KMP[i] = j;
	}
	for(int i = 2, j = 0; i <= n; i ++)
	{
		while(j > 0 && t[i] != t[j + 1]) j = KMP2[j];
		if(t[i] == t[j + 1]) j ++;
		KMP2[i] = j;
	}
	if(!KMP[n] || n % (n - KMP[n]) != 0) // 没有循环节
	{
		cout << 1 << '\n' << 1 << '\n';
		return 0;
	} 
	int ans = 0;
	for(int i = 1; i < n; i ++)
		if((KMP[i] == 0 || i % (i - KMP[i]) != 0) && (KMP2[n - i] == 0 || (n - i) % (n - i - KMP2[n - i]) != 0)) ans ++;
	cout << 2 << '\n' << ans << '\n';
	return 0;
}
posted @ 2024-11-21 10:24  Fractured_Angel  阅读(1)  评论(0编辑  收藏  举报