日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【解题报告】博弈专场 (CF 2000~2200)前五题

發布時間:2023/12/14 编程问答 40 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【解题报告】博弈专场 (CF 2000~2200)前五题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

【解題報告】博弈專場 (CF 2000+)前五題

  • A:Fox and Card Game | CF388C
    • 題意
    • 思路
    • 代碼
  • B:Berzerk | CF786A
    • 題意
    • 思路
    • 代碼
  • C:Ithea Plays With Chtholly | CF896B
    • 題意
    • 思路
    • 代碼
  • D:Lieges of Legendre | CF603C
    • 題意
    • 思路
    • 代碼
  • E:Funny Game | CF731E
    • 題意
    • 思路
    • 代碼

  • 博弈專場 (CF 2000+)十題
    這里面假設每個人都是聰明的,不會犯錯,希望自己的收益最大。

A:Fox and Card Game | CF388C

題意

  • Fox and Card Game | CF388C
    nnn 堆牌,第 iii 堆有 aia_iai? 張牌,每堆牌從上往下,給出每張牌的分數
    兩個人輪流拿牌,AliceAliceAlice 選一堆牌,拿走這堆牌的最上面那張牌;BobBobBob 則是選一堆,拿走這堆牌的最下面那張牌。
  • 選完后,計算每個人的分數和,大的獲勝。求每個人的最終分數。
    ∑ai≤104\sum a_i\le 10^4ai?104

思路

  • 一開始感覺是無從下手的,選擇決策有很多。
    如果靠近牌頂有收益很大的,肯定是 AliceAliceAlice 能拿到的。但是她可以不一開始就去拿。當 BobBobBob 拿了這堆的底的時候,她再來拿這堆的頂即可,有點像圍棋的下法和思路。
  • 這么一看,每堆牌就是一個獨立的游戲。那么我們只考慮一堆牌。如果是偶數牌,AliceAliceAlice 拿走前一半, BobBobBob 拿走后一半。如果是奇數,那么中間那張牌會被先手拿走
  • 如果考慮多堆奇數牌,先手拿走中間的某張牌后,先手就會變成后手,后手會拿走另一堆中間的某張牌。所以我們把奇數牌堆的中間那張牌的收益降序,然后雙方輪流拿即可。

代碼

  • 時間復雜度:O(nlog?n)O(n\log n)O(nlogn)
#include<bits/stdc++.h> using namespace std; void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);} typedef long long ll; const int MAX = 5e5+50; const int MOD = 1e9+7; const int INF = 0x3f3f3f3f; const ll LINF = 0x3f3f3f3f3f3f3f3f;vector<int>V;int main() {int n;cin >> n;ll sumA = 0,sum = 0;ll mx = 0;for(int i = 1;i <= n;++i){int s;cin >> s;for(int j = 1;j <= s;++j){ll t;cin >> t;if(s & 1 && j == (s + 1) / 2)V.push_back(t); // 中間的拿出來排序else if(j <= s / 2)sumA += t;sum += t;}}sort(V.begin(),V.end(),greater<ll>());int sz = V.size();for(int i = 0;i < sz;++i){if(i%2==0)sumA += V[i]; // 雙方輪流拿}cout << sumA + mx << " " << sum - (sumA + mx);return 0; }

B:Berzerk | CF786A

題意

  • Berzerk
    nnn 個球,編號 1~n1\sim n1n,環形排成一圈,第 nnn 個球是黑洞。
    AliceAliceAlice 有一個前進集合 AAABobBobBob 有一個前進集合 BBB
    對于每一個開始位置 ppp,雙方輪流選擇他集合中的一個數,相當于他順時針走幾步。如果走到黑洞,他就贏了。
  • 求對于每一個 ppp,對于每個人先手的情況,是必勝還是必輸,或者是平局。
    2≤n≤70002\le n\le 70002n7000

