Codeforces Round #697 (Div. 3) 解题报告

题目链接:https://codeforces.com/contest/1475

震惊!2021年1月25日晚上23点,3万余人聚集在codeforces集体上分!导致服务器卡顿,最终整场比赛不计分(⊙﹏⊙)

A. Odd Divisor

题目大意

t组数据,每组给你一个整数n,问你n有没有非1的奇数因子,有输出YES,否则输出NO

范围:(1≤t≤1e4,2≤n≤10^14)

思路

位运算。因为他不看偶数因子,那么可以一直除2,然后看最后的n是不是1,如果是1,那么说明都是偶数因子,否则那么就有奇数因子了

也可以直接看二进制中1的个数;

也可以直接lowbit运算,取最右边1的位置开始直到最后这个二进制代表的数,然后直接跟原来本身比大小,就知道这个1的位置是不是最前面了,如果是最前面,那么就等于本身,也就是说他没有奇数因子,反之则有奇数因子。

ac代码

暴力除2版本

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
	int t; scanf("%d", &t);
	while(t --){
		ll n; scanf("%lld", &n);
		while(n % 2 == 0) n >>= 1;
		if(n == 1) puts("NO");
		else puts("YES");
	}
	return 0;
}

lowbit版本

#include<bits/stdc++.h>
using namespace std;
#define ll long long
#define lowbit(x) x & (-x)
int main(){
    int t; scanf("%d", &t);
    while(t --){
        ll n; scanf("%lld",&n);
        ll d = lowbit(n);
        if(d == n) puts("NO");
        else puts("YES");
    }
    return 0;
}

B. New Year's Number

题目大意

t组数据,每组给个整数n,问你n能不能是a个2020和b个2021相加的结果。

范围:(1≤t≤1e4,1≤n≤10^6) 

思路

n=a*2020+b*2021可以用结合律转化成n=2020*(a+b)+b,那么这里的a+b就变成了n/2020,b就变成了n%2020。这个等式的转化条件是b>=a,因为b足够多才能合并成(a+b)*2020呀。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
int main(){
	int t; scanf("%d", &t);
	while(t --){
		int n; scanf("%d", &n);
		int a1 = n % 2020;
		int a2 = n / 2020;
		if(a1 <= a2) puts("YES");
		else puts("NO");
	}
	return 0;
}

C. Ball in Berland

题目大意

有a位男士和b位女士参加舞会,有k对关系,表示两个人可以共舞。然后要取出四个人,组成两支舞队,有几种方式?

t组数据,每组第一行三个数a,b,k,第二行k个数表示男士的序号ai,第三行bi表示女士的序号,ai和bi可以共舞。

范围:(1≤t≤1e4,1≤a,b,k≤2e5,1≤ai≤a,1≤bi≤b)

思路

开两个vector数组存边,v1[i]表示可以与男士i共舞的女士的编号们,v2[i]表示可以与女士i共舞的男士的编号们。

然后遍历v1[i],it是i的舞伴,那么v1[i].size()+v2[it].size()-1就是与这两位相关的所有人,那么这些边不能再取了,因为取了的话那么会有点重复,要么是点i,要么是点it。剩下的就是k-(v1[i].size()+v2[it].size()-1),这些关系与i和it没有任何重复,可以作为第二支舞队,那么遍历累加即可,由于第一对第二对没有顺序,所以最后的结果记得除2。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
vector<int> v1[maxn], v2[maxn];
int a[maxn], b[maxn];
int main(){
    int t; scanf("%d", &t);
    while(t --){
        int n, m, k;
        scanf("%d%d%d", &n, &m, &k);
        for(int i = 1; i <= n; i ++) v1[i].clear();
        for(int i = 1; i <= m; i ++) v2[i].clear();
        for(int i = 1; i <= k; i ++){
            scanf("%d", &a[i]);
        }
        for(int i = 1; i <= k; i ++){
            scanf("%d", &b[i]);
            v1[a[i]].push_back(b[i]);
            v2[b[i]].push_back(a[i]);
        }
        ll ans = 0;
        for(int i = 1; i <= n; i ++){
            for(auto it : v1[i]){ //遍历vector v1[i]
                ans += k - (v1[i].size() + v2[it].size() - 1);
            }
        }
        cout << ans / 2 << endl;
    }
    return 0;
}

