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

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

根号分治。首先如果 bn,那么直接暴力枚举求得数位和是否为 s

如果 b>n,我们注意到有一个结论,其最小的可能的进制就是 nsi+1。注意这里要保证 nsi 的倍数。记 p=nsi+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

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

考虑正解,设 fi,j 为从 i2j 步能跳到的最远点。然后每次 Query 的时候你都往上调,基本就类似最近公共祖先 LCA 的那种求法随便倍增。然后就做完了。注意一下处理 fi,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|4000

考虑一个 O(n2) 的做法。看到划分想到动态规划。这个应该是简单的,设 fi 表示前 i 个能划分的最少子串个数。gi 表示划分成最少子串个数的方案数。但是我们需要一个 Qi,j 表示 ij 的子串是否是好串。

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

#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|5×105

首先特判:

  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 @   DataEraserQ  阅读(5)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示