Loading

AtCoder Regular Contest 058

F 题感觉难度比较高,有点高妙,现在写有点浪费了,先放着,以后有实力了再写。

C - Iroha's Obsession

买家想买一个价格为 \(n\) 的物品,但他又讨厌 \(k\) 个数字,分别为 \(D_1,D_2,\dots,D_k\)。问他最少出多少钱,才能在保证买下这个物品的同时使自己出的钱不包括自己讨厌的数字。

\(1\le n\lt 10000\)

直接从 \(n\) 开始枚举即可。

Code

#include <bits/stdc++.h>

const int maxn = 1e4 + 5;
int n,k;
bool vis[10];

int main() {
	scanf("%d %d",&n,&k);
	for(int i = 1,x;i <= k;++ i)
		scanf("%d",&x),vis[x] = true;
	for(int i = n;;++ i) {
		bool flag = true;
		for(int x = i;x;x /= 10)
			flag &= !vis[x % 10];
		if(flag) {
			printf("%d\n",i);
			return 0;
		}
	}
	return 0;
}

D - Iroha and a Grid

有一个 \(n\times m\) 的矩阵, 现在你正位于左上角的格子, 并且你只能向右移动或向下移动, 不幸的是, 矩阵的左下角 \(a\times b\) 的地方被划为了禁区, 即你不能在此行走, 那么现在你有多少种方法从左上角走到右下角的格子呢?

\(1\le n,m\le 10^5,1\le a\lt n,1\le b\lt m\)

每次肯定是先走到 \(b+1\) 列保证不进入 \(a\times b\) 的区域,然后再走到 \((n,m)\)

因为 \(n,m\) 的范围支持 \(\mathcal O(n)\) 枚举,所以我们可以枚举 \((x,b+1)(x\in [1,n-a])\) 中的每个点,用 \(\binom{x+b-1}{b}\times \binom{n+m-x-b-1}{n-x}\) 算出方案数,累加即可。

但是这样又有一个问题:比如 \((2,b+1)\) 的方案数中会混入 \((1,b+1)\to (2,b+1)\) 的方案数,这样就算重了。

这个也比较好处理,根据 \(\binom{n}{m}=\binom{n-1}{m}+\binom{n}{m-1}\),把 \(\binom{x+b-1}{b}\) 换成 \(\binom{x+b-2}{b-1}\) 即可。

(upd:感觉上面这个式子前后因果逻辑有点崩,理解一下,实在理解不了直接相减去重也不是不行)

时间复杂度 \(\mathcal O(n+m)\)

Code

// Problem: D - Iroha and a Grid
// Contest: AtCoder - AtCoder Regular Contest 058
// URL: https://atcoder.jp/contests/arc058/tasks/arc058_b
// Memory Limit: 256 MB
// Time Limit: 2000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
typedef long long i64;

const i64 mod = 1e9 + 7;
const int maxn = 2e5 + 5;

i64 power(i64 x,i64 y) {
	i64 ans = 1;
	for(;y;y >>= 1) {
		if(y & 1)(ans *= x) %= mod;
		(x *= x) %= mod;
	}
	return ans;
}

int n,m,a,b;
i64 frac[maxn],inv[maxn];

i64 C(int n,int m) {
	if(n < 0||m < 0||n < m)return 0;
	return frac[n] * inv[m] % mod * inv[n - m] % mod;
}

int main() {
	scanf("%d %d %d %d",&n,&m,&a,&b);
	
	int cnt = n + m;
	frac[0] = 1;
	for(int i = 1;i <= cnt;++ i)
		frac[i] = (1ll * frac[i - 1] * i) % mod;
	inv[cnt] = power(frac[cnt] , mod - 2);
	for(int i = cnt - 1;~ i;-- i)
		inv[i] = (1ll * inv[i + 1] * (i + 1)) % mod;
	
	i64 ans = 0;
	for(int i = 1;i <= n - a;++ i) {
		(ans += C(b + i - 2 , b - 1) * C(n + m - i - b - 1 , n - i) % mod) %= mod;
	}
	
	printf("%lld\n",ans);
	return 0;
}

