题目链接: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;
}