思路

  • 會跑循環,貌似挺麻煩的。考慮到多起點單終點,我們倒著跑是一個不錯的選擇。
    我們可以定義 dp[i][0/1]dp[i][0/1]dp[i][0/1] 表示在這個位置,誰先手,的必勝態怎么樣。
    如果下一步,都走到對方的必勝態,自己就是必敗態。如果下一步,有一步走到對方的必敗態,自己就是必勝態。
    否則,就是平局。
  • 感覺寫起來挺麻煩的。倒著走,都走到必勝態,我們可以用一個 cntcntcnt 數組去做。
    平局,我們可以使用一個 visvisvis 數組去做。

代碼

  • 時間復雜度:O(n2)O(n^2)O(n2)
#include<bits/stdc++.h> using namespace std; void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);} typedef long long ll; const int MAX = 7e3+50; const int MOD = 1e9+7;int n; vector<int>V[2]; bool vis[MAX][2]; int mex[MAX][2]; int cnt[MAX][2];void dfs(int x,int who){if(vis[x][who])return;vis[x][who] = 1;int opp = who ^ 1;for(auto it : V[opp]){int xp = (x - it + n - 1) % n + 1; // 上一步if(xp == 1)continue; // 上一步是黑洞,不可能if(!mex[x][who]){ // 這一步必敗,上一步必勝mex[xp][opp] = 1;dfs(xp,opp);}else if(++cnt[xp][opp] == V[opp].size()){ // 上一步的下一步全是必勝,上一步必敗mex[xp][opp] = 0;dfs(xp,opp);}} }int main() {cin >> n;int s;cin >> s;while(s--){int t;cin >> t;V[0].push_back(t);}cin >> s;while(s--){int t;cin >> t;V[1].push_back(t);}dfs(1,0);dfs(1,1);for(int i = 2;i <= n;++i)cout << (vis[i][0] ? mex[i][0] ? "Win" : "Lose" : "Loop") << " ";puts("");for(int i = 2;i <= n;++i)cout << (vis[i][1] ? mex[i][1] ? "Win" : "Lose" : "Loop") << " ";return 0; }

C:Ithea Plays With Chtholly | CF896B

題意

  • Ithea Plays With Chtholly | CF896B
    nnn 張紙,一開始沒有都是空。
    mmm 輪,每一次給定一個 [1,c][1,c][1,c] 的數字。
    給你數字之后,你需要選擇把它寫在那一張紙上,若紙上之前寫過數字,則覆蓋。
    你需要擺完一個數字,他才給你下一個數字。
    問你怎么擺,讓最后 nnn 張紙上的數字非單調遞減。
  • n,m≥2n,m\ge 2n,m2
    1≤c≤10001\le c\le 10001c1000
    1≤n??c2?≤m≤10001\le n \cdot \lceil \frac{c}{2}\rceil \le m\le 10001n??2c??m1000

思路

  • 非常神奇的交互題。也是一開始感覺擺數字讓最后非單調遞減,策略比較復雜的感覺。
    但是仔細想想,假設它最開始給了你個 111,你擺在最左邊是毫無疑問的。
    假設最開始給了你個 222,你擺在最左邊好像也可以,但如果再給了你個 111,你擺成 [2,1][2,1][2,1] 就很劣,不如把 222 替換成 111
  • 經過上述非常離散的思考,我們感覺一直維護一個非單調遞減的序列就可以了,做法有點類似求最長上升子序列的過程。
  • 但是這樣 WAWAWA了。再次仔細考慮,我們的次數超過了 mmm 輪了。假設它給你 [1,c][1,c][1,c] 范圍內的數字,給你 ccc 的話,你放在最右位置是毫無疑問的。
    于是,憑感覺,我們把值分成小值和大值,序列從左往右維護一個小值的非遞減序列,從右往左維護一個大值的非遞增序列。這樣,次數就夠了。
  • 嚴格分析輪數的話,考慮最劣情況的維護非遞增序列的次數,即給定序列為 [x,x?1,x?2,?,1,x,x?1,?][x,x-1,x-2,\cdots,1,x,x-1,\cdots][x,x?1,x?2,?,1,x,x?1,?],即可以分析得到輪數的最劣情況就是 n??c2?n \cdot \lceil \frac{c}{2}\rceiln??2c??

代碼

  • 時間復雜度:O(nlog?n)O(n\log n)O(nlogn)
