Codeforces Round #714 部分题解

这场依旧质量很高,题目都有一种耳目一新的感觉。

A. Array and Peaks

题意: 一个下标 i i i被成为peak的条件是 1 < i < n , a i > a i − 1 , a i > a i + 1 1<i<n,a_i>a_{i-1},a_i>a_{i+1} 1<i<nai>ai1ai>ai+1。现给出一个数字 n n n,要求构造一个peak数恰为 k k k排列。如果不能构造,输出-1。
做法: 瞎搞。首先可以断定, 2 k + 1 > n 2k+1>n 2k+1>n时,无法构造。否则,我们仅使用 1 − 2 k + 1 1-2k+1 12k+1就能构造出来。将 1 − k + 1 1-k+1 1k+1放到前面的奇数位置,将 k + 2 − 2 k k+2-2k k+22k放到偶数位置上,之后剩下的位置 a i = i a_i=i ai=i即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
int n, k, a[120]; 
int main()
{
	ios::sync_with_stdio(false);
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n >> k;
		if(k * 2 >= n) cout << -1 << endl;
		else
		{
			for(int p = 0, i = 1; p <= k; p++, i++) a[2 * p + 1] = i;
			for(int p = 1, j = k + 2; p <= k; p++, j++) a[2 * p] = j;
			for(int i = 2 * k + 2; i <= n; i++) a[i] = i;
			for(int i = 1; i <= n; i++)
			{
				cout << a[i];
				if(i == n) cout << endl;
				else cout << ' ';
			}
		} 
	}
	return 0;
}

B. AND Sequences

题意: AND序列的定义是对于所有的 < n <n <n i i i,都有 A N D j = 1 i = A N D j = i + 1 n AND_{j=1}^{i}=AND_{j=i+1}^{n} ANDj=1i=ANDj=i+1n。现在给出一个数组 a a a,你可以打乱其下标的顺序形成一个新的数组,问由多少下标序列能够使得打乱后的数组为AND序列?
做法: 首先,AND序列的条件还是比较有规律的,我们试试能从题目条件中推导出什么不。首先 a 1 = A N D j = 2 n a_1=AND_{j=2}^{n} a1=ANDj=2n a 1 & a 2 = A N D j = 2 n & a 2 = A N D j = 3 n a_1\&a_2=AND_{j=2}^{n}\&a_2=AND_{j=3}^{n} a1&a2=ANDj=2n&a2=ANDj=3n,说明 a 2 & A N D j = 3 n = A N D j = 3 n a2\&AND_{j=3}^{n}=AND_{j=3}^{n} a2&ANDj=3n=ANDj=3n,以此类推,最终可以得到 a n − 1 & a n = a n a_{n-1}\&a_n=a_n an1&an=an,再将这个结果依次回代,最终可以得到 a i & a n = a n a_i\&a_n=a_n ai&an=an。说明,一个AND序列里的最后一个元素是最小的。另外,我们回代到 a 1 = A N D j = 2 n = a n a_1=AND_{j=2}^{n}=a_n a1=ANDj=2n=an时可以发现, a 1 a_1 a1 a n a_n an的值相等。至此,可以发现,一个AND序列的两头都是最小值,并且其他任何一个元素与最小值做按位与都是最小值本身。
所以,我们首先检查 a a a数组中的每个元素是否满足和最小值按位与还是等于最小值的条件。顺便统计一下最小值的个数,设为 m m m,答案就是 m ∗ ( m − 1 ) ∗ ( n − 1 ) ! m*(m-1)*(n-1)! m(m1)(n1)!

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, mod = 1e9 + 7;
int n, a[N], f;
ll fac[N], ans;
map<int, int> mp;

int main()
{
	ios::sync_with_stdio(false);
	fac[0] = 1;
	for(int i = 1; i <= 2e5; i++) fac[i] = fac[i - 1] * i % mod;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> n;
		mp.clear();
		f = 1;
		for(int i = 1; i <= n; i++) cin >> a[i], mp[a[i]]++;
		sort(a + 1, a + 1 + n);
		for(int i = 1; i <= n; i++)
		{
			if((a[i] & a[1]) != a[1])
			{
				f = 0;
				break;
			}
		}
		if(!f) cout << 0 << endl;
		else
		{
			ans = fac[n - 2] * mp[a[1]] % mod * (mp[a[1]] - 1) % mod;
			cout << ans << endl;
		}
	}
	return 0;
}

C. Add One

