Codeforces Round #697(div3)

A. Odd Divisor

  题意就是判断这个数是否有大于等于\(3\)的奇数因子。
  考虑用二进制来表示一个数,可以举例去找规律。\((110)_{2} = (6)_{10}\)\((1000)_{2} = (8)_{10}\), \((1001)_{2} = (9)_{10}\),\(\dots\).通过这些例子不难发现只要一个数\(x\)的二进制表示下,只要末尾有\(0\),那么它一定可以被\(2\)整除,所以如果末尾有\(0\)的话,直接右移就可以了,一直到\(x'\)的二进制表示下最后一位是\(1\)的时候停止,此时的\(x'\)一定是一个奇数,只要判断最后这个\(x'\)是否等于\(1\)就行了。所以总的代码就是

int a; std::cin>>a;
while(a % 2 == 0) a /= 2;
if(a == 1) puts("NO");
else puts("YES");

但是我们再思考一步,如果没有大于等于\(3\)的奇数因子的话,那么就是这个数在一直右移的过程中,最后得到的结果是\(1\),那么它就可以表示成\(x = 1 * 2 ^ {k}\)。基于这个结论,我们可以每次\(O(1)\)的判断这个数是不是满足题意得

i64 n;
std::cin >> n;
std::cout << ((n & (n - 1)) ? "YES\n" : "NO\n");

B. New Year's Number

  题意是让我们判断这个数能不能被若干个\(2020\)和若干个\(2021\)来表示。
  如果一个数\(x\)可以通过题目得意思得到得话,那么就是满足这个式子\(x = cnt_1 * 2020 + cnt_2 * 2021\),可以转化一下式子,也就是\(x = cnt_1 * 2020 + cnt_2 * (2020 + 1)\),接下来我们合并一下同类项,就可以得到\(x = (cnt_1 + cnt_2) * 2020 + cnt_2\),只要我们能够满足\((cnt_2 + cnt_1) * 2020 \leq x\)就可以了

int n; std::cin >> n;
int cnt = n / 2020;
int res = n - cnt * 2020;
std::cout << (res <= cnt ? "YES\n" : "NO\n");

C. Ball in Berland

  题意是给定了男生和女生之间是否能够配对的关系,让我们求出至少能够配成两对的方案数
  可以因为男生和女生存在配对的关系,可以联想到连边的操作,也就是如果\(u\)\(v\)能够配对成功,就将\(u\)\(v\)连边。因为只需要找出两对男女就行了,所以可以先固定一对,然后在再看剩下的人中能不能再配对出一对就可以了。对于这样的问题,正面去考虑第二对是否存在的话会比较麻烦,所以考虑正难则反的策略,去考虑此时固定的第一对会影响到哪些对,使这些对能够陪对的男女之间不能配对成功,可以通过\(u\)\(v\)所连的边来进行表示,选定了\(u\),\(v\)的话就会影响到\(du[u] + du[v] - 1\)种关系的,所以选定\(u\),\(v\)这一对关系的话,可以得到\(k - du[u] - du[v] + 1\)种关系满足题意。这样去求解的话所有的情况会多算一次,所以最后要除以\(2\).

int n, m, q;
std::cin >> n >> m >> q;
std::vector<int> a(q), b(q);
std::map<int,int> ma, mb;

rep(i,0,q) {
	std::cin >> a[i];
	ma[a[i]] ++;
}
rep(i,0,q) {
	std::cin >> b[i];
	mb[b[i]] ++;
}

i64 ans = 0;
rep(i,0,q) {
	ans += q - ma[a[i]] - mb[b[i]] + 1;
}
std::cout << ans / 2 << "\n";

D. Cleaning the Phone

  题意是我们对每一个应用后它们自己相对应得重要程度和内存大小,在至少删除若干个内存大小总和为\(m\)的应用后,使得剩余应用的重要程度最大。
  先考虑无解的情况,当所有的内存之和加起来都要小于\(m\)的话,那就是无解的

if (std::accumulate(v.begin() + 1, v.end(), 0ll) < m) { 
  std::cout << "-1\n"; 
  return ; 
}

  接下来就是考虑在有解的前提下,使得重要程度最大了,对于删除应用的选择的话,可以分为四种:
  第一种:在还没有删除的重要程度为\(1\)的应用中,占用内存最大的应用内存大于此时的\(m\),直接删掉并且结束循环

if (a[l] >= m) {
  ans ++;
  m -= a[l ++ ];
}

  第二种:在第一种情况不成立的前提要,如果重要程度为\(2\)的应用中,占用内存最大的应用删除后可以直接让\(m \leq 0\)那么就删掉它并结束循环

if (b[r] >= m) {
  ans += 2;
  m -= b[r ++ ];
}

  第三种:在前两种情况都不满足的前提下,如果现在剩下的重要程度为1的应用的最大值和次大值之和的贡献要小于重要程度为2的应用的话,优先删除重要程度为\(2\)的应用

if (b[r] > a[l] + a[l + 1]) {
  m -= b[r ++ ];
  ans += 2;
}		

  第四种:直接删除重要程度为\(1\)的应用

void solve() {
	int n, m;
	std::cin >> n >> m;
	std::vector<i64> v(n + 1);
	std::vector<int> p(n + 1);
	for (int i = 1; i <= n; i ++ ) std::cin >> v[i];
	for (int i = 1; i <= n; i ++ ) std::cin >> p[i];

	if (std::accumulate(v.begin() + 1, v.end(), 0ll) < m) { std::cout << "-1\n"; return ; }
	std::vector<int> a(n + 1), b(n + 1);
	int idx = 0, idt = 0;
	rep(i,1,n + 1) {
		if (p[i] & 1) a[ ++ idx] = v[i];
		else b[ ++ idt] = v[i];
	}

	std::sort(a.begin() + 1, a.begin() + 1 + idx, [&] (int x, int y) {
		return x > y;
	});
	std::sort(b.begin() + 1, b.begin() + 1 + idt, [&] (int x, int y) {
		return x > y;
	});
	int l = 1, r = 1;
	int ans = 0;
	while ((l != idx + 1 || r != idt + 1) && m > 0) {
		if (a[l] >= m) {
			ans ++;
			m -= a[l ++ ];
		} else if (b[r] >= m) {
			ans += 2;
			m -= b[r ++ ];
		} else if (b[r] > a[l] + a[l + 1]) {
			m -= b[r ++ ];
			ans += 2;
		} else {
			ans ++ ;
			m -= a[l ++ ];
		}
	}

	std::cout << ans << "\n";
}

E.Advertising Agency

  题意就是在使得签约的博主粉丝数量最多的签约方案有多少种。
  可以将所有的博主按照它们所拥有的粉丝数进行从大到小排序,在排序好了的博主中选择前\(k\)名就一定可以达到最大值,现在要考虑就是方案了。因为在\(a[1 \sim k]\)的序列中,除了最小值以外的所有数都是必须要选上才能使得贡献最大的,所以只需要去考虑最小值的方案选择,那么就只有两种情况了,第一种就是刚好把所有的最小值都包含进了所要的区间中,此时的方案数就是\(1\)。第二种就是还有部分的最小值在这个区间外面,那么就从所有的这些数中选出\(need\)个那么方案就是\(\binom{total}{need}\)

void solve() {
	int n, k;
	std::cin >> n >> k;
	std::vector<int> a(n);
	for(int i = 0; i < n; i++ ) std::cin >> a[i];
	std::sort(a.rbegin(), a.rend());
	int mid = a[k - 1];
	int cnt = 0, cur = 0;
	for (int i = 0; i < n; i++ ) cnt += a[i] == mid, cur += (a[i] == mid && i < k);
	Z ans = binom(cnt, cur);
	std::cout << ans.val() << "\n";
}

F. Unusual Matrix

  题意是给我们两个\(01\)矩阵,判断能不能通过整行异或或者整列异或次操作使得矩阵\(A\)和矩阵\(B\)相等。
  因为是整行整列的异或,所以我们可以把问题简化到判断每一行的第一列和每一列的第一行是否进行异或操作就行了,可以让矩阵\(A\)和矩阵\(B\)同时操作,按照两种方式选择是否异或的时候直接判断在该位置上是不是有\(1\),是就异或不是就不异或,这样我们就可以让\(A\)\(B\)的第一行和第一列相同了,如果这个时候两个矩阵相同就是可以否则不行

void solve() {
    int n;
    std::cin >> n;
    std::vector<std::string> s(n), e(n);
    rep(i,0,n) std::cin >> s[i];
    rep(i,0,n) std::cin >> e[i];

    rep(i,0,n) {
        if (s[i][0] == '1') 
            rep(j,0,n) s[i][j] =  s[i][j] ^ '1' ^ '0';
        if (e[i][0] == '1') 
            rep(j,0,n) e[i][j] = e[i][j] ^ '1' ^ '0';
    }

    rep(i,0,n) {
        if (s[0][i] == '1') 
            rep(j,0,n) s[j][i] = s[j][i] ^ '1' ^ '0';
        if (e[0][i] == '1') 
            rep(j,0,n) e[j][i] = e[j][i] ^ '1' ^ '0';
    }

    std::cout << (s == e ? "YES\n" : "NO\n");
}

G. Strange Beauty

  题意是给我们一个序列\(a\),要满足在这个序列中,任意一个数\(a_i\)满足小于它的所有数都能够整除\(a_i\)。问最少需要移走多少个元素能够使得序列满足题意
  因为要让删除的次数最小,那么就是求出在这个序列中拥有除数最多的那个数的它有包含的除数个数, 然后用\(n\)减去就可以了。我们可以用一个数去更新它的所有倍数,也就是$$dp[i * k] = \max(dp[i], dp[i * k])$$.

void solve() {
    int n; std::cin >> n;
    std::vector<int> a(n);
    for (int i = 0; i < n; i ++ ) std::cin >> a[i];
    int N = *max_element(all(a));
    std::vector<int> cnt(N + 1), dp(N + 1);
    
    for (int i = 0; i < n; i ++ ) cnt[a[i]] ++;

    for (int i = 1; i <= N; i ++ ) {
        dp[i] += cnt[i];
        for (int j = 2 * i; j <= N; j += i ) 
            dp[j] = std::max(dp[j], dp[i]);
    }

    std::cout << n - *max_element(all(dp)) << "\n";
}
posted @ 2022-05-04 13:30  浅渊  阅读(24)  评论(0)    收藏  举报