E - Iroha and Haiku

给定 \(n,X,Y,Z\),构造一个长为 \(n\) 的序列 \(a\),满足 \(\forall i\in [1,n],1\le a_i\le 10\),且有 \(1\le x\lt y\lt z\lt w\le n+1\) 使得 \(\sum\limits_{i=x}^{y-1} a_i=X,\sum\limits_{i=y}^{z-1} a_i=Y,\sum\limits_{i=z}^{w-1}=Z\)

求构造方案数。

\(\texttt{Data Range}:3\le n\le 40,1\le X\le 5,1\le Y\le 7,1\le Z\le 5,\rm{4s,512MB}\)

我和大多数错误的思路一样,一开始想先去对于每种长度算分界点方案,然后组合数统计。

但是这样有一个问题:如果一种方案存在不止一组 \((x,y,z,w)\),这种构造就会被计算两次。

想用容斥,但是可能有重叠,很不好处理。

观察数据范围,\(n\) 不可能用于状压,但 \((X+Y+Z)_{\max}=17\),非常适合状压。

由于 \(X,Y,Z\) 是一段数的值,我们要用特殊的方法进行压缩。

我们用 1 作分界点,每个 1 前面的 0 的数量加 1 代表着一个数。

每次在 \(a\) 结尾添加一个数 \(k\),就令当前状态 t 变为 (t<<k)|(1<<(k-1))&((1<<(x+y+z))-1)

然后,考虑计算答案。因为直接算「存在」会有重复,我们可以用总方案数减去「不存在」。

具体地,令 \(dp(i,j)\) 表示前 \(i\) 个数,当前状态为 \(j\),不存在题中四元组的方案数。

显然,\(j\) 的第 \(Z-1,Y+Z-1,X+Y+Z-1\) 位均不能为 1。

如果 \(j\) 满足上述条件,就可以从上一个状态转移。

答案为 \(10^n-\sum\limits_{i=0}^{2^{X+Y+Z}-1}dp(n,i)\)

时间复杂度:\(\mathcal O(n\times 2^{X+Y+Z})\)

Code

// Problem: AT_arc058_c [ARC058E] 和風いろはちゃん
// Contest: Luogu
// URL: https://www.luogu.com.cn/problem/AT_arc058_c
// Memory Limit: 512 MB
// Time Limit: 4000 ms
// 
// Powered by CP Editor (https://cpeditor.org)

#include <bits/stdc++.h>
typedef long long i64;

const i64 mod = 1e9 + 7;
const int maxn = 45;
const int maxm = (1 << 17) + 1;

i64 power(i64 x,i64 y) {
    i64 ans = 1;
    for(;y;y >>= 1) {
        if(y & 1)(ans *= x) %= mod;
        (x *= x) %= mod;
    }
    return ans;
}

int n,x,y,z;

bool check(int k) {
    if(!(k >> (z - 1) & 1))return true;
    if(!(k >> (y + z - 1) & 1))return true;
    if(!(k >> (x + y + z - 1) & 1))return true;
    return false;
}

i64 dp[maxn][maxm];

int main() {
    scanf("%d %d %d %d",&n,&x,&y,&z);
    dp[0][0] = 1;
    for(int i = 1;i <= n;++ i) {
        for(int j = 0;j < (1 << (x + y + z));++ j) {
            for(int k = 1;k <= 10;++ k) {
                int s = (j << k) | (1 << (k - 1));
                s &= (1 << (x + y + z)) - 1;
                if(check(s))(dp[i][s] += dp[i - 1][j]) %= mod;
            }
        }
    }

    i64 ans = power(10 , n);
    for(int i = 0;i < (1 << (x + y + z));++ i)
        (ans += mod - dp[n][i]) %= mod;

    printf("%lld",ans);
    return 0;
}
posted @ 2022-10-29 11:20  Skylakes  阅读(40)  评论(0)    收藏  举报