Description:
有一组整数 {0,1,2,…,2^m−1}, 请从中选出 k 个数,使得这 k 个数的异或和为 n, 请输出最大的满足条件的 k。
Solution:
既然要选择最多的k 那么我们可以想到0和x异或为x 我们让尽量多的数字异或和为0 最后找一个x
对
于
任
何
[
0
,
2
m
−
1
]
中
,
举
例
0
⊕
7
=
1
⊕
6
=
2
⊕
5
=
3
⊕
4
=
0
那
么
总
异
或
和
就
为
0
所
以
如
果
我
们
需
要
一
个
数
n
的
话
,
我
们
就
会
将
带
有
n
的
数
对
拆
掉
,
且
去
掉
n
的
另
一
个
,
这
是
一
般
情
况
特
殊
情
况
就
是
当
n
和
m
特
别
小
的
时
候
我
们
需
要
分
类
讨
论
一
下
边
界
情
况
当
n
=
=
0
且
m
!
=
1
的
时
候
,
我
们
可
以
一
个
都
不
去
掉
,
k
=
2
m
当
n
=
=
1
且
m
=
=
1
的
时
候
,
k
=
2
其
余
情
况
,
皆
为
正
常
情
况
,
有
k
=
2
m
−
1
对于任何[0, 2^m-1]中,举例0\oplus7 = 1\oplus6 = 2\oplus5 = 3\oplus4 = 0 \\那么总异或和就为0\\所以如果我们需要一个数n的话,我们就会将带有n的数对拆掉,且去掉n的另一个,这是一般情况\\特殊情况就是当n和m特别小的时候 我们需要分类讨论一下边界情况\\当n==0且m!=1的时候,我们可以一个都不去掉,k=2^m\\当n==1且m==1的时候,k=2\\其余情况,皆为正常情况,有k=2^m-1
对于任何[0,2m−1]中,举例0⊕7=1⊕6=2⊕5=3⊕4=0那么总异或和就为0所以如果我们需要一个数n的话,我们就会将带有n的数对拆掉,且去掉n的另一个,这是一般情况特殊情况就是当n和m特别小的时候我们需要分类讨论一下边界情况当n==0且m!=1的时候,我们可以一个都不去掉,k=2m当n==1且m==1的时候,k=2其余情况,皆为正常情况,有k=2m−1
Code:
int main()
{
cin >> n >> m;
LL res = (1LL << m);
if(n == 0 && m != 1)
cout << res << '\n';
else if(n == 1 && m == 1)
cout << 2 << '\n';
else
cout << res - 1LL << '\n';
}
Description:
对于给定的数字 a , b ,当整数 n 在十进制下的所有数位都为 a 或 b 时,我们称 n 是“好数”
对于好数 n ,当 n 在十进制下每一位的数字之和也为“好数”时,我们称 n 是一个“完美数”
请你求出有多少 m 位数是“完美数”
(1≤m≤1e6,1≤a,b≤9)。
Solution:
由于m很大 你心想的dfs暴力行不通 O(2^m) 想来也是岂会那么简单
为什么dfs如此缓慢呢 因为对于每一位我都确认了他是a还是b 如果我避免这一点就可以节省大量时间
所以我们想到枚举a和b分别的数量 但是不考虑其摆放 花费时间O(n)
我们在枚举的同时 验证方案是否可行 大概是常数级别的时间
当方案可行的时候 我们要更新答案 如何更新呢 假设有x个a n-x个b 这是一种合法的方案
由于方案跟摆放顺序无关 于是这x个a可以放置在任意数位上 此时一个排列组合的做法就想到了
Code:
const int N = 1e6 + 5, mod = 1e9 + 7;
int a, b;
LL m;
LL fac[N], inv[N];
LL qmi(int a, int b)
{
LL res = 1;
while(b)
{
if(b & 1) res = res * a % mod;
a = a * 1LL * a % mod;
b >>= 1;
}
return res;
}
void init()
{
inv[0] = fac[0] = 1;
rep(i, 1, N)
fac[i] = i * fac[i - 1] % mod;
inv[N - 1] = qmi(fac[N - 1], mod - 2);
for(int i = N - 2; i; i --)
inv[i] = inv[i + 1] * (i + 1) % mod;
}
LL C(int a, int b)
{
return (fac[a] * inv[b] % mod * inv[a - b] % mod) % mod;
}
//组合数学板子
int main()
{
cin >> a >> b >> m;
init();
LL res = 0;
rep(i, 0, m + 1) //选i个b m-i个a 组合
{
LL last = m - i;
LL num = last * a + i * b;
bool flag = 1;
while(num)
{
if(num % 10 != a && num % 10 != b)
{
flag = false;
break;
}
num /= 10;
}
if(flag)
res = (res + C(m, i)) % mod;
}
cout << res;
}
Description:
给定 N 个正整数 A_1,A_2,…,A_N,从中选出若干个数,使它们的和为 M,求有多少种选择方案。
N < 100, M < 10000, A_i < 1000
Solution:
我们可以定义一个数组dp[ j ],表示和为j的方案数有多少种
显然dp[0] = 1
转移方程为dp[j] += dp[j - a[i]]
Code:
int main()
{
cin >> n >> m;
rep(i, 1, n + 1)
cin >> a[i];
dp[0] = 1;
rep(i, 1, n + 1)
for(int j = m; j >= a[i]; j --)
dp[j] += dp[j - a[i]];
cout << dp[m];
}
Description:
给定一个自然数 N,要求把 N 拆分成若干个正整数相加的形式,参与加法运算的数可以重复。
Solution:
这题还是求和的方案数,但是跟上一题的差别就在于上一题是每个数只能选一次 然而这一题每个数可以选任意次,所以这就是01背包和完全背包的区别了
定义数组dp[ j ] 表示和为j的方案数
dp[0] = 1
转移方程为dp[j] += dp[j - a[i]]
Code:
int main()
{
cin >> n;
dp[0] = 1;
for(int i = 1; i < n; i ++) //[1, n - 1]
for(int j = i; j <= n; j ++)
dp[j] = (dp[j] + dp[j - i]) % mod;
cout << dp[n];
}
每次合并相邻两堆石子,合并代价为两堆石子的质量和,由于合并的顺序不同,因此付出的代价也就不同,请问合并长度为n的石子需要的最小代价是多少
N = 300
区间DP是为了解决线性DP中分阶段划分问题时,与顺序相关和合并相关的问题而产生的
其主要的特点分为以下三点
对于本题而言 我们需要设置dp[ i ] [ j ]来表示和并[i, j]的最小代价
通过区间DP的特点 我们不难写出转移方程dp[ i ] [ j ] = min(dp[i] [k] + dp[k + 1] [j] + 合并需要付出的代价)
因为区间dp的性质 是将一个问题分为左右两个部分 于是我们必须先知道小的部分 才能向大的部分递推求解 这就决定了区间DP中的遍历顺序 也就是说我们必须先得到长度更小的区间的最优值 再向长度更大的区间递推而得最优值
//本题核心代码
memset(dp, 0x3f, sizeof dp);
for(int len = 1; len <= n; len ++)
{
for(int i = 1; i + len - 1 <= n; i ++)
{
int j = i + len - 1;
if(len == 1) //初始化为0
dp[i][j] = 0;
else //若len不为1 开始递推
{
for(int k = i; k < j; k ++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + s[j] - s[i - 1]); //s代表前缀和数组
}
}
}
//练习一下记忆化搜索写法
int dfs(int s, int e)
{
if(s == e) return 0;
int &v = dp[s][e];
if(v != -1) return v;
v = 1e9;
for(int k = s; k < e; k ++)
v = min(v, dfs(s, k) + dfs(k + 1, e) + pre_[e] - pre_[s - 1]);
return v;
}
明天一套CF 刷区间DP题目