Atcoder Regular Contest 058 题解

ARC058C. Iroha's Obsession *1174

n 再大一点的就是巨大恶心分类讨论。但我们注意到 n104,所以我们可以直接暴力枚举然后写个 check。首先我们先把被 ban 掉的数存标记一下。然后从 n 开始往上查,一直查到 106 基本就可以了。然后每次检查一下有没有数位被 ban 掉,没有的话直接输出然后退出就好了。

#include <bits/stdc++.h>
using namespace std;
int n, k, x;
bool D[11];
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> k;
	for(int i = 1; i <= k; i ++) 
	{
		cin >> x;
		D[x] = true;
	}
	for(int i = n; i <= 1000000; i ++)
	{
		int ii = i;
		bool Flag = true;
		while(ii > 0)
		{
			if(D[ii % 10]) Flag = false;
			ii /= 10;
		}
		if(Flag) 
		{
			cout << i << '\n';
			break;
		}
	}
	return 0;
}

ARC058D. Iroha and a Grid *1905

还算比较经典的组合数学题目。我们注意到 n,m 非常大,不足以让我们动态规划。但是我们注意到它只 ban 掉一个矩形,于是我们可以考虑分阶段组合计数。

我们先考虑如果没有任何矩形被 ban 掉我们要怎么做。这是简单的,就是 (n+m2n1),或者是 (n+m2m1),因为由组合恒等式,这两个东西是相等的。

接下来我们考虑它 ban 掉了哪个区域,其实就是 (nA+1,1)(n,B) 这段区域。那我们考虑把矩阵切了,变为 (1,1)(nA,B)(1,B+1)(n,n) 这两部分。然后枚举分界点,用乘法原理和加法原理统计它们对于答案的贡献。其实就是

i=1nA(i+B2i1)×(n+mBi1ni)

然后就做完了。因为我们需要计算组合数,所以我们可能还需要预处理一步阶乘和阶乘逆元。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MAXN = 200005;
const LL MOD = 1e9 + 7;
LL n, m, A, B, fac[MAXN], inv[MAXN];
LL QuickPow(LL a, LL b, LL p)
{
	LL res = 1;
	while(b > 0)
	{
		if(b & 1) res = (res * a) % p;
		a = (a * a) % p;
		b >>= 1; 
	}
	return res;
}
LL C(LL a, LL b) 
{
	if(a < b) return 0; 
	else return ((fac[a] * inv[b]) % MOD * inv[a - b]) % MOD; 
}
LL calc(LL a, LL b) { return C(a + b - 2, a - 1); }
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> m >> A >> B;
	fac[0] = 1;
	for(LL i = 1; i <= n + m - 2; i ++) fac[i] = (fac[i - 1] * i) % MOD;
	inv[n + m - 2] = QuickPow(fac[n + m - 2], MOD - 2, MOD);
	for(LL i = n + m - 3; i >= 0; i --) inv[i] = (inv[i + 1] * (i + 1)) % MOD;
	LL ans = 0;
	for(LL i = 1; i <= n - A; i ++)
		ans = (ans + (calc(i, B) * calc(n - i + 1, m - B)) % MOD) % MOD;
	cout << ans << '\n';
	return 0;
}

ARC058E. Iroha and Haiku *2473

过于神秘了,一眼丁真为我做不出来的题。

首先我们肯定知道如果正着做肯定是不好做的,一个很直接的原因就是贡献会算重复。然后就寄了。所以我们算有多少个序列是不好的。

然后我们考虑到它的 x,y,z 特别小,所以其实我们非常容易联想到状态压缩动态规划。事实证明我猜对了,但是我会不了一点。我们发现不仅 x,y,z 小,x+y+z 也在可接受范围内。于是我们考虑有没有关于 x+y+z 这个常数的时间复杂度。

我们定义 fi,j 为考虑到第 i 个位置,其后缀和存在状态为 j 的方案数。 就非常神奇的一个状态定义。因为我们考虑到我们关心的后缀和情况最多只有 17 位,所以我们考虑设计这个状态。

什么情况下这个后缀和串是不好的呢?非常简单,有三种情况:

  1. 不存在一个后缀和等于 z
  2. 不存在一个后缀和等于 y+z
  3. 不存在一个后缀和等于 x+y+z

至于怎么计算后缀和串,这是简单的。我们考虑枚举 k110,那么加上这位后缀和就从 j 变为了 ((j << k) | (1 << (k - 1))) & ((1 << (X + Y + Z)) - 1)。这是容易理解的。然后就是普通的计数,后面是简单的。

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
const LL MOD = 1e9 + 7;
LL n, X, Y, Z, tot = 1, DP[45][(1 << 17) + 5]; 
bool check(LL stat)
{
	if(!(stat & (1 << (Z - 1)))) return true;
	if(!(stat & (1 << (Y + Z - 1)))) return true;
	if(!(stat & (1 << (X + Y + Z - 1)))) return true;
	return false; 
}
int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin >> n >> X >> Y >> Z;
	for(LL i = 1; i <= n; i ++) tot = (tot * 10) % MOD;  
	DP[0][0] = 1;
	for(LL i = 1; i <= n; i ++)
	{
		for(LL j = 0; j < (1 << (X + Y + Z)); j ++)
		{
			for(LL k = 1; k <= 10; k ++)
			{
				LL stat = ((j << k) | (1 << (k - 1))) & ((1 << (X + Y + Z)) - 1);
				if(!check(stat)) continue;
				DP[i][stat] = (DP[i][stat] + DP[i - 1][j]) % MOD;
			}
		}
	}
	for(LL i = 0; i < (1 << (X + Y + Z)); i ++) tot = ((tot - DP[n][i]) % MOD + MOD) % MOD;
	cout << tot << '\n';
	return 0;
}

F 题是巨大不可做题,我没有能力补。

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