CF Round 693(Div3) 解题补题报告
作者:互联网
A题 Odd Divisor(数学/二进制)
给定 \(T(T \leq 10^4)\) 组数据。
判断一个数 \(n(2 \leq n \leq 10^{14})\) 是否存在一个大于 \(1\) 的奇数能够整除 \(n\),存在则输出"YES",反之输出 "NO"。
如果 \(n\) 是一个奇数,显然它自身可以整除自己(题目数据已经限制了,\(n\) 不可能为 \(1\),就不需要特判了),输出 "YES" 。
如果 \(n\) 是一个偶数,那么分两类:
-
它是 \(2\) 的幂,那么除了 \(1\),不可能存在其他的奇数因子,输出 "NO" 即可。
-
不是 \(2\) 的幂,那么这个数就可以变成 \(2^k*m\) 的形式(其中 \(m\) 是奇数),那么奇数 \(m\) 可以整除 \(n\),输出 "YES" 即可。
那么程序就很明显了:如果 \(n\) 是 \(2\) 的幂就输出 "NO",反之输出 "YES"。
判断一个数 \(n\) 是不是 \(2\) 的幂很简单:一直除以 \(2\),直到无法被 \(2\) 整除,看剩下来这个数是不是 \(1\) 即可,复杂度 \(O(\log n)\)。不过,作为打 \(ACM\) 的我们,应当想出来一些更加简便快速的方法。
我们学树状数组的时候,需要寻找一个数的二进制里面的 \(1\) 的位置,当时接触过 \(lowbit\) 的概念:找到一个二进制里面最右边的 \(1\) 和它右边的 \(0\) 构成的数。显然,如果一个数 \(n\) 是 \(2\) 的幂,那么 \(n = lowbit(n)\),我们依此进行判断即可。
另外,再介绍一个骚操作:\(n \& (n - 1)\),可以将一个数的二进制里面最右边的 \(1\) 变成 \(0\) (原理略),那么如果 \(n\) 是 \(2\) 的幂,那么显然 \(n \& (n-1) = 0\)。
贴出代码(只给出暴力的那种):
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin>>T;
while (T--) {
long long n;
cin>>n;
while (n % 2 == 0) n /= 2;
if (n == 1) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
return 0;
}
B题 New Year's Number(数学)
给定 \(T(T \leq 10^4)\) 组数据。
判断一个数 \(n(1 \leq n \leq 10^6)\) 是否能表示为 \(2020a+2021b\) 的形式(\(a,b\geq 0\)),存在则输出"YES",反之输出 "NO"。
这题我们还可以转化一下:方程 \(2020x +2021y=n\) 有没有一组非负解?
我们给出结论:若存在\(k \geq 1\),使得 \(2020k \leq n \leq 2021k\),那么便存在这么一组解。
证明
方法1
\(2020(x+y) + y = n\),其中\(0 \leq y \leq x + y\)。不妨设 \(x+y=k\),那么就变成了\(2020k+b=n\),其中 \(0\leq b \leq k\)。那么我们得出结论:如果解存在,那么必然有 \(2020k \leq n \leq 2021k\),其中 \(k \geq 1\)。(似乎证的并不是很明)
方法2
对于方程 \(2020x+2021y=n\), 我们显然容易想到扩展欧几里得(exgcd)。
因为 \(\gcd(2020,2021)=1\),那么方程 \(2020x+2021y=1\) 有解,我们可以轻易找出一组:\(\begin{cases}x=-1\\y=1\end{cases}\) 。
同乘上 \(n\),便得到了 \(2020x+2021y=n\) 的一组解:\(\begin{cases}x=-n\\y=n\end{cases}\) 。
我们对这组解尝试进行变换,看看能不能给他们变成一组非负解。
换言之,我们需要找到一个正整数 \(k\),使得\(\begin{cases}x=2021k-n\\y=n - 2020k\end{cases}\) ,且 \(x,y \geq 0\)。
转换后,也就是我们需要证明的:\(2020k \leq n \leq 2021k\)。
判定
方法1
\(n \leq 10^6\),这个数据规模小的一,不管是每次都暴力判断,或者先预处理后回答询问,复杂度都完全可以接受。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin>>T;
while (T--) {
int n;
cin>>n;
bool flag = false;
for (int k = 1; k <= 600; ++k)
if (2020 * k <= n && n <= 2021 * k) {
flag = true;
break;
}
cout<<(flag ? "YES" : "NO")<<endl;
}
return 0;
}
方法2
我们将 \(n\) 变成 \(2020a+b\) 的形式,即 \(a=n/2020\),\(b=n\%2020\) 。
如果 \(n\) 符合要求,那么显然 \(a \geq 1\) 且 \(0 \leq b \leq a\) 。也就是说,我们只需要比较 \(n/2020\) 和 \(n\%2020\) 的大小即可。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int T;
cin>>T;
while (T--) {
int n;
cin>>n;
cout<<((n/2020 >= n%2020) ? "YES" : "NO")<<endl;
}
return 0;
}
C题 Ball in Berland(组合数学)
给定 \(T(T \leq 10^4)\) 组数据。
每个班级中有 \(a\) 个男生和 \(b\) 个女生。总计 \(k\) 组关系,每组关系表明某一个男生和某一个女生可以组队。现在圣诞节要到了,老师需要选出两对男生女生来组成两个队伍(显然,一个人不可能在两个队里面)。求出可能的方案数。
\(1 \leq a,b,k \leq 2*10^5\)
方法1:组合数学
如果我们不考虑冲突,任选两组关系的话,那么就一共有 \(C_{k}^{2}\) 种选法。
我们不妨从反面切入,看看到底有多少组冲突的选法,然后用总选法减去冲突选法,剩下来的就是合法的选法。
冲突的原因只有一个:两个关系里面包含了同一个人。
那么我们可以从枚举具体的人来入手,对于每个人,如果他/她存在于 \(n\) 个关系之中,那么对于这个人,他就可以产生 \(C_{n}^{2}\) 组冲突(用行话来说,这又叫做贡献)。对于这种枚举方法,显然可以做到不重不漏。
假设第 \(i\) 个人(女生编号设为\([a + 1,a+b]\))存在于 \(m[i]\) 组关系之中,那么总冲突数目为\(\displaystyle\sum_{i=1}^{a+b} C_{m[i]}^2\) 。
所以答案 \(ans = {C_{k}^{2}}-\displaystyle\sum_{i=1}^{a+b} C_{m[i]}^2\)
注意了,记得开 long long,不光是算 \(C_k^2\) 的时候要记得用,后面的那个 \(m\) 数组也要开(记住这一点,我就是因为这玩意 \(WA\) 了不知道多少发)。干脆点的话,最好专门写一个算组合数的函数,不容易出错。
整体复杂度:\(O(Tk)\)(实际上由于题目表明总数据量有限制,实际上的复杂度仅有\(O(k)\))(我们默认下 \(k \geq a,b\))
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int a, b, k, m[N], n[N];
long long C2(long long x) {
if (x < 2) return 0;
return x * (x - 1) / 2;
}
int main()
{
int T;
scanf("%d", &T);
while (T--) {
int a, b, k, tmp;
scanf("%d%d%d", &a, &b, &k);
memset(m, 0, sizeof(int) * (a + 1));
memset(n, 0, sizeof(int) * (b + 1));
for (int i = 1; i <= k; ++i) {
scanf("%d", &tmp); ++m[tmp];
}
for (int i = 1; i <= k; ++i) {
scanf("%d", &tmp); ++n[tmp];
}
long long ans = C2(k);
for (int i = 1; i <= a; ++i) ans -= C2(m[i]);
for (int i = 1; i <= b; ++i) ans -= C2(n[i]);
printf("%lld\n", ans);
}
return 0;
}
D题 Cleaning the Phone(前缀和,二分)
给定 \(T(T \leq 10^4)\) 组数据。
手机上有 \(n(n \leq 2*10^5)\) 个应用,第 \(i\) 个应用的占用内存为 \(a_i\),重要程度为 \(b_i\)( \(b_i\) 值仅为 \(1\) 或 \(2\))。现在我们需要删除一些应用,希望在释放至少 \(m(m \leq 10^9)\) 大小的内存的情况下,使得这些被删除应用的重要程度之和最小。如果无论怎么删都没法释放期望大小的空间,输出 \(-1\) 即可。
显然,我们可以将应用按照重要程度分成两类,对于每一类里面的应用,肯定是尽量删掉那些消耗内存比较多的,我们将他们从大到小排个序。
贪心吗?看数据规模好像确实可以,但是对于几种容易想出来的贪心策略,我们都可以构造出一些情况卡过去,pass。
背包?有点像,但是这个数据规模有亿点大了,没法跑过去,pass。
好家伙,那就只能枚举了。反正我们已经排好序了,直接从前到后枚举就行了。我们可以尝试着先构造出一个前缀和出来, \(s1[i]\) 表示前 \(i\) 个一类应用的占用内存之和, \(s2[i]\) 表示前 \(i\) 个二类应用占用内存之和。那么我们只需要找出 \(x,y\) ,使得在 \(s1[x]+s2[y] \geq m\) 的前提下令 \(x+2*y\) 最小。
如果直接暴力枚举的话,复杂度是 \(O(n^2)\) ,显然会 T 飞。我们考虑下咋优化吧。
很显然,这个 \(s\) 数组是单调递增的,如果 \(s1[x]+s2[y] \geq m\),显然对于 \(k \geq y\),都有\(s1[x]+s2[k] \geq m\)。既然如此,答案具有单调性质,那我们可以找出二分策略:枚举 \(x\),然后二分 \(y\),使得 \(s2[y]\geq m-s1[x]\)即可。这样的话,我们便可以将答案复杂度压到了 \(O(n\log n)\),达到题目要求,成功 \(AC\)。
#include<bits/stdc++.h>
using namespace std;
const int N = 200010;
int n, m, a[N], cnt1, cnt2, arr1[N], arr2[N];
//前缀和一定要记得开long long!(十年OI一场空,不开long long见祖宗)
long long s1[N], s2[N];
int solve()
{
scanf("%d%d", &n, &m);
for (int i = 1; i <= n; ++i) scanf("%d", &a[i]);
cnt1 = cnt2 = 0;
for (int i = 1; i <= n; ++i){
int tmp;
scanf("%d", &tmp);
if (tmp == 1) arr1[++cnt1] = a[i];
else arr2[++cnt2] = a[i];
}
//排序
sort(arr1 + 1, arr1 + cnt1 + 1, greater<int>());
sort(arr2 + 1, arr2 + cnt2 + 1, greater<int>());
//前缀和
for (int i = 1; i <= cnt1; ++i)
s1[i] = s1[i - 1] + arr1[i];
for (int i = 1; i <= cnt2; ++i)
s2[i] = s2[i - 1] + arr2[i];
if (s1[cnt1] + s2[cnt2] < m) return -1;
int ans = 1e9 + 10;
for (int x = 0; x <= cnt1; ++x) {
//一组特判,保证我的二分或着lower_bound肯定能找到解
if (s1[x] + s2[cnt2] < m) continue;
//我自己写的二分,但是STL里面有更方便的lower_bound,就不重复造轮子了
/*
int l = 0, r = cnt2 + 1, y;
while (l < r) {
int mid = (l + r) >> 1;
if (s2[mid] >= m - s1[x]) r = mid;
else l = mid + 1;
}
y = l;
*/
//注意,y可以为0,所以lower_bound的左端点是s2而不是s2+1(还好样例就能测出这个bug)
int y = lower_bound(s2, s2 + cnt2 + 1, m - s1[x]) - s2;
ans = min(ans, x + 2 * y);
}
return ans;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
printf("%d\n", solve());
return 0;
}
E题 Advertising Agency(贪心,组合数学)
给定 \(T(T \leq 10^4)\) 组数据。
现有 \(n(n \leq 1000)\) 个正整数,我们要求从中选取 \(k\) 个数,要使得其总和尽量大。问一共有几种选法。
答案可能过大,所以要求对 \(10^9+7\) 取模。
总和尽量大,显然我们应该贪心的选择,将他们排个序,尽量选那些大的。
如果这些数字各不相同,那么答案方案数显然只是 \(1\),所以我们思考,什么时候会出现多种方案的情况呢?
观察一组样例:\(n=4.k=3.a=[3,2,1,1]\),最大值显然为6,有两组方案。
我们可以发现不同方案的来源,对于一些数字(也就是排在入选方案里面最末尾的那些数字),因为他们的数量过多,导致了我们可以从中任选一些,而这任选的方案数也就是答案。
那么我们也就可以得到解题的两个步骤了:
-
确定可选数目 \(a\) 和需要选择的数目 \(b\)。
-
算出组合数 \(C_a^b\) 对 \(10^9+7\) 的值。
组合数我觉得用递推来预处理一下比较适合(主要是我也不会写分数取模)。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
//C
const long long mod = 1e9 + 7;
long long C[N][N];
//
int n, k, a[N];
long long solve()
{
scanf("%d%d", &n, &k);
for (int i = 1; i <= n; ++i)
scanf("%d", &a[i]);
sort(a + 1, a + n + 1, greater<int>());
int l = k, r = k;
//注意,这里一定要加上边界的判断
//我之前也觉得没有必要,因为a[1]=a[n+1]=0
//但是这是多组数据,我并没有每次memset,所以a数组里面会有上一次的残留
//例如上一次数组a 里面是 3 2 1 1 1,这一次里面是 3 2 1 1
//我们以为 a[5]=0,实际上a[5]=1,这时候就会导致WA
//所以对于多组数据,我们一定要严格管控,防止上一组数据的残留对下一组造成影响
while (a[l - 1] == a[k] && l > 1) --l;
while (a[r + 1] == a[k] && r < n) ++r;
return C[r - l + 1][k - l + 1];
}
int main()
{
for (int i = 1; i <= 1000; ++i)
for (int j = 0; j <= i; ++j)
if (j == 0 || j == i) C[i][j] = 1;
else C[i][j] = (C[i-1][j-1] + C[i-1][j]) % mod;
int T;
scanf("%d", &T);
while (T--)
printf("%lld\n", solve());
return 0;
}
F题 Unusual Matrix(数学,枚举)
给定 \(T(T \leq 1000)\) 组数据。
有 \(2\) 个 \(n*n\) 大小的01矩阵(\(n \leq 1000\)),分别记作 \(A\) 和 \(B\) 。
我们有两种操作:
选择矩阵的某一行,对该行的每一个元素和 \(1\) 进行异或(将这一行所有元素全部反转)
选择矩阵的某一列,对该列的每一个元素和 \(1\) 进行异或(将这一列所有元素全部反转)
判断矩阵 \(A\) 能否经过多次操作变为 \(B\)。
我们可以对 \(A\) 和 \(B\) 进行运算,构成新矩阵 \(C\),相同位置为\(0\),不同位置为 \(1\)。那么就变化为:能否将矩阵 \(C\) 经过多次操作全部置为 \(0\) 。
另外注意到,同一操作进行两次相当于没有操作过,这就意味着,对于某一格而言,操作在其上面的行列操作,各不会超过一次。
枚举吗?一共 \(n\) 行 \(n\) 列,要枚举的话,每组数据的复杂度都是 \(O(2^{2n}+n^2)\),直接T飞,不谈。
数学知识?我想着镜像翻转,奇偶性判断啥的,一段时间也没有想出来啥思路,就在那一直僵着。
苦思冥想,我突然在枚举这条线上有了一个大突破:我们已知 \(A[1][1]\) 的值,如果是 \(0\),那么所在行和列有且只有一个会被反转。反之,要么都没有反转,要么都被反转了。
看起来似乎没啥用(我一开始也这么觉得的),但是如果我们细细挖下去,会发现一个细思恐极的现实:如果我们仅仅根据第一行和第一列各自有没有反转,外加 \(A\) 数组中第一行和第一列的数据,就足够构造出来所有行和列的反转情况了。在这种情况下,我们根据这些反转情况重新构造出一个新矩阵 \(D\),如果 \(C\) 和 \(D\) 相等,那么这种构造方式就是合法的,也就是说,我们的矩阵 \(C\) 确实可以经过多次反转操作全部置为 \(0\)。反之,如果枚举的两种情况都无法成功,那就说明 \(C\) 无法通过这些操作全部置为 \(0\) 了。
#include<bits/stdc++.h>
using namespace std;
const int N = 1010;
int n, A[N][N];
int dx[N], dy[N];
bool dfs(int state1, int state2) {
dx[1] = state1, dy[1] = state2;
for (int i = 2; i <= n; ++i)
dx[i] = A[i][1] ^ dy[1];
for (int i = 2; i <= n; ++i)
dy[i] = A[1][i] ^ dx[1];
for (int i = 1; i <= n; ++i)
for (int j = 1; j <= n; ++j)
if (A[i][j] != (dx[i] ^ dy[j])) return false;
return true;
}
bool solve()
{
scanf("%d", &n);
char s[N];
for (int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
for (int j = 1; j <= n; ++j)
A[i][j] = s[j] - '0';
}
for (int i = 1; i <= n; ++i) {
scanf("%s", s + 1);
for (int j = 1; j <= n; ++j)
A[i][j] = (A[i][j] == s[j] - '0') ? 0 : 1;
}
bool ans;
if (A[1][1] == 0) ans = dfs(0, 0) || dfs(1, 1);
else ans = dfs(1, 0) || dfs(0, 1);
return ans;
}
int main()
{
int T;
scanf("%d", &T);
while (T--)
puts(solve() ? "YES" : "NO");
return 0;
}
G题 Strange Beauty
给定 \(T(T \leq 10)\) 组数据。
有 \(n(n \leq 2*10^5)\) 个正整数,问至少去掉多少个数,可以让剩下来的所有数字中,两两都具有整除关系?
显然,我们最好给他们排一个序,这样就可以直接从小到大直接考虑了。
很显然的,我们可以构造出这样一个 \(DP\) 方程:记 \(dp[i]\) 为选取 \(a[i]\) 为整个剩下数列的最后一项时,这个剩下的序列的可能的最大长度。显然的,我们有转移关系:
\[dp[i]=\{\max\limits_{1\leq k <i,a[i]\%a[k]=0}dp[k]\}+1 \]遗憾的是,线性递推的话,时间复杂度是 \(O(n^2)\) 的,会超时,所以我们必须想办法优化。
这里借鉴了程佬的思路,有兴趣的可以去他的博客看一下:链接
简言之,对于数列中的任意一项,他的最大值的规模和 \(n\) 相当,都是 \(2*10^5\)。我们注意到,枚举一个数的所有因子,其复杂度为 \(O(\sqrt{n})\)。我们只需要一开始再用一个数组,记录下某个数是否出现在数组里面,如果出现了,下标是多少,那么我们对于每一个数,就可以在 \(O(\sqrt{n})\) 的复杂度内找出其所有因子,然后判断是否在数组中,并且尝试更新 \(DP\) 数组。这样的话,我们就可以将每组数据的时间复杂度压进 \(O(n\sqrt{n})\),完全可以 \(AC\)。
另外,这里有几个小注意点:
- 有可能会有重复数字。处理办法多种多样,最好的方法就是去重,然后再找一个数组记录一下这个数出现了几遍。
- 枚举因数的时候,记得不要把自己包含进去。
下面给出代码:
//等CF恢复正常,能够正常测题的时候,我就把AC代码贴上去
标签:693,10,int,CF,long,leq,枚举,补题,我们 来源: https://www.cnblogs.com/cyhforlight/p/14336570.html