#include<bits/stdc++.h> using namespace std; void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);} typedef long long ll; const int MAX = 1e3+50; const int MOD = 1e9+7; const int INF = 0x3f3f3f3f;int arr[MAX]; int arr_iv[MAX]; int main() {int n,m,c;cin >> n >> m >> c;fill(arr+1,arr+n+1,INF);fill(arr_iv+1,arr_iv+n+1,INF);while(1){int t;cin >> t;if(t <= c / 2){int p = upper_bound(arr+1,arr+1+n,t) - arr;cout << p << endl;arr[p] = t;arr_iv[n - p + 1] = -t;fflush(stdout);}else{int p = upper_bound(arr_iv+1,arr_iv+1+n,-t) - arr_iv;cout << n - p + 1 << endl;arr[n - p + 1] = t;arr_iv[p] = -t;fflush(stdout);}// for(int i = 1;i <= n;++i){ // show(i,arr[i],arr_iv[i]); // }bool can = true;for(int i = 1;i <= n;++i){if(arr[i] == INF){can = false;break;}if(arr[i] < arr[i-1]){can = false;break;}}if(can)break;}return 0; }

D:Lieges of Legendre | CF603C

題意

  • Lieges of Legendre | CF603C
    nnn 堆石頭,每堆有 aia_iai? 個石頭。
    兩個人輪流拿石頭。每次可以:
    (1) 選擇一堆,拿走其中一個石頭
    (2)選擇一堆,這堆的石頭個數為 2x2x2x,把它用 kkk 堆石頭,每堆 xxx 個石頭替換
    誰不能拿就輸了。求先手是否必勝。
  • 1≤n≤1051\le n\le 10^51n105
    1≤ai,k≤1091\le a_i,k\le 10^91ai?,k109

思路

  • 石頭,考慮 SGSGSG 值。
    f(x)f(x)f(x) 表示 xxx 個石頭的 SGSGSG 值。
    一個可以轉移到 f(x?1)f(x-1)f(x?1)
    xxx 是偶數,可以變成 kkkf(x/2)f(x/2)f(x/2) 的異或值。
  • 容易想到按照 kkk 的奇偶性分類討論。
  • 如果 kkk 是偶數,我們能算出來 f[0,1,2]={0,1,2}f[0,1,2]=\{0,1,2\}f[0,1,2]={0,1,2}
    如果 xxx 是奇數,那么可以得到 f(x)=mex{f(x?1)}f(x)=mex\{f(x-1)\}f(x)=mex{f(x?1)}
    如果 xxx 是偶數,那么可以得到 f(x)=mex{f(x?1),0}f(x)=mex\{f(x-1),0\}f(x)=mex{f(x?1),0},因為 kkkf(x/2)f(x/2)f(x/2) 的異或值為 000
    我們可以直接得到 f(x)=0f(x)=0f(x)=0,如果 xxx 是大于 222 的奇數;f(x)=1f(x)=1f(x)=1,如果 xxx 是大于 222 的偶數。
  • 如果 kkk 是奇數,我們能算出來 f[0,1,2,3,4]={0,1,0,1,2}f[0,1,2,3,4]=\{0,1,0,1,2\}f[0,1,2,3,4]={0,1,0,1,2}
    如果 xxx 是奇數,仍然 f(x)=mex{f(x?1)}f(x)=mex\{f(x-1)\}f(x)=mex{f(x?1)}
    如果 xxx 是偶數,那就 f(x)=mex{f(x?1),f(x/2)}f(x)=mex\{f(x-1),f(x/2)\}f(x)=mex{f(x?1),f(x/2)} 因為 kkkf(x/2)f(x/2)f(x/2) 的異或值為 f(x/2)f(x/2)f(x/2)
    我們能得到一個結論,如果 xxx 是大于 444 的奇數,那么 f(x)=0f(x)=0f(x)=0。否則,f(x)>0f(x)>0f(x)>0,我們可以歸納法去做,假設成立,然后根據 mex{f(x?1)}mex\{f(x-1)\}mex{f(x?1)} 可以證明。
    我們接下來只要遞歸計算 f(x)f(x)f(x)xxx 是偶數的情況即可。每次都除以 222,是在 log?\loglog 的數量級可以求出來的。
  • 當然有人說:結論怎么想得出來的?你可以打表找規律,但是記得按 kkk 的奇偶性去看。