题意: 定义一个+1操作,规则如下:将 n n n的十进制每一位都+1,结果字符串拼起来就是操作结果,如19执行完变成了210。现在给出 n , m n,m n,m,求 n n n操作 m m m次后结果由多少位。
做法(官方题解): 我们注意到数字片段10是比较特殊的,因为任何一个数字位要产生新的数位,都必须经过它,到达了10后演变路线完全一致。所以我们来考虑 10 10 10操作 m m m次后能够生成多少数位。设 d p [ j ] dp[j] dp[j]表示数字 10 10 10操作了 m m m次后产生的位数。显然, 10 10 10要产生新的位至少操作9次,因此有 d p [ i ] + = d p [ i − 9 ] dp[i]+=dp[i-9] dp[i]+=dp[i9],10操作了9次后变为109,前面的10部分产生的位数反映在了 d p [ i − 9 ] dp[i-9] dp[i9]里。还有一个数字9,此时再操作一次还会产生新的进位2110,因而有 d p [ i ] + = d p [ i − 10 ] dp[i]+=dp[i-10] dp[i]+=dp[i10],这里就将数字9的贡献考虑了进来。总的来说, d p [ i ] = d p [ i − 9 ] + d p [ i − 10 ] dp[i]=dp[i-9]+dp[i-10] dp[i]=dp[i9]+dp[i10],递推即可,初值也很好设置 d p [ k ] = 2 , k < 9 , d p [ 9 ] = 3 dp[k]=2,k<9,dp[9]=3 dp[k]=2,k<9,dp[9]=3
对于每个数位,我们先将其凑出10,然后计算剩下的次数给10用能够产生多少新的数位。注意有些数位操作 m m m次后是凑不出10的,此时只对答案有1位的贡献。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, mod = 1e9 + 7;
ll ans, dp[N], m;
char s[N];

int main()
{
	ios::sync_with_stdio(false);
	for(int i = 0; i < 9; i++) dp[i] = 2;
	dp[9] = 3;
	for(int i = 10; i <= 2e5; i++) dp[i] = (dp[i - 9] + dp[i - 10]) % mod;
	int T;
	cin >> T;
	while(T--)
	{
		cin >> s + 1 >> m;
		ans = 0;
		for(int i = 1; s[i]; i++)
		{
			int n = s[i] - '0';
			ans += (m - 10 + n) < 0? 1 : dp[m + n - 10];
			ans %= mod;
		}
		cout << ans << endl;
	}
	return 0;
}

做法2(记忆化搜索,感谢队友的提醒): 题目中给出的操作显然是各数位之前毫无影响的,因此我们分别计算每个数位上的数字演变 m m m次可以产生多少新的数位。
d p [ d ] [ j ] dp[d][j] dp[d][j]表示数位上是 d d d操作 j j j次可以生成的位数。转移很简单: d p [ d ] [ j ] = d p [ 1 ] [ j − 1 ] + d p [ 0 ] [ j − 1 ] , d = 9 , d p [ d ] [ j ] = d p [ d + 1 ] [ j − 1 ] , d ≠ 9 dp[d][j]=dp[1][j-1]+dp[0][j-1],d=9,dp[d][j]=dp[d+1][j-1],d\neq 9 dp[d][j]=dp[1][j1]+dp[0][j1],d=9,dp[d][j]=dp[d+1][j1],d=9
初值 d p [ 0 − 9 ] [ 0 ] = 1 dp[0-9][0]=1 dp[09][0]=1
然后就是记忆化搜索并计算答案了。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 2e5 + 10, mod = 1e9 + 7;
ll dp[10][N], ans, m;
char s[N];
ll dfs(int i, int j)
{
	if(dp[i][j] != -1) return dp[i][j];
	ll res = 0;
	if(i == 9) res = (dfs(1, j - 1) + dfs(0, j - 1)) % mod;
	else res = dfs(i + 1, j - 1);
	return dp[i][j] = res;
}
int main()
{
	ios::sync_with_stdio(false);
	memset(dp, -1, sizeof(dp));
	for(int i = 0; i <= 9; i++) dp[i][0] = 1;
	int T;
	cin >> T;
	while(T--)
	{
		ans = 0;
		cin >> s + 1 >> m;
		for(int i = 1; s[i]; i++)
		{
			ans += dfs(s[i] - '0', m);
			ans %= mod;
		}
		cout << ans << endl;
	}
	return 0;
}

D(待补)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值