2024牛客多校第一场A Bit Common & A Bit More Common
A Bit Common
题目描述
Given two integers
Note that a non-empty subsequence of a sequence
Since the answer may be very large, output it modulo a positive integer
The bitwise AND of non-negative integers
- When
is written in base two, the digit in the 's place ( ) is if those of and are both , and otherwise.
For example, we have
Generally, the bitwise AND of
and we can prove that this value does not depend on the order of
输入描述
The only line contains three integers
( ), ( ) and ( ).
输出描述
Output a line containing an integer, denoting the answer.
示例1
输入
2 3 998244353
输出
17
示例2
输入
5000 5000 998244353
输出
2274146
笔者翻译
给你两个整数
思路
关键词:按位与,小于
不难想到这道题中出现的数字不能简单的当成十进制的数字来看,当成二进制数来看比较合适。也就是说,每一个元素都当作一个长度为
存在一段子序列使得子序列所有的元素的按位与为
下面的这张图或许能帮助你更好的理解
对于没有被选种的
那么显而易见的,最后的答案就是:
具体的代码实现细节
其实式子列到这个地方,这道题的关键部分就已经结束了,剩下的都是些写代码的无关紧要的小事罢。不过我还是说一些我觉得可以优化的地方。
首先是求组合数的时候,如果每个循环都单独求一次组合数,未免太浪费时间了,比较常用的做法是在主程序开始之前就用
rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
//把C[i][0]标记为1,其他的用公式递推
//rep是for循环但是i<,_rep是i<=,下同
式子中还有很多求幂的地方,使用快速幂来优化
ll binpow(ll a, ll b, ll m)
{
a %= m;
ll res = 1;
while (b > 0)
{
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
同时,式子中出现了许多求2的次方的地方,虽然使用快速幂可以在
twoFang[0] = 1;
_rep(i, 1, 25000006)
twoFang[i] = twoFang[i - 1] * 2 % mod;
最后只需要代入一点点算就行了
_rep(i, 1, n)
{
ll cnk = c[n][i];
ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
temp = temp * twoFang[(n - i) * (m - 1)] % mod;
ans = (ans + temp) % mod;
}
第一问完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define endl '\n'
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define rep(i, a, b) for (int i = (a); i < (b); ++i)
#define Forget_that return
#define Just_go 0
#define Endl endl
#define ednl endl
#define eldn endl
#define dnel endl
#define enndl endl
#define Ednl endl
#define Edml endl
#define llmax 9223372036854775807
#define intmax 2147483647
int n;
ll mod;
ll twoFang[30000007];
ll c[5005][5005];
ll binpow(ll a, ll b, ll m)
{
a %= m;
ll res = 1;
while (b > 0)
{
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll n, m;
cin >> n >> m;
cin >> mod;
rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
twoFang[0] = 1;
_rep(i, 1, 30000006)
twoFang[i] = twoFang[i - 1] * 2 % mod;
ll ans = 0;
_rep(i, 1, n)
{
ll cnk = c[n][i];
ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
temp = temp * twoFang[(n - i) * (m - 1)] % mod;
ans = (ans + temp) % mod;
}
cout << ans;
Forget_that Just_go;
}
没有时间为第一问的解决感到开心,接下来到场的是第二问Orz
A Bit More Common
题目描述
Given two integers
Note that a non-empty subsequence of a sequence
Since the answer may be very large, output it modulo a positive integer
The bitwise AND of non-negative integers
- When
is written in base two, the digit in the 's place ( ) is if those of and are both , and otherwise.
For example, we have
Generally, the bitwise AND of
and we can prove that this value does not depend on the order of
示例1
输入
2 3 998244353
输出
7
示例2
输入
5000 5000 998244353
输出
530227736
笔者翻译
这一题和第一问不同的地方在于,第一问只需要找到序列中含有子序列满足题意的数列,第二问要求找到序列中存在两个子序列,使得子序列的所有元素的按位与为
思路
都把第一问写完了,这第一问肯定不是白写的啊,我们不难发现,如果记序列中只含有一个子序列使得子序列的所有元素的按位与为1的序列数量为
所以,这道题的重点就成了求序列中只含有一个子序列使得子序列的所有元素的按位与为1的序列数量。
一样的,我们设该序列的这段子序列的长度为
特别的,当这段子序列长度为
剩下的,我们只需要找到满足下面性质的矩阵数量就行了
行 列- 矩阵中的元素不是
就是 - 每行都有
是这一列中唯一一个 - 每行可以有很多
- 每一列都必须有一个0
不难发现,这个矩阵按列进行划分,可以划分成两种列
- 每一列含有两个以上包括两个的0
- 每一列只有一个0,同时所有的这些列的0会在矩阵的所有k行中出现(矩阵的每一行都有这些0中的某些)
我们设第二种情况的列有
首先对于第一种情况
对于每一列来说都有
接着对于第二种情况
直接计算会有点麻烦,读者不妨自己试一下,这里我使用大家都推荐的一种方法---动态规划
我们设满足下面条件的矩阵的数量为
行 列- 每一列都只有
个 (这些 出现在所有的 行中) - 每一行都有
先说结论:
解释
总体思想就是:第
对于
如图所示,将一个
同时,有一种前3列组成的矩阵并不满足题意的情况,即从
对于一个
如图所示,将一个
故而
综上所述,共有
最后拿第一问的答案减去上面这个就行了。
具体的代码实现细节
首先是那个动态规划的部分
dp[1][1] = 1;
dp[0][0] = 1;
_rep(i, 1, 5003) _rep(j, i, 5003)
dp[i][j] = i * (dp[i - 1][j - 1] + dp[i][j - 1]) % mod;
当子序列长度为1时的特殊情况
ans = (((ans - n * twoFang[(n - 1) * (m - 1)] % mod) % mod) + mod) % mod;
对于非特殊情况的求解
_rep(k, 2, n)
{
ll temp = c[n][k];
ll suffix = 0;
ll btm = 1;
for (int t = m - 1; t >= k; t--)
{
ll mark = c[m - 1][t];
mark = mark * btm % mod;
mark = mark * dp[k][t] % mod;
suffix = (suffix + mark) % mod;
btm = btm * (twoFang[k] - k - 1) % mod;
}
temp = temp * suffix % mod * twoFang[(n - k) * (m - 1)] % mod;
ans = ((ans - temp) % mod + mod) % mod;
}
这里有一点需要注意,笔者在第一次写的时候,求
完整代码
点击查看代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define endl '\n'
#define _rep(i, a, b) for (int i = (a); i <= (b); ++i)
#define rep(i, a, b) for (int i = (a); i < (b); ++i)
#define Forget_that return
#define Just_go 0
#define Endl endl
#define ednl endl
#define eldn endl
#define dnel endl
#define enndl endl
#define Ednl endl
#define Edml endl
#define llmax 9223372036854775807
#define intmax 2147483647
int n;
ll mod;
ll twoFang[30000007];
ll c[5005][5005];
ll dp[5005][5005];
ll binpow(ll a, ll b, ll m)
{
a %= m;
ll res = 1;
while (b > 0)
{
if (b & 1)
res = res * a % m;
a = a * a % m;
b >>= 1;
}
return res;
}
int main()
{
ios::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
ll n, m;
cin >> n >> m;
cin >> mod;
rep(i, 0, 5004) _rep(j, 0, i) if (!j) c[i][j] = 1;
else c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
twoFang[0] = 1;
_rep(i, 1, 25000006)
twoFang[i] = twoFang[i - 1] * 2 % mod;
ll ans = 0;
_rep(i, 1, n)
{
ll cnk = c[n][i];
ll temp = cnk * binpow((twoFang[i] - 1), m - 1, mod) % mod;
temp = temp * twoFang[(n - i) * (m - 1)] % mod;
ans = (ans + temp) % mod;
}
ans = (((ans - n * twoFang[(n - 1) * (m - 1)] % mod) % mod) + mod) % mod;
dp[1][1] = 1;
dp[0][0] = 1;
_rep(i, 1, 5003) _rep(j, i, 5003)
dp[i][j] = i * (dp[i - 1][j - 1] + dp[i][j - 1]) % mod;
_rep(k, 2, n)
{
ll temp = c[n][k];
ll suffix = 0;
ll btm = 1;
for (int t = m - 1; t >= k; t--)
{
ll mark = c[m - 1][t];
mark = mark * btm % mod;
mark = mark * dp[k][t] % mod;
suffix = (suffix + mark) % mod;
btm = btm * (twoFang[k] - k - 1) % mod;
}
temp = temp * suffix % mod * twoFang[(n - k) * (m - 1)] % mod;
ans = ((ans - temp) % mod + mod) % mod;
}
cout << ans;
Forget_that Just_go;
}
总结
虽然像我这么做,时空复杂度都不小,第二问花了1700多毫秒才过。但是好歹过了。具体的优化可以根据n、m来自主分配所需要的空间和常量。
本文作者:AcidBarium
本文链接:https://www.cnblogs.com/acidbarium/p/18324582
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 一文读懂知识蒸馏
· 终于写完轮子一部分:tcp代理 了,记录一下