代碼

  • 時間復雜度:O(nlog?n)O(n\log n)O(nlogn)
#include<bits/stdc++.h> using namespace std; void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);} typedef long long ll; const int MAX = 1e3+50; const int MOD = 1e9+7; const int INF = 0x3f3f3f3f;const int pre[] = {0, 1, 0, 1, 2};int sg(int x,int k){if(k & 1){if(x < 5)return pre[x];if(x & 1)return 0;return sg(x / 2,k) == 1 ? 2 : 1;}else{if(x <= 2) return x == 1 ? 1 : 2;return (x % 2) ^ 1;} }int main() {int n,k;scanf("%d%d",&n,&k);int mex = 0;for(int i = 1;i <= n;++i){int t;scanf("%d",&t);mex ^= sg(t,k);}puts(mex?"Kevin":"Nicky");return 0; }

E:Funny Game | CF731E

題意

  • Funny Game | CF731E
    nnn 個數,兩個人輪流操作。
    每一次,選擇至少兩個,把最左邊的 xxx 個數字合并成一個新的數字,值為這 xxx 個數字的和 yyy,操作的人獲得 yyy 分。
    選完后,第一個人的分數減去第二個人的分數的差值為 CCC,第一個人希望讓 CCC 最大,第二個人希望讓 CCC 最小,你需要求出這個 CCC
  • 2≤n≤2×1052\le n\le 2\times 10^52n2×105

思路

  • 每次就是選擇一個前綴和,設 dp[i][0/1]dp[i][0/1]dp[i][0/1] 表示已經選擇好前 iii 個數了,接下來由哪個人開始選。
    寫下狀態轉移方程:
    dp[i][0]=max?{dp[j][1]+pre[j]}dp[i][0]=\max\{dp[j][1] + pre[j]\}dp[i][0]=max{dp[j][1]+pre[j]}
    dp[i][1]=min?{dp[j][0]+pre[j]}dp[i][1]=\min\{dp[j][0] + pre[j]\}dp[i][1]=min{dp[j][0]+pre[j]}
    然后考慮暴力做時間不夠的,只要記錄一個最大值和最小值,直接轉移即可。
  • 注意至少選擇兩個,也就是說我們的答案是 dp[1][0]dp[1][0]dp[1][0] 而不是 dp[0][0]dp[0][0]dp[0][0]
  • 還有別的更簡單的做法。考慮每個操作的人都是希望最大化自己的分數的,所以可以寫成:
ll ans = pre[n];for(int i = n-1;i >= 2;--i)ans = max(ans,pre[i] - ans); // -ans 是因為差值對于不同雙方是取反的cout << ans;

也是非常的妙。

代碼

  • 時間復雜度:O(n)O(n)O(n)
#include<bits/stdc++.h> using namespace std; void show(){std::cerr << endl;}template<typename T,typename... Args>void show(T x,Args... args){std::cerr << "[ " << x << " ] , ";show(args...);} typedef long long ll; const int MAX = 2e5+50; const int MOD = 1e9+7; const int INF = 0x3f3f3f3f; const ll LINF = 0x3f3f3f3f3f3f3f3f;ll pre[MAX]; ll dp[MAX][2]; int main() {int n;scanf("%d",&n);for(int i = 1;i <= n;++i){ll t;scanf("%lld",&t);pre[i] = pre[i-1] + t;}ll mx = pre[n];ll mn = -pre[n];for(int i = n - 1;i >= 0;--i){dp[i][0] = mx;dp[i][1] = mn;if(dp[i][0] - pre[i] < mn){mn = dp[i][0] - pre[i];}if(dp[i][1] + pre[i] > mx){mx = dp[i][1] + pre[i];} // show(i,dp[i][0],dp[i][1]);}printf("%lld",dp[1][0]);return 0; }

總結

以上是生活随笔為你收集整理的【解题报告】博弈专场 (CF 2000~2200)前五题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。