心得
感覺是超常發揮了,開局覺得D題題目短,直接開題,口胡一番發現是個二分,19分鍾兩發A了。之后開了K題,又是和隊友一頓口胡討論,WA了兩發終於找到所有情況,73分鍾A掉。看榜發現J過的人挺多,直接開題,一頓瞎搞140分鍾A掉,之后兩個小時就和G題杠上了,一直到最后半小時才A掉,做了四個題,L題再看的時候已經沒腦子了,最后二十分鍾全程看榜,看着自己最后一分鍾掉出銀尾,變成銅首(心態崩了)。蒟蒻只寫了5個題解。
Defuse the Bombs
題意
給 \(n\) 個炸彈,每個炸彈有一個倒計時 \(A_i\) ,倒計時小於 \(0\) 炸彈就會爆炸,現在順序做如下操作:
- 把一個炸彈的時間加 \(1\) 。
- 所有炸彈時間減 \(1\) 。
- 如果沒有炸彈爆炸,重復上訴操作。
問最多能做多少次第一個操作。
\(1\leq n\leq 10^5\)
分析
答案具有單調性,直接二分答案。如果能做 \(x\) 次操作,那么所有炸彈在 \(x-1\) 輪及之前都不會爆炸,那么如果我們操作 \(x-1\) 次能保證所有數字都不小於 \(0\) 即可。注意一開始就有兩個倒計時是 \(0\) 的情況,只能操作一輪。
代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
long long arr[MAXN];
int n;
bool check(long long x) {
long long sum = 0;
for (int i = 0; i < n; i++) {
if (arr[i] < x - 1)sum += 0 - (arr[i] - x + 1);
//如果當前炸彈經過x-1輪會爆炸,就操作它,次數就是這個差值
if (sum > x - 1)return false;
//如果操作不過來了就不行
}
return true;
}
int main() {
int t, T = 1;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 0; i < n; i++)scanf("%lld", arr + i);
printf("Case #%d: ", T++);
long long l = 0, r = 1e18, res = 1;
while (l <= r) {
long long mid = l + r >> 1;
if (check(mid))res = mid, l = mid + 1;
else r = mid - 1;
}
printf("%lld\n", res);
}
return 0;
}
Knowledge is Power
題意
給出一個正整數 \(n\) ,要求把這個數字拆成若干個兩兩互質的數(都要大於1),使這些數字和等於 \(n\) ,現在求這些數字最大值和最小值的差的最小值是多少。
\(5\leq n \leq10^9\)
分析
- 如果是 \(6\) ,則無解,輸出 \(-1\) 。
- 如果 \(n\) 是奇數,則可以分成 \(n/2\) 和 \(n/2+1\) ,必互質,答案是 \(1\) 。
- 如果 \(n/2\) 扔是偶數,則可以分成 \(n/2-1\) 和 \(n/2+1\) ,必互質,答案是 \(2\) 。
- 如果 \(n/2\) 是奇數,則分如下情況:
- 如果 \(n\%3==0\) 則可以分成 \(n/3-1\) ,\(n/3\) ,\(n/3+1\) ,因為不可能有兩個偶數,必互質,答案是 \(3\) 。
- 如果 \(n\%3==1\) 則可以分成 \((n-1)/3-1\) ,\((n-1)/3\) ,\((n-1)/3+2\) ,如果互質,答案為 \(3\) ,否則為 \(4\) 。
- 如果 \(n\%3==2\) 則可以分成 \((n+1)/3-2\) ,\((n+1)/3\) ,\((n+1)/3+1\) ,如果互質,答案為 \(3\) ,否則為 \(4\) 。
代碼
#include<bits/stdc++.h>
using namespace std;
int main() {
int t, T = 1;
scanf("%d", &t);
while (t--) {
int n;
scanf("%d", &n);
printf("Case #%d: ", T++);
if (n & 1) printf("1\n");
else if (n % 4 == 0) printf("2\n");
else if (n == 6) printf("-1\n");
else {
if (n % 3 == 0) {
printf("2\n");
} else if (n % 3 == 1) {
int a = (n - 1) / 3;
int b = a - 1, c = a + 2;
if ( __gcd(b, c) == 1) printf("3\n");
else printf("4\n");
} else if (n % 3 == 2) {
int a = (n + 1) / 3;
int b = a - 2, c = a + 1;
if (__gcd(b, c) == 1) printf("3\n");
else printf("4\n");
}
}
}
return 0;
}
Game of Cards
題意
現在有點數分別為 \(0,1,2,3\) 的四種卡片,每一種分別有 \(C_0,C_1,C_2,C_3\) 張,兩個人輪流操作,可以選擇兩張牌,這兩張牌的點數之和不能大於 \(3\) ,選擇之后舍棄這兩張牌,用一張新牌替代,新牌的點數就是這兩張牌的點數之和。誰不能操作了誰久輸了,問誰會贏。
\(0\leq C_0,C_1,C_2,C_3\leq10^9\)
分析
手推了一個半小時才找全規律,暈了。
首先 \(0\) 點數的牌可以當成跳過當前回合,但是不全是,\(3\) 點數的牌基本上沒意義,除非有 \(0\) ,\(1\) 點數的牌要不然和 \(1\) 合成 \(2\) ,要不然和 \(2\) 合成 \(3\) 。
- 如果所有的牌的張數小於 \(2\) ,先手必輸。
- 如果只有 \(0\) 點數的牌,是偶數則先手勝。
- 如果 \(0\) 點的牌是偶數且有其他排則對勝負沒有影響,當 \(C_1\%3==0\) 或者只有 \(C_1\%3==1\) 而沒有 \(2\) 時先手輸。
- 如果 \(0\) 點是奇數,當 \(C_1\%3==0\) 或者 \(C_1\%3==2\) 並且 \(C_2>1\) 時先手勝。
代碼
#include <bits/stdc++.h>
using namespace std;
int main() {
int t,T=1;
scanf("%d",&t);
while(t--) {
long long a,b,c,d;
scanf("%lld%lld%lld%lld",&a,&b,&c,&d);
printf("Case #%d: ",T++);
if(a+b+c+d<2ll) {
printf("Horse\n");
continue;
}
if(b==0&&c==0&&d==0) {
if(a&1)printf("Horse\n");
else printf("Rabbit\n");
continue;
}
int f=1;
if(a%2==0) {
if(b%3==0)f=0;
else if(b%3==1&&c==0)f=0;
}else{
if(b%3==0)f=1;
else if(b%3==1&&c==0)f=1;
else if(b%3==1&&c)f=0;
else if(b%3==2&&c==0)f=0;
else if(b%3==2&&c==1)f=0;
else if(b%3==2&&c>1)f=1;
}
if(f)printf("Rabbit\n");
else printf("Horse\n");
}
return 0;
}
Joy of Handcraft
題意
有 \(n\) 種燈泡,有 \(m\) 分鍾的時間,每一種燈泡亮 \(t\) 分鍾,熄 \(t\) 分鍾,亮的時候亮度為 \(x\) ,問從 \(1\) 到 \(m\) 分鍾,每一分鍾最亮的燈泡亮度是多少。
\(1\leq n,m\leq 10^5\)
\(1\leq t,x\leq10^5\)
分析
題解講的是用線段樹,比賽的時候想到了但是覺得有點難操作,於是瞎搞用優先隊列搞過了,口胡了半天隊友也沒聽懂。。。
首先對區間賦值,對於同周期的燈泡肯定只記錄一個最亮的燈泡,然后用類似埃式篩的方法給每個區間起始位置賦值,最后用一個優先隊列搞一遍,對於每一分鍾刪去熄滅的燈,知道有亮的,添加當前點有周期開始的燈,然后輸出最亮的燈的亮度即可。
代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 2e5 + 10;
struct node {
int t, x, id;
bool operator<(const node &a) const {
return x < a.x;//優先隊列里按亮度從大到小
}
} arr[MAXN];
int brr[MAXN];//記錄第二個周期當前位置會亮的最亮燈泡
int vis[MAXN];//哪些燈泡在隊列里
vector<int> crr[MAXN];//記錄當前時間有哪些燈泡會亮
int n, m;
int main() {
int t, T = 1;
scanf("%d", &t);
while (t--) {
scanf("%d%d", &n, &m);
priority_queue<node> q;
for (int i = 0; i <= m; i++)brr[i] = 0, crr[i].clear();
for (int i = 1; i <= n; i++) {
scanf("%d%d", &arr[i].t, &arr[i].x);
arr[i].id = i;
q.push(arr[i]);
vis[i] = 1;
if (!brr[arr[i].t << 1 | 1])brr[arr[i].t << 1 | 1] = i;
else if (arr[brr[arr[i].t << 1 | 1]].x < arr[i].x)brr[arr[i].t << 1 | 1] = i;
//更新當前燈泡第二個周期開始的時候最亮的是誰
}
for (int i = 2; i <= m; i++)
if (brr[i])
for (int j = i; j <= m; j += i - 1)
crr[j].push_back(brr[i]);//按周期存入這里
printf("Case #%d:", T++);
for (int i = 1; i <= m; i++) {
while (!q.empty()) {
node now = q.top();
if ((i % (now.t << 1)) == 0 || (i % (now.t << 1)) > now.t)q.pop(), vis[now.id] = 0;
else break;
//如果隊頭的當前沒亮就出隊,否則退出循環
}
for (auto &it:crr[i])
if (!vis[it])q.push(arr[it]), vis[it] = 1;
//當前點會亮的燈泡入隊
if (q.empty())printf(" 0");
else printf(" %d", q.top().x);
}
printf("\n");
}
return 0;
}
Lottery
題意
給出 \(n\) 組數字,每組包含兩個正整數 \(a_i,x_i\) ,表示這個數字是 \(2^{a_i}\) ,有 \(x_i\) 個,問這些數字相加能組成多少不同的數字。
\(1\leq n\leq 10^5\)
\(0\leq a_i,x_i\leq 10^9\)
分析
賽后才想出來。首先肯定類似於二進制,如果有 \(k\) 個位置可以填 \(1\) 那么不同的數字就是 \(2^k\) 個。
在這個題里面,一個數字可能不是一個,我們發現 \(2\) 個 \(2^k\) 可以換成一個 \(2^{k+1}\) ,這個是解題關鍵,如果有 \(3\) 個 \(2^1\) ,可以等價成 \(1\) 個 \(2^2\) 和一個 \(2^1\) ,那么很容易想到需要這樣子轉換下去,直到變成 \(0\) 個,但是要注意,對於 \(2\) 個 \(2^k\) 並不能換成 \(1\) 個 \(2^{k+1}\) 和 \(0\) 個 \(2^k\) 。那么操作完會在 \(2\) 的每個次方的位置上有一個數字,並且不超過 \(2\) 。
假如 \(2\) 的 \(1\) 到 \(k\) 次方,每一個次方的位置上都有至少一個數字,那么至少可以表示 \(2^k\) 個不同的數字,但是有些位置上的數字是 \(2\) 肯定對答案有影響。
找了一下規律發現,把一段連續的非 \(0\) 位置上的數字都減去 \(1\) ,以二進制表示,那么這一段數字可以組合的數字就多出這個二進制表示的數字的個數。比如有一段連續的數字的個數是 \(1,2,1,1,2\) ,可以組合的數字就是 \(2^5+(01001)_2=32+17=49\) 個。然后把每一個這樣的段的結果乘起來,就是答案。
代碼
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 1e5 + 10;
const long long mod = 1e9 + 7;
struct node {
long long a, x;
bool operator<(const node A) const {
return a < A.a;//按次方排序
}
} arr[MAXN];
int n;
int main() {
int t, T = 1;
scanf("%d", &t);
while (t--) {
scanf("%d", &n);
for (int i = 0; i < n; i++)scanf("%d%d", &arr[i].a, &arr[i].x);
sort(arr, arr + n);
long long res = 1, sum = 1, add = 0, k = 0, now = 0;
//res記錄最后的結果,sum表示2^k
//add表示額外加上的值,k表示當前次方有的數字個數,now表示當前次方
int ind = 0;
while (true) {
if (ind != n && arr[ind].a == now)k += arr[ind++].x;
//如果當前now已經到了下一個存在的次方,加上這個次方的個數
if (ind != n && k == 0) {
//如果不是后一個,並且當前now的位置個數為0,計算,重新記錄
sum = (sum + add) % mod;
res = res * sum % mod;
sum = 1, add = 0, k = arr[ind].x, now = arr[ind++].a;
}
if (ind == n && k == 0) {
//如果已經是最后一個,計算后退出
sum = (sum + add) % mod;
res = res * sum % mod;
break;
}
if (k) {
//如果當前位置有數字,看是否能往后表示
if ((k & 1) == 0)add = (add + sum ) % mod;
//這里是算add,用二進制的特性。
sum = sum * 2 % mod;
now++;
k = (k - 1) >> 1;
//如果是奇數,往前移的就是 (k-1)/2 位,偶數算出來會舍棄小數,答案不會錯
}
}
printf("Case #%d: %lld\n",T++,res);
}
return 0;
}