這場比賽不是CF的周賽,但因為自我感覺做的還不錯,做出來5題!於是寫下此文,以示紀念
A: AB Balance
題意如下,進行最少次數的修改,使得一個只包含'a'和'b'的字符串中,"ab"和“ba”出現的次數相同,其中每次修改指將一位的a替換成b或者將b替換成a,輸出修改后的字符串。
簽到題,只要判斷第一個和最后一個字符是否相同,如果相同,直接輸出該字符串,如果不同,進行一次替換,輸出該字符串。
一串a或者一串b如果沒有出現在兩端,那么他們會各自提供一個“ab”和一個“ba”,如果出現在兩端,比如開頭是a,那么它只會提供一個"ab",出現在結尾,會提供一個"ba",所以只要考慮開頭和結尾的字符就可以了。
#include<string>
#include<iostream>
using namespace std;
int main() {
int T;
cin >> T;
for (int i = 0; i < T; i++) {
string s;
cin >> s;
if (s[0] != s[s.length() - 1]) {
s[s.length() - 1] = s[0];
}
cout << s << endl;
}
}
B:Update Files
題意如下,有一台機器有資料,我們可以花一個小時將從有資料的機器上將資料傳輸到另一個機器,一個有資料的機器只能同時給一台機器傳輸,且同時傳輸的機器數目有上限,給出機器數目n和同時傳輸的機器數目的上限k,求最少需要花費多少小時使得所有機器都有資料,其中初始僅僅只有1台機器有資料。(表達能力匱乏,可以看原題)
我們記錄當前有資料的機器個數為x,可以把這個過程看成兩段,第一段是倍增階段,x<=k的時候,這時傳輸資料受到x的限制,第二階段是平穩階段,x>k,這時傳輸資料受到k的限制。
於是我們就要計算倍增階段和平穩階段花費(如果有的話)的時間,這個很容易得出。
點擊查看代碼
#include<iostream>
using namespace std;
int main() {
int t;
cin >> t;
here:for (; t > 0; t--) {
long long n, k;
cin >> n >> k;
int hour = 0;
long long num = 1;
if (n == 1) {
cout << 0 << endl;
continue;
}
while (num <= k) {
num *= 2;
hour++;
if (num >= n) {
cout << hour << endl;
t--;
goto here;
}
}
n -= num;
cout << hour + (n + k - 1) / k<<endl;
}
}
C:Banknotes
題目就不翻譯了,有點兒麻煩...
題目既然要找到至少要用k+1張紙幣表示的最小數值,那么就貪心一點,用k+1張盡可能小的紙幣去表示這個值。
那么什么樣的情況,滿足用不能用更少的紙幣表示呢?很容易想到,因為題目中的紙幣都是10的冪,所以用10^ak+1/10^ak-1及以下個第k個紙幣時,一定不能用更高面額的紙幣代替。而最高面額的紙幣則沒有這樣的限制,如果用的紙幣數量小於k+1,那么多出來的數目都可以放最高面額的紙幣。
於是代碼如下:
點擊查看代碼
#include<iostream>
#include<vector>
using namespace std;
int main() {
int T;
cin >> T;
for (; T > 0; T--) {
long long n, k;
cin >> n >> k;
k++;
vector<long long>arr(n,1);
for (int i = 0; i < n; i++) {
int a;
cin >> a;
while (a) {
a--;
arr[i] *= 10;
}
}
long long res = 0;
for (int i = 0; i < n-1; i++) {
if (k >= arr[i+1]/arr[i]-1) {
k -= arr[i + 1] / arr[i] - 1;
res += arr[i + 1] - arr[i];
}
else {
res += k * arr[i];
k = 0;
}
}
if (k) {
res += k * arr[n - 1];
}
cout << res << endl;
}
}
D Red-Blue Matrix
這一題很有意思,因為乍一看很難想到如何處理,因為紅藍色分配會有2^n種可能,而分割線也有m-1種可能,如果暴力破解一定會失敗
但是有一個很重要的特質,第一列一定在分割線的左邊,且如果存在分割線,那么它的位置一定是唯一的。
第一條性質是顯然的
由此,我們可以得到
Lemma 4.1: 第一列中最小元素所在行一定為藍色,最大元素所在行一定為紅色。
反證法,假設最小元素所在行為紅色,那么一定有一行是藍色,在分割線左邊,藍色那一行的元素不小於最小元素,與題干矛盾,所以得證。最大元素為紅色同理。
Theorem 4.2: 如果存在合理的解,那么分割線的位置是唯一的。
借助Lemma 4.1 我們可以確定一個藍色行以及一個紅色行,如果滿足題目中的兩個限制,那么對於已經確定顏色的這兩行的要求就是,split左邊的部分,藍色行中元素小於對應紅色行中元素,split右邊部分藍色行中元素大於對應紅色行中元素。
又因為不等關系具有反對稱性,因此分割線的位置一定是唯一的。
因此在這里借助Lemma 4.1和Th 4.2,我們可以判斷某些矩陣是無法進行合理的塗色和分割的
這些矩陣包括
1.對於所有列,都有藍色行元素小於紅色行元素的
2.存在某一列,藍色行元素等於紅色行元素
3.對於某一列,藍色行元素大於紅色行元素,且存在它后面的某一列,藍色行元素小於紅色行元素。
因此我們可以進行初步判斷,一些不合理的矩陣
點擊查看代碼
int n, m;
cin >> n >> m;
vector<vector<int>>mat(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> mat[i][j];
}
}
int minpl = 0, maxpl = 0;
for (int i = 0; i < n; i++) {
if (mat[i][0] < mat[minpl][0]) {
minpl = i;
}
if (mat[i][0] > mat[maxpl][0]) {
maxpl = i;
}
}
//找到一對點,minpl一行一定是藍,maxpl一定是紅。
//開始判斷split的位置
bool check = 0;
int split = m-1;
for (int j = 1; j < m; j++) {
if (mat[minpl][j] == mat[maxpl][j]) {
//情況二,存在一列使得藍色行和紅色行元素相同
cout <<"NO" << endl;
T--;
goto here;
}
else if (mat[minpl][j] < mat[maxpl][j]) {
if (check) {
//情況三,在之前有一列出現藍色行元素大於紅色行元素之后,該列藍色行元素小於紅色行元素。
cout << "NO" << endl;
T--;
goto here;
}
}
else {
if (!check) {
check = 1;
split = j;
}
}
}
if (!check) {
//情況一,所有列的藍色行元素都小於紅色行元素
cout << "NO" << endl;
T--;
goto here;
}
在得到split的位置之后,下一步是判斷哪些行為藍色,哪些行為紅色
Lemma 4.3 如果在split之前,有元素小於split左邊藍色行元素的最小值,那么該元素所在行一定是藍色,如果大於左邊紅色行元素的最大值,那么該元素所在行一定是紅色。
Proof: 類似於Lemma 4.1的證明,利用反證法即可。
Lemma 4.4 在split之后,如果有元素大於split右邊藍色行元素的最大值,那么該元素所在行一定是藍色,如果小於右邊紅色行元素的最小值,那么該元素所在行一定是紅色。
Proof:略過。
在有了Lemma 4.3,4.4之后,我們可以進一步對矩陣進行塗色,通過不斷將某一行塗色,然后更新藍色行元素的最小(大)值,以及紅色行元素的最大(小)值,直到無法繼續,如果存在合理塗色的方法,那么這樣塗色一定是正確的。
但也存在一些不合理的塗色,因此我們僅僅只對同一行元素進行一次塗色,就算它同時滿足Lemma 4.3的前后兩個性質。我們最后根據藍色行元素和紅色行元素的相對大小判斷塗色是否合理。
這里我的代碼的順序有一點點問題,但是影響不大,就是在遍歷的時候,應該先按照行遍歷,如果遇到塗色行,跳過,我這里先按照列遍歷,則需要額外執行很多次continue.
點擊查看代碼
vector<int>color(n, -1);
color[minpl] = 0;
color[maxpl] = 1;
int bluel = mat[minpl][0];
int bluer = mat[minpl][m - 1];
int redl = mat[maxpl][0];
int redr = mat[maxpl][m - 1];
for (int i = 0; i < split; i++) {
bluel = max(bluel, mat[minpl][i]);
redl = min(mat[maxpl][i], redl);
}
for (int i = split; i < m; i++) {
bluer = min(bluer, mat[minpl][i]);
redr = max(mat[maxpl][i], redr);
}
while (1) {
bool isadd = 0;
for (int i = 0; i < split; i++) {
for (int j = 0; j < n; j++) {
if (color[j] != -1) {
continue;
}
if (mat[j][i] <= bluel) {
isadd = 1;
color[j] = 0;
for (int k = 0; k < split; k++) {
bluel = max(mat[j][k], bluel);
}
for (int k = split; k < m; k++) {
bluer = min(mat[j][k], bluer);
}
}
else if (mat[j][i] >= redl) {
isadd = 1;
color[j] = 1;
for (int k = 0; k < split; k++) {
redl = min(mat[j][k], redl);
}
for (int k = split; k < m; k++) {
redr = max(mat[j][k], redr);
}
}
}
}
for (int i = split; i < m; i++) {
for (int j = 0; j < n; j++) {
if (color[j] != -1) {
continue;
}
if (mat[j][i] >= bluer) {
isadd = 1;
color[j] = 0;
for (int k = 0; k < split; k++) {
bluel = max(mat[j][k], bluel);
}
for (int k = split; k < m; k++) {
bluer = min(mat[j][k], bluer);
}
}
else if (mat[j][i] <= redr) {
isadd = 1;
color[j] = 1;
for (int k = 0; k < split; k++) {
redl = min(mat[j][k], redl);
}
for (int k = split; k < m; k++) {
redr = max(mat[j][k], redr);
}
}
}
}
if (!isadd) {
break;
}
}
最后,如果存在一些沒有塗色的行,那么統一把它們塗色為紅色或者藍色均可,證明略。
加上一段判斷塗色結果是否合理的代碼
點擊查看代碼
if (bluel >= redl || bluer <= redr) {
cout << "NO" << endl;
T--;
goto here;
}
cout << "YES" << endl;
for (int i = 0; i < n; i++) {
if (color[i] == 0) {
cout << "B";
}
else {
cout << "R";
}
}
cout << " " << split << endl;
完整代碼如下
完整代碼
#include<vector>
#include<iostream>
using namespace std;
int main() {
int T;
cin >> T;
here:for (; T > 0; T--) {
int n, m;
cin >> n >> m;
vector<vector<int>>mat(n, vector<int>(m, 0));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> mat[i][j];
}
}
int minpl = 0, maxpl = 0;
for (int i = 0; i < n; i++) {
if (mat[i][0] < mat[minpl][0]) {
minpl = i;
}
if (mat[i][0] > mat[maxpl][0]) {
maxpl = i;
}
}
//找到一對點,minpl一行一定是藍,maxpl一定是紅。
//開始判斷split的位置
bool check = 0;
int split = m - 1;
for (int j = 1; j < m; j++) {
if (mat[minpl][j] == mat[maxpl][j]) {
cout << "NO" << endl;
T--;
goto here;
}
else if (mat[minpl][j] < mat[maxpl][j]) {
if (check) {
cout << "NO" << endl;
T--;
goto here;
}
}
else {
if (!check) {
check = 1;
split = j;
}
}
}
if (!check) {
cout << "NO" << endl;
T--;
goto here;
}
//接下來划分紅藍
//首先能確定的是,在split左邊的點,val<=藍色點的最大值的點一定是藍色,val>=紅色點的最小值的點一定是紅色。
//其次能確定的是,在split右邊的點,val>=藍色點的最大值的點一定是藍色,val<=紅色點最小值的點一定是紅色。
//對於剩余點,全部划分為紅色。
vector<int>color(n, -1);
color[minpl] = 0;
color[maxpl] = 1;
int bluel = mat[minpl][0];
int bluer = mat[minpl][m - 1];
int redl = mat[maxpl][0];
int redr = mat[maxpl][m - 1];
for (int i = 0; i < split; i++) {
bluel = max(bluel, mat[minpl][i]);
redl = min(mat[maxpl][i], redl);
}
for (int i = split; i < m; i++) {
bluer = min(bluer, mat[minpl][i]);
redr = max(mat[maxpl][i], redr);
}
while (1) {
bool isadd = 0;
for (int i = 0; i < split; i++) {
for (int j = 0; j < n; j++) {
if (color[j] != -1) {
continue;
}
if (mat[j][i] <= bluel) {
isadd = 1;
color[j] = 0;
for (int k = 0; k < split; k++) {
bluel = max(mat[j][k], bluel);
}
for (int k = split; k < m; k++) {
bluer = min(mat[j][k], bluer);
}
}
else if (mat[j][i] >= redl) {
isadd = 1;
color[j] = 1;
for (int k = 0; k < split; k++) {
redl = min(mat[j][k], redl);
}
for (int k = split; k < m; k++) {
redr = max(mat[j][k], redr);
}
}
}
}
for (int i = split; i < m; i++) {
for (int j = 0; j < n; j++) {
if (color[j] != -1) {
continue;
}
if (mat[j][i] >= bluer) {
isadd = 1;
color[j] = 0;
for (int k = 0; k < split; k++) {
bluel = max(mat[j][k], bluel);
}
for (int k = split; k < m; k++) {
bluer = min(mat[j][k], bluer);
}
}
else if (mat[j][i] <= redr) {
isadd = 1;
color[j] = 1;
for (int k = 0; k < split; k++) {
redl = min(mat[j][k], redl);
}
for (int k = split; k < m; k++) {
redr = max(mat[j][k], redr);
}
}
}
}
if (!isadd) {
break;
}
}
if (bluel >= redl || bluer <= redr) {
cout << "NO" << endl;
T--;
goto here;
}
cout << "YES" << endl;
for (int i = 0; i < n; i++) {
if (color[i] == 0) {
cout << "B";
}
else {
cout << "R";
}
}
cout << " " << split << endl;
}
}
E:Arena
Ps:Arena是競技場的意思,MOBA游戲里的A就是這個單詞,(MOBA--multiplayer online battle arena)
首先看到這樣的數據范圍應該有一個敏感性,可能要用到dp了
這題確實可以通過記憶化搜索來實現。
我們假設剩余n個人,且每個人剩余血量不超過m時,能達到平局的可能次數為f(n,m);
顯然,當n=1時,f(1,m)=0;
那么如果m<=n-1時,在第一輪,所有人都死亡,那么每個人的血量可以任取1-m的值,也就是說,f(n,m)=m^n
接着考慮m>=n時
如果有i個人此時血量<=n-1, n-i個人血量>=n,那么第一輪結束后,將會剩下n-i個人,他們將繼續決斗,且此時,他們的剩余血量不超過m-(n-1),在這種情況下,對答案的貢獻為C(i,n)*(m^i)*f(n-i,m-n+1)
那這樣狀態轉移方程就寫完了,具體實現還需要計算組合數和快速冪,這些應該都是很輕松的任務了。
完整代碼如下,因為數據范圍極小,所以組合數采取最原始的運算方法,如果數量級大一點應該使用階乘和逆元計算。
點擊查看代碼
#include<vector>
#include<iostream>
using namespace std;
const int mod = 998244353;
long long ksm(long long b, long long e) {
long long res = 1, tmp = b;
while (e) {
if (e & 1) {
res *= tmp;
res %= mod;
}
tmp *= tmp;
tmp %= mod;
e >>= 1;
}
return res;
}
vector<vector<long long>>c(501, vector<long long>(501, 0));
vector<vector<long long>>dp(501, vector<long long>(501, -1));
void initial() {
for (int i = 1; i < 501; i++) {
c[i][0] = c[i][i] = 1;
}
for (int i = 1; i < 501; i++) {
for (int j = 1; j < i; j++) {
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
c[i][j] %= mod;
}
}
}
long long cal(long long n, long long x) {
if (dp[n][x] != -1) {
return dp[n][x];
}
else if (x <= n-1) {
return dp[n][x]=ksm(x, n);
}
else {
long long res = 0;
for (int i = 0; i <= n; i++) {
//死了i個的情況,選i個死
res += ((cal(n - i, x - n + 1) * ksm(n-1, i))%mod)*c[n][i];
res %= mod;
}
return dp[n][x] = res;
}
}
int main() {
//dp[n][x]代表剩余n個人,每個人的最高血量為x.
//dp[1][]=0;
//dp[n][x]=x^n;x<=n;
initial();
for (int i = 1; i < 501; i++) {
dp[0][i] = 1;
}
for (int i = 1; i < 501; i++) {
dp[1][i] = 0;
}
int n, x;
cin >> n >> x;
cout << cal(n, x);
//3 3
//第一輪一個沒死
//3 1
//第一輪死了一個
//f(2 1) *(2)*C(3,1)
}