D. Cleaning the Phone

题目大意

清内存,每个应用都有ai的内存和bi的价值。

t组数据,每组两个整数n,m,分别表示n个应用和需要清理s的内存,接着一行是n个数表示应用内存ai,接着一行是n个数表示应用价值bi,问你在清理的内存不低于m的前提下,使得删掉的价值最小,输出这个最小值。

范围:(1≤t≤1e4,1≤n≤2e5,1≤m≤1e9,1≤ai≤1e9,1≤bi≤2) 

思路

首先开两个数组分别存价值是1,2的所有应用,然后按照所有应用内存大小从大到小排。最后选的肯定是a个价值为1的和b个价值为2的。如果要选a个,肯定要a个内存最大的之和,所以对已经排序过的数组求个前缀和,那么对于前缀和suma[i] 就表示取i个价值为1的最大内存,那么还剩s-suma[i],就从价值为2的里面取,这里也做同样处理,求前缀和sumb,然后二分查找第一个大于等于s-suma[i]的下标,如果下标超范围表示不存在,如果存在id,则可以删除不低于s的内存,然后他的价值和就是i + id * 2,更新这个最小值就好了。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
const int inf = 0x3f3f3f3f;
int w[maxn], v[maxn];
int main(){
    int t; cin >> t;
    while(t --){
        vector<ll> v1, v2; //v1存价值为1的应用的内存,v2存价值为2的应用的内存
        int n, s;
        ll sum = 0;
        cin >> n >> s;
        for(int i = 1; i <= n; i ++){
            cin >> w[i];
            sum += w[i];
        }
        for(int i = 1; i <= n; i ++){
            cin >> v[i];
            if(v[i] == 1) v1.push_back(w[i]);
            else v2.push_back(w[i]);
        }
        if(sum < s){ //如果所有应用内存加起来都低于s,那直接出-1
            puts("-1");
            continue;
        }
        sort(v1.begin(), v1.end(), greater<int>()); //按从大到小排
        sort(v2.begin(), v2.end(), greater<int>());
        v1.insert(v1.begin(), 0); //选0个应用的时候,内存是0
        v2.insert(v2.begin(), 0);
        for(int i = 1; i < v1.size(); i ++) v1[i] += v1[i - 1]; //求前缀
        for(int i = 1; i < v2.size(); i ++) v2[i] += v2[i - 1];
        int ans = inf; 
        for(int i = 0; i < v1.size(); i ++){
            int id = lower_bound(v2.begin(), v2.end(), s - v1[i]) - v2.begin(); //找剩余内存的位置
            if(id < v2.size()) ans = min(ans, i + id * 2); //更新最小值
        }
        cout << ans << endl;
    }
    return 0;
}

E. Advertising Agency

题目大意

t组数据,每组数据给你两个数n,k,然后是n个数ai,然后问你取k个数能得到最大值,有几种取法(mod 1e9+7)

范围(1≤t≤1000 ,1≤k≤n≤1000) 

思路

组合数。最大值肯定从大到小取得,用map维护数得个数,如果取到ai了,一共有d个ai, 还有m个数好取,那么就有C(d,m)种取法。

n范围不大,可以根据杨辉三角初始化组合数数组c。c[i][j]=c[i-1][j]+c[i-1][j-1]。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
int c[1005][1005];
void init(){
    c[0][0] = 1;
    for(int i = 1; i <= 1000; i ++){
        c[i][0] = 1;
        for(int j = 1; j <= i; j ++) c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
    }
}
int main(){
    init();
    int t; scanf("%d", &t);
    while(t --){
        map<int, int, greater<int> >mp; //按照第一键值从大到小排
        int n, m;
        scanf("%d%d", &n, &m);
        while(n --){
            int x; scanf("%d", &x);
            mp[x] ++;
        }
        for(auto it : mp){ //从大到小取
            int d = it.second;
            if(d >= m) { // m不够,才有取法
                printf("%d\n", c[d][m]);
                break;
            }
            m -= d; // m足够,那么取完d
        }
    }
    return 0;
}

