件名リンク:https://codeforces.com/contest/1475
ショック!2021年1月25日夕方23時、3万人以上がコードフォースに集まりました!サーバーをフリーズさせると、最終的にゲーム全体がスコアリングされなくなります(⊙﹏⊙)
A.奇数除数
トピック
データのTグループ。各グループは整数nを返し、nに1以外の奇数の因数があるかどうかを尋ね、YESを出力します。そうでない場合はNOを出力します。
範囲:(1≤t≤1e4、2≤n≤10 ^ 14)
アイデア
ビット演算。彼は偶数の因数を見ないので、常に2で割り、最後のnが1であるかどうかを確認できます。1の場合、すべての因数が偶数の因数であり、そうでない場合は奇数の因数があります
バイナリの1の数を直接確認することもできます。
また、ロービット演算を直接実行し、右端の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;
}
ロービットバージョン
#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.年末年始
トピック
データのTグループ、各グループには整数nが与えられ、nが2020とb2021を追加した結果である可能性があるかどうかを尋ねます。
範囲:(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.バーランドのボール
トピック
男性と女性が参加し、k組あり、2人で一緒に踊ることができます。それから4人で2つのダンスチームを結成します。
データのtグループでは、各グループの最初の行に3つの番号a、b、およびkがあり、2番目の行のk番号は男性のシリアル番号aiを表し、3番目の行biはシリアル番号を表します。女性の、そしてaiとbiは一緒に踊ることができます。
範囲:(1≤t≤1e4、1≤a、b、k≤2e5、1≤ai≤a、1≤bi≤b)
アイデア
2つのベクトル配列を開いて保存します。v1[i]は男性iと踊ることができる女性の数を表し、v2 [i]は女性iと踊ることができる男性の数を表します。
次に、v1 [i]をトラバースします。これは、iのダンスパートナーです。次に、v1 [i] .size()+ v2 [it] .size()-1が、これら2つに関連するすべての人であり、これらのエッジを取得できなくなります。 、それが取られた場合、それは少し繰り返しになるので、ポイントiまたはポイントします。残りはk-(v1 [i] .size()+ v2 [it] .size()-1)であり、これらの関係はiとitとの繰り返しがなく、2番目のダンスチームとして使用できます。ただし、トラバーサルの累積は次のようになります。ただし、最初のペアと2番目のペアには順序がないため、最終結果を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.電話の掃除
トピック
メモリをクリアします。各アプリケーションにはaiメモリとbi値があります。
データのtグループ、それぞれn個のアプリケーションとクリーンアップが必要なメモリを表す2つの整数n、mの各グループs、次の行はアプリケーションメモリaiを表すn個の数字、次の行はアプリケーション値を表すn個の数字ですbi、どこで質問するかを尋ねるクリーンアップされたメモリがm以上であるという前提で、削除の値が最小化され、最小値が出力されます。
範囲:(1≤t≤1e4,1≤n≤2e5,1≤m≤1e9,1≤ai≤1e9,1≤bi≤2)
アイデア
まず、2つの配列を開いて、それぞれ値が1、2であるすべてのアプリケーションを格納し、次にすべてのアプリケーションのメモリサイズに従ってそれらを大きいものから小さいものへと並べ替えます。最終的な選択は、値1およびb値2でなければなりません。を選択する場合は、メモリの合計が最大である必要があるため、並べ替えられた配列のプレフィックスの合計を見つけます。次に、プレフィックスとsuma [i]の場合、値が1のi個の最大のメモリを取得することを意味します。次に、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.広告代理店
トピック
データのTグループ、データの各グループは2つの数値n、k、次にn個の数値aiを与え、最大値を取得するためにk個の数値を取るように求めます。選択する方法はいくつかあります(mod 1e9 + 7)
範囲(1≤t≤1000、1≤k≤n≤1000)
アイデア
組み合わせの数。最大値は大きいものから小さいものまで取得する必要があり、その数はマップによって維持されます。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.異常なマトリックス
トピック
データのグループはt個あり、データの各グループにはnがあり、次に2つのn * n行列AとBがあり、値は0または1です。反転する行を選択する操作(0から1、1から0)と、反転する列を選択する操作の2つがあります。これらの2つの操作を通じて、行列Aを行列Bに変換できるかどうかを尋ねます。
範囲:(1≤t≤1000、1≤n≤1000)
アイデア
構造の質問。2種類の反転、順序は結果に影響しません。まず、A [i] [j]とB [i] [j]が同じかどうかを示す2次元行列Cを定義します。同じでない場合は、1であり、同じ場合は、問題はゼロ行列に変換されます。次に、各行の最初の桁が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 ++) {
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.奇妙な美しさ
トピック
完全なシーケンス:シーケンス内の任意の2つの値aとbを取り、bで割り切れるまたはaで割り切れるbのいずれかがあります。
次に、長さnのシーケンスを指定し、残りのシーケンスを完全なシーケンスにするために、少なくともいくつかの番号を削除するように依頼します。
範囲:(1≤t≤10、1≤n≤2e5、1≤ai≤2e5)
アイデア
dp。質問の意味は、再帰形式のふるいに似た、最長の完全なシーケンスを見つけることに変換できます。
ここでの完全なシーケンスは昇順であると想定します。dp [i]は、最後にiが付いた完全なシーケンスの最大長を表し、cnt [i]はiの数を表し、次の値jはiにのみ関連しているため、iの倍数である必要があります。 、次にトラバーサルを2倍にします。
再帰的に値を更新し、iが終わりになる可能性がある場合、jも終わりになる可能性があるため、dp [j]はdp [i]と等しくなり、dp [j]は最大のdp [i]を取ります。つまり、dp [j] = max(dp [i])(j%i == 0)です。最後に、dp [i]の最大値を更新します。
必要なのは削除された番号であるため、最終的な答えはn-maximumです。
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;
}