Codeforces Round #697 (Div. 3)A-G題解
技術標籤:演算法入門
A. Odd Divisor
題目連結:點選此處
每個大於等於2的整數都可以劃分為質數的積,然後質數只有2是偶數。所以我們對於一個數除完2,看看是否為1,為1就NO,大於1就是YES。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include <queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e5 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
ll arr[MAXN];
ll sum[ MAXN];
int main() {
int t;
cin >> t;
while (t--) {
cin >> n;
while (n % 2 == 0) {
n /= 2;
}
if (n == 1) { cout << "NO" << endl; }
else cout << "YES" << endl;
}
}
B. New Year’s Number
題目連結:點選此處
觀察一個數是否被 2020 2020 2020和 2021 2021 2021組成,很明顯的是 2021 = 2020 + 1 2021=2020+1 2021=2020+1,所以如果這個數 a i a_i ai符合要求,那麼 a i = 2020 ∗ x + 2021 ∗ y = 2020 ∗ ( x + y ) + y a_i=2020*x+2021*y=2020*(x+y)+y ai=2020∗x+2021∗y=2020∗(x+y)+y,所以取餘2020,會得到 y y y,然後 a i a_i ai減去 2021 ∗ y 2021*y 2021∗y,觀察是否大於等於 0 0 0,並且能被 2020 2020 2020取餘,能的話YES,不能NO。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e5 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
ll arr[MAXN];
ll sum[MAXN];
int main() {
int t;
cin >> t;
while (t--) {
cin >> n;
ll a = n % 2020;
ll sum = n - a * 2021;
if (sum < 0 || sum % 2020 != 0) {
cout << "NO" << endl;
}
else cout << "YES" << endl;
}
}
C. Ball in Berlan
題目連結:點選此處
給我n個數,那麼我們可以得到 ( n − 1 ) ∗ n 2 \frac{(n-1)*n}{2} 2(n−1)∗n組,但是我們要減去不符合的。即出現重複的。這裡我們要知道一個性質。如果 a i a_i ai和 a j a_j aj重複,那麼 b i b_i bi和 b j b_j bj不會重複,因為如果重複,那麼就是同一組了,題意不允許出現相同兩組。
所以我們遍歷陣列到 a i a_i ai時,假設前面有 k 1 k_1 k1個與 a i a_i ai重複, k 2 k_2 k2個與 b i b_i bi重複,那麼 a n s − = k 1 + k 2 ans-=k_1+k_2 ans−=k1+k2。因為這些組不能和 a i , b i {a_i,b_i} ai,bi組一起。對於 a i , b i {a_i,b_i} ai,bi後續的組,如果出現與 a i , b i {a_i,b_i} ai,bi重複,那麼通過後續的組來刪去。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 2e5 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
ll a[MAXN], b[MAXN];
ll a1[MAXN] = { 0 }, b1[MAXN] = { 0 };
ll arr[MAXN];
int main() {
int t;
cin >> t;
while (t--) {
ll k;
cin >> n >> m >> k;
ll ans = (k - 1) * k / 2;
for (int i = 1;i <= k;i++) {
cin >> a[i];
ans -= a1[a[i]];
a1[a[i]]++;
}
for (int i = 1;i <= k;i++) {
cin >> b[i];
ans -= b1[b[i]];
b1[b[i]]++;
}
for (int i = 1;i <= k;i++) {
a1[a[i]] = 0;
b1[b[i]] = 0;
}
cout << ans << endl;
}
}
D. Cleaning the Phone
題目連結:點選此處
c 1 [ i ] c1[i] c1[i]為第 i i i大的代價為1的容量。
c 2 [ i ] c2[i] c2[i]為第 i i i大的代價為2的容量。
d 1 [ i ] d1[i] d1[i]表示花費 i i i代價得到的最大容量後,選了幾個代價為1的。
d 2 [ i ] d2[i] d2[i]表示花費 i i i代價得到的最大容量後,選了幾個代價為2的。
我用的是DP, d p [ i ] dp[i] dp[i]表示花費了 i i i代價,最多能消除多少體積。那麼題目轉化很簡單, d p [ i ] = m a x ( d p [ i − 1 ] + c 1 [ d 1 [ i − 1 ] ] , d p [ i − 2 ] + c 2 [ d 2 [ i − 2 ] ] , d p [ i − 2 ] + c 1 [ d 1 [ i − 2 ] ] + c 1 [ d 1 [ i − 2 ] + 1 ] ) dp[i]=max(dp[i-1]+c1[d1[i-1]],dp[i-2]+c2[d2[i-2]],dp[i-2]+c1[d1[i-2]]+c1[d1[i-2]+1]) dp[i]=max(dp[i−1]+c1[d1[i−1]],dp[i−2]+c2[d2[i−2]],dp[i−2]+c1[d1[i−2]]+c1[d1[i−2]+1]),即現在花費 i i i的最大值只能由花費 i − 2 i-2 i−2的加上一個代價2的或者兩個代價1的,或者花費 i − 1 i-1 i−1的加上一個代價1的。之後得到相應最大值後,把 d 1 [ i ] d1[i] d1[i]和 d 2 [ i ] d2[i] d2[i]隨之更新一下就行。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 5e5 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
ll a[MAXN], b[MAXN];
ll c1[MAXN], c2[MAXN];
ll dp[MAXN];
ll d1[MAXN], d2[MAXN];
bool cmp(ll& a, ll& b) {
return a > b;
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n >> m;
int cnt1 = 0, cnt2 = 0;
int sum = 0;
for (int i = 1;i <= n;i++)cin >> a[i];
for (int i = 1;i <= n;i++) {
cin >> b[i];
sum += b[i];
if (b[i] == 1)c1[++cnt1] = a[i];
else c2[++cnt2] = a[i];
}
sort(c1 + 1, c1 + 1 + cnt1,cmp);
sort(c2 + 1, c2 + 1 + cnt2, cmp);
d1[0] = d2[0] = 1;
dp[0] = 0;
dp[1] = c1[1];
d1[1] = 2;d2[1] = 1;
if (dp[1] >= m) { cout << 1 << endl;
//初始化
d1[0] = d2[0] = dp[0] = dp[1] = d1[1] = d2[1] = 0;
for (int i = 1;i <= cnt1;i++)c1[i] = 0;
for (int i = 1;i <= cnt2;i++)c2[i] = 0;
continue;
}
bool f = false;
for (int i = 2;i <= sum;i++) {
int k = 0;
int tmp = 0;
if (c1[d1[i - 2]] + c1[d1[i - 2] + 1] > c2[d2[i - 2]]) {
tmp = c1[d1[i - 2]] + c1[d1[i - 2] + 1];
dp[i] = dp[i - 2] + tmp;
d1[i] = d1[i - 2] + 2;
d2[i] = d2[i - 1];
}
else {
tmp = c2[d2[i - 2]];
dp[i] = dp[i - 2] + tmp;
d2[i] = d2[i - 2] + 1;
d1[i] = d1[i - 2];
}
if (tmp + dp[i - 2] < dp[i - 1] + c1[d1[i - 1]]) {
tmp = c1[d1[i - 1]];
dp[i] = dp[i - 2] + tmp;
d1[i] = d1[i - 1] + 1;
d2[i] = d2[i - 1];
}
if (dp[i] >= m) { cout << i << endl;f = true;break; }
}
//初始化
for (int i = 1;i <= cnt1;i++)c1[i] = 0;
for (int i = 1;i <= cnt2;i++)c2[i] = 0;
for (int i = 0;i <= sum;i++)dp[i] = d1[i] = d2[i] = 0;
if (!f)cout << -1 << endl;
}
}
E. Advertising Agency
題目連結:點選此處
貪心,所以先對陣列排序,肯定要拿前k個。情況很多是因為有很多 a r r [ i ] arr[i] arr[i]可能是等於 a r r [ k ] arr[k] arr[k]的,那麼我們定一個 p r e pre pre和 l a s t last last標記,表示第一個值等於 a r r [ k ] arr[k] arr[k]的下標,和最後一個等於 a r r [ k ] arr[k] arr[k]的下標,然後 l a s t − p r e + 1 last-pre+1 last−pre+1得到 a r r [ k ] arr[k] arr[k]有幾個。之後就是組合數+逆元求解。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e3 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
ll arr[MAXN];
bool cmp(ll& a, ll& b) {
return a > b;
}
void extend_gcd(ll a, ll b, ll& x, ll& y) {
if (b == 0) {
x = 1, y = 0;
return;
}
extend_gcd(b, a % b, x, y);
ll tmp = x;
x = y;
y = tmp - (a / b) * y;
}
//逆元
ll mod_inverse(ll a, ll b) {
ll x, y;
extend_gcd(a, b, x, y);
//防止負數
return (x % b + b) % b;
}
int main() {
int t;
cin >> t;
while (t--) {
cin >> n >> m;
for (int i = 1;i <= n;i++)cin >> arr[i];
sort(arr + 1, arr + 1 + n,cmp);
int pre = MAXN;
int last = 0;
for (int i = 1;i <= n;i++) {
if (arr[i] == arr[m]) {
pre = min(pre, i);
last = max(last, i);
}
}
ll a = last - pre + 1;
ll b = m - pre + 1;
ll ans = 1;
for (int i = a;i >= b + 1;i--)ans = ans * i % mod;
for (int i = 1;i <= a - b;i++) {
ans = ans * mod_inverse(i, mod)%mod;
}
cout << ans << endl;
}
}
F. Unusual Matrix
題目連結:點選此處
很容易知道一行或者一列異或後,會使得所有值翻轉。
一個數翻轉2次等於沒動。
我們列舉矩陣,當列舉到 i , j i,j i,j時,前面列舉到的點都已經匹配完了。如果在該點需要翻轉,那麼我們不能列翻轉,因為翻轉列,會使得該列上面幾行的點匹配不成功,所以我們只能翻轉行,但是翻轉行又有問題了,因為該行前面的點已經匹配完了。翻轉行會使得前面的點不匹配。有人說,那麼在下一行我們翻轉列就行,但是我們這樣會使得該列最上面那些匹配好的點不匹配,形成矛盾。
所以我們瞭解到,在一定程度後,我們就不能再翻轉了,如果出現沒匹配的,那麼答案肯定是匹配不了的。
那麼我們要知道什麼條件才不能翻轉。
那就是出現後效性,即我們翻轉這個會使得我們前面匹配完的還要翻轉就不行。
那麼哪些點沒有後效性呢?就是第一列的所有點的行翻轉無後效性。第一行的所有點的列翻轉無後效性。
所以答案很顯然了,我們對第一行和第一列進行行列翻轉讓他們滿足要求,之後遍歷其他點,如果出現不符合的就是錯的。
程式碼中 m p [ i ] [ j ] = = 1 mp[i][j]==1 mp[i][j]==1表示要翻轉的,為0表示不用翻轉, h o r [ j ] hor[j] hor[j]和 v e r [ i ] ver[i] ver[i]表示第j行,第 i i i列是否翻轉過。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 1e3 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
int mp[MAXN][MAXN];
int ver[MAXN];
int hor[MAXN];
int main() {
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= n;j++) {
char ch;
cin >> ch;
mp[i][j] = ch - '0';
}
}
for (int i = 1;i <= n;i++) {
for (int j = 1;j <= n;j++) {
char ch;
cin >> ch;
mp[i][j] = (mp[i][j] + (ch - '0')) % 2;
}
}
if (n == 1) {
cout << "YES" << endl;
continue;
}
bool f = true;
for (int i = 1;i <= n;i++) {
if (mp[1][i] == 1) {
hor[i] = 1;
}
}
for (int i = 2;i <= n;i++) {
if (hor[1])mp[i][1] = mp[i][1] ^ 1;
if (mp[i][1] == 1)ver[i] = 1;
else ver[i] = 0;
for (int j = 2;j <= n;j++) {
for (int k = 1;k <= ver[i] + hor[j];k++) {
mp[i][j] = mp[i][j] ^ 1;
}
if (mp[i][j] == 1) { f = false;break; }
}
if (!f)break;
}
for (int i = 1;i <= n;i++) {
ver[i] = hor[i] = 0;
}
if (f)cout << "YES" << endl;
else cout << "NO" << endl;
}
}
G. Strange Beauty
題目連結:點選此處
這題我們要知道如果 a a a能被 b b b整除, b b b能被 c c c整除,那麼 a a a能被 c c c整除。
那麼我們很容易想到遞推。
d p [ b ] + = d p [ a ] dp[b]+=dp[a] dp[b]+=dp[a], d p [ c ] + = d p [ b ] dp[c]+=dp[b] dp[c]+=dp[b]
之後我們想列舉。發現列舉肯定會超時。
所以需要優化。 那麼怎麼才能知道一個數的整數倍有哪些呢?
我們知道一個數是由質數夠成的,所以我們只要列舉質數就行。
所以 d p [ i ] = d p [ i ] + m a x ( d p [ i ∗ p r i m e [ 1 ] ] , d p [ i ∗ p r i m e [ 2 ] ] , . . . d p [ i ∗ p r i m e [ c n t ] ] ) dp[i]=dp[i]+max(dp[i*prime[1]],dp[i*prime[2]],...dp[i*prime[cnt]]) dp[i]=dp[i]+max(dp[i∗prime[1]],dp[i∗prime[2]],...dp[i∗prime[cnt]])
通過這個我們知道我們要從後往前列舉。其中 i i i就表示一個數 i i i,不是表示 a r r [ i ] arr[i] arr[i],如果表示 a r r [ i ] arr[i] arr[i]就會錯誤,比如樣例 2 2 8,我們得到8的 d p dp dp值,但是不能通過 p r i m e prime prime得到2的dp值。只能通過4來作為間接值來得到,即 d p [ 4 ] = d p [ 4 ] + m a x ( d p [ 4 ∗ 2 ] , d p [ 4 ∗ 3 ] . . . ) , d p [ 2 ] = d p [ 2 ] + m a x ( d p [ 2 ∗ 2 ] , d p [ 2 ∗ 3 ] . . . ) dp[4]=dp[4]+max(dp[4*2],dp[4*3]...),dp[2]=dp[2]+max(dp[2*2],dp[2*3]...) dp[4]=dp[4]+max(dp[4∗2],dp[4∗3]...),dp[2]=dp[2]+max(dp[2∗2],dp[2∗3]...)。
但是這樣列舉肯定複雜度很大,所以要優化,那麼就是 i ∗ p r i m e [ j ] i*prime[j] i∗prime[j]大於2e5時,我們就 b r e a k break break,這樣,我們會減少很多複雜度。
這邊我們初始化 d p [ i ] dp[i] dp[i]表示在 a r r arr arr中的出現次數。
#include<iostream>
#include<vector>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<iomanip>
#include<stdio.h>
#include<map>
#include<queue>
using namespace std;
#define Check(x) cout<<"x:"<<x<<" "
#define Min(x,y,z) min(x,min(z,y))
#define Max(x,y,z) max(x,max(z,y))
typedef long long ll;
const int MAXN = 2e5 + 5;
const int MAXM = 1e6 + 5;
const ll mod = 1e9 + 7;
ll n, m;
int dp[MAXN];
int arr[MAXN];
bool visit[MAXN] = { 0 };
int prime[MAXN];
int cnt = 0;
//尤拉篩
void init() {
for (int i = 2;i < MAXN;i++) {
if (!visit[i])
prime[++cnt] = i;
for (int j = 1;j <= cnt, prime[j] * i < MAXN;j++) {
visit[prime[j] * i] = true;
if (i % prime[j] == 0)break;
}
}
}
int main() {
int t;
cin >> t;
init();
while (t--) {
int n;
cin >> n;
int ans = 1;
for (int i = 1;i <= n;i++) {
cin >> arr[i];
dp[arr[i]]++;
}
for (int i = 2e5;i >= 1;i--) {
int maxn = 0;
for (int j = 1;j <= cnt;j++) {
if (i * prime[j] > (int)2e5)break;
if (dp[i * prime[j]] != 0) {
maxn = max(maxn, dp[i * prime[j]]);
}
}
dp[i] += maxn;
ans = max(dp[i], ans);
}
for (int i = 1;i <= 2e5;i++) {
dp[i] = 0;
}
cout << n - ans << endl;
}
}