F. Unusual Matrix

题目大意

t组数据,每组数据一个n,然后两个n*n的矩阵A,B,值是0或者1。有两种操作,分别是选择一行翻转(0变1,1变0),选择一列翻转。问你能否通过这两种操作将矩阵A变成矩阵B。

范围:(1≤t≤1000,1≤n≤1000)

思路

构造题。两种翻转,先后顺序对结果不会产生影响。首先定义一个二维矩阵C表示A[i][j]与B[i][j]是否相同,不相同是1,相同时0,问题就转换成将C矩阵变成零矩阵。然后看每行的第一位数是不是1,如果是1,那么就进行行变换,这样的话行变换结束,接着就看每行是否相同即可,如果都相同,那么就可以通过列变换消除所有不同啦。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 1005;
string a[maxn], b[maxn], c[maxn];
int main(){
    int t; cin >> t;
    while(t --){
        int n; cin >> n;
        for(int i = 0; i < n; i ++) cin >> a[i];
        for(int i = 0; i < n; i ++) cin >> b[i];
        for(int i = 0; i < n; i ++) {
            c[i] = "";
            for(int j = 0; j < n; j ++) {
                if(a[i][j] == b[i][j]) c[i] += "0";
                else c[i] += "1";
            }
            if(c[i][0] == '1'){
                for(int j = 0; j < n; j ++){ //行变换,1-1=0,1-0=1 实现翻转
                    c[i][j] = '1' - (c[i][j] - '0');
                }
            }
        }
        int flag = 1;
        for(int i = 0; i < n; i ++){
            if(c[i] != c[0]){ //判断每行是否相同
                flag = 0;
                break;
            }
        }
        if(flag) cout << "YES" << endl;
        else cout << "NO" << endl;
    }
    return 0;
}

G. Strange Beauty

题目大意

完美序列:序列中任意取两个值a,b,都存在a能整除b或者b能整除a。

然后给你长度为n的序列,问你最少删除几个数使得剩下的序列是完美序列。

范围:(1≤t≤10,1≤n≤2e5,1≤ai≤2e5)

思路

dp。题意可以转化为求最长完美序列,递推式的话类似埃式筛。

假设这里的完美序列都是升序的。dp[i]表示末尾是i的完美序列的最大长度,然后cnt[i]表示i的个数,然后下一个的值j应该是i的倍数,因为他只与i有关了,那么就倍增遍历。

往后递推,更新后值,然后i能当末尾的话,j也能当末尾啦,所以说dp[j]可以等于dp[i]的,然后dp[j]取最大的dp[i],也就是dp[j]=max(dp[i])(j%i==0)。最后更新dp[i]的最大值就好了。

最后答案是n-最大值,因为求的是删除的数。

ac代码

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int maxn = 2e5 + 5;
int dp[maxn], cnt[maxn];
int main(){
    int t; scanf("%d", &t);
    while(t --){
        int n, mx = -1; scanf("%d", &n);
        for(int i = 1; i <= n; i ++){
            int x; scanf("%d", &x);
            cnt[x] ++;
            mx = max(mx, x);
        }
        int ans = 0;
        for(int i = 1; i <= mx; i ++){
            dp[i] += cnt[i]; //加上i的个数,因为后面递推的时候没有算上他的个数
            for(int j = i * 2; j <= mx; j += i){
                dp[j] = max(dp[j], dp[i]);
            }
            ans = max(ans, dp[i]);
        }
        printf("%d\n", n - ans);
        for(int i = 1; i <= mx; i ++){
            cnt[i] = dp[i] = 0;
        }
    }
    return 0;
}

 

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值