1. 程式人生 > 其它 >[ACM] 訓練賽題解

[ACM] 訓練賽題解

2021_積分賽第二場

名稱 來源 演算法
敵兵佈陣 HDU-1166 線段樹(簡單)
Emoticons ICPC Central Russia Regional Contest (CRRC 19) map 模擬
Power play ICPC Central Russia Regional Contest (CRRC 19) 浮點二分(卡精度)
Prinzessin der Verurteilung CodeForces 1536B 思維題
Vases and Flowers HDU 4614 線段樹 + 二分
Omkar and Bad Story CodeForces 1536A 思維題

A - 敵兵佈陣

題解

​ 題意中文沒啥好說的,多組測試樣例,對輸入的陣列進行區間查詢單點修改,就是專門留給你們的線段樹板子題。

#include <bits/stdc++.h>
using namespace std;

const int N = 50005;

struct Node
{
   int l,r;
   int sum;
}T[N<<2];

int a[N];

void push_up(int rt)
{
   T[rt].sum = T[rt << 1].sum + T[rt << 1|1 ].sum;
}

void build(int rt,int l,int r)
{
   T[rt] = {l,r,0};
   if( l == r ){
       T[rt].sum = a[l];
       return ;
   }

   int mid = (l + r) >>1;
   build(rt << 1,l,mid),build(rt << 1|1,mid + 1,r);
   push_up(rt);
}

void update(int rt,int pos ,int val)
{
   if( T[rt].l == T[rt].r ){
       T[rt].sum += val;
       return ;
   }

   int mid = (T[rt].l + T[rt].r) >>1;
   if( pos <= mid ) update(rt << 1,pos,val);
   else update( rt << 1|1,pos ,val );
   push_up(rt);
}

int query(int rt,int l,int r)
{
   if( l <= T[rt].l && r >= T[rt].r ){
       return T[rt].sum;
   }

   int mid = ( T[rt].l + T[rt].r ) >> 1;
   int son = 0;

   if( l <= mid ) son += query(rt << 1,l,r);
   if( r > mid ) son += query(rt << 1|1,l,r);
   return son;
}

void solve(){
   int n;
   cin >> n;
   for(int i = 1 ; i <= n ; i++){
       cin >> a[i];
   }

   build(1,1,n);

   string op;
   while( cin >> op ){
       if( op == "End" ) break;
       int x,y;
       cin >> x >> y;

       if( op == "Query" ){
           cout << query(1,x,y) <<"\n";
       }else if( op == "Add" ){
           update( 1,x,y );
       }else{
           update( 1,x,(-1)*y);
       }
   }

}

int main()
{
   ios::sync_with_stdio(false);
   cin.tie(nullptr);
   cout.tie(nullptr);

   int t;
   cin >>t;
   for(int i = 1 ; i <= t ; i++){

       cout << "Case "<<i<<":"<< "\n";
       solve();
   }

   return 0;
}

B - Emoticons

題意

​ 一個叫 Basil 的程式設計師,正在寫一個新的文字編輯器。為了檢視郵件,Basil編寫了一個特殊的模組,該模組從文字檔案中提取所有表情符號,並將它們放在單獨的行中顯示。不幸的是,模組中出現了一個錯誤,表情符號中的字元混淆了。

​ Basil 知道以下的這些表情都應用到了信件中:

;-);-(:):(:-\:-P

:D:C:-0:-|8-0:-E

%0:-X:∼([:|||:]

​ 幫助 Basil, 寫一個程式,把讀入的亂序表情,恢復正常。如果有幾種可能的恢復選項,那麼其中任何一種都是正確的。

題解

​ 就是一道大模擬題,用map來存每個字元出現了多少次。

​ 由分析後我們可以知道,有的表情的組合優先順序是高於其他表情的(也就是說,我們在組合的時候要優先去組合他)。

​ 有的符號是獨一無二的,他只要出現,就唯一對應一個表情,那麼其他非唯一的表情就是肯定會給他優先消耗。就比如說 D , C , P , 8 ... 只要這些字元出現,那麼它就唯一的對應 :D,:C ... 這些表情,我們此時就把這個表情所涉及到的字元都在map中 -- ,就好了。

​ 注:\字元,要兩個才可以\\ ,此為轉義字元

注意:這段程式碼非常非常繁瑣,模擬題就是來噁心人的,你wa了,就是細節錯了,我們當時做的時候,他們都放棄了,我最後看了半天才發現那裡錯。

#include <bits/stdc++.h>

using namespace std;
const int N = 1e3 + 5;
int a[N];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    string s;
    cin >> s;
    int len = s.size();
    int cnt = 0;

    map<char, int> ma;
    ma['['] = 0, ma[':'] = 1;
    ma[']'] = 2, ma['|'] = 3;
    ma['D'] = 4, ma['C'] = 5;
    ma['P'] = 6, ma['E'] = 7;
    ma['8'] = 8, ma['0'] = 9;
    ma['%'] = 10, ma['X'] = 11;
    ma['~'] = 12, ma['\\'] = 13;
    ma['-'] = 14, ma['('] = 15;
    ma[';'] = 16, ma[')'] = 17;


    for(int i = 0; i < len; i ++ ){
        a[ma[s[i]]] ++;
    }

    while(a[0]){
        cout << "[:|||:]" << endl;
        a[0] --, a[1] -= 2, a[2] --;
        a[3] -= 3;
    }

    while(a[4]){
        cout << ":D" << endl;
        a[4] --, a[1] --;
    }

    while(a[5]){
        cout << ":C" << endl;
        a[5] --, a[1] --;
    }

    while(a[6]){
        cout << ":-P" << endl;
        a[6] --, a[1] --, a[14] --;
    }

    while(a[7]){
        cout << ":-E" << endl;
        a[14] --, a[1] --, a[7] --;
    }


    while(a[3]){
        cout << ":-|" << endl;
        a[3] --, a[1] --, a[14] --;
    }

    while(a[8]){
        cout << "8-0" << endl;
        a[8] --, a[14] --, a[9] --;
    }

    while(a[10]){
        cout << "%0" << endl;
        a[9] --, a[10] --;
    }

    while(a[9]){
        cout << ":-0" << endl;
        a[9] --, a[1] --, a[14] --;
    }

    while(a[11]){
        cout << ":-X" << endl;
        a[11] --, a[1] --, a[14] --;
    }

    while(a[12]){
        cout << ":~(" << endl;
        a[12] --, a[1] --, a[15] --;
    }

    while(a[13]){
        cout << ":-\\" << endl;
        a[13] --, a[1] --, a[14] --;
    }

    while(a[14] && a[16] && a[15]){
        cout << ";-(" << endl;
        a[16] --, a[14] --, a[15] --;
    }

    while(a[14] && a[16] && a[17]){
        cout << ";-)" << endl;
        a[16] --, a[14] --, a[17] --;
    }

    while(a[1] && a[15]){
        cout << ":(" << endl;
        a[1] --, a[15] --;
    }

    while(a[1] && a[17]){
        cout << ":)" << endl;
        a[1] --, a[17] --;
    }

    cout << "LOL" << endl;

    return 0;
}

C - Power play

題意

​ basil 在分析一個數學問題的時候發現一個有趣的現象, 對於數字 2,4 滿足 $ 2^4 = 4^2$​ 。他認為這對程式設計設計大賽的參賽者來說可能是一個巨大的挑戰。不幸的是,Basil 不能發現另外的這樣的數對了,他認為對於滿足這種關係的數對,他只能找到2,4。好吧,那我們就改變條件,有三個數字。Basil 決定。編寫的列舉選項程式證實了三個數字的任務是有意義的。

​ 你的任務是:找到一個 x 其範圍在 \(1 \le x\le1e^{18}\) ,滿足 $ a^x = x^b$​ (a,b為輸入的數字),答案可能有很多任意輸出滿足條件的答案即可,如果沒有這樣的數存在那麼輸出0 。​

題解

​ 由題目我們得到這個公式

\[a^x = x^b \]

​ 我們對兩邊取對數得到

\[x\times log(a) =b\times log(x) \]

​ 移項可得

\[x = \frac{ b \times log(x) }{log(a)} \]

​ 然後我們二分x的值,就可以了

​ 單調性證明略

#include <bits/stdc++.h>
using namespace std;
typedef long long LL;
LL a, b;
LL f(LL l, LL r) 
{
    if (l > r) return 0;
    LL mid = (l + r) / 2;
    double s=double(log(mid)/log(a));
    if (abs(s * b - mid) < 0.00000000001) 	///看是否有比他更小的存在 
    {
        LL ans = f(1, mid - 1);
        if (ans) return ans;
        return mid;
    }
    if (mid > s * b)  return f(l, mid - 1);
    return f(mid + 1, r);
}
int main() 
{
    int flag = 1;
    scanf("%lld%lld", &a, &b);
    printf("%lld\n", f(1, 1e18));
}
\[ \]

D - Prinzessin der Verurteilung

題意

​ 給你一個字串,讓你按字典序求第一個沒在這個字串中出現的子串,(由小到大排列的順序為 a b c d e f ... z ... aa ab ac ad ... ),在樣例qaabzwsxedcrfvtgbyhnujmiklop 中,az 都 出現了, ab 出現了, ac 沒有出現,那麼答案就是 ac

題解

​ 因為資料範圍不大,所以我們直接上 STL 就好了 ,stringfind 函式可以解決

​ 我們建立一個 vector<string >陣列來遍歷的字典序字串,然後對於每個字串我們都在其後面26個字母

​ 對於""空字串,我們加26個字母后變成,abc,...,z,對於a字串我們加了26個字串後變成 aaabac,...,az以此類推。

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;

    /// 遍歷的模板
string str = "abcdefghijklmnopqrstuvwxyz";

void solve()
{
    int n;
    cin >> n;
    string s;
    cin >> s;
    /// 建立字串陣列,同時把第一個字串初始化為空字串
    vector<string> bfs = {""};
    for(int i = 0; i < bfs.size() ; i++){

        string te = bfs[i];
        /// 找到了就直接輸出
        if( s.find(te) == string::npos ){
            cout << te << "\n";
            break;
        }
        /// 對於每個字串,我們都在起後面加上26個字母
        for( auto it : str ){
            bfs.push_back(te+it);
        }
    }
}

int main()
{
    ios::sync_with_stdio(false);
    cout.tie(nullptr);
    cin.tie(nullptr);

    int t;
    cin>> t;
    while(t--){
        solve();
    }

}

E - Vases and Flowers

題意

​ 給定一個整數n, 表示有n個花瓶(初始為空花瓶), 編號從0~n-1. 有如下兩種操作:

​ ①從編號為x的花瓶開始, 要放y朵花, 從前往後一次遍歷, 如果是空花瓶則放一朵花在裡面, 直至放完所有花或者遍歷到最後一個花瓶為止. 倘若此時還有花放不下, 則將它們直接丟棄.
​ ②清理[l, r]區間的所有花瓶, 如果裡面有花則將其丟棄

​ 對於每個操作①, 需要輸出第一個放花的位置和最後一個放花的位置. 倘若一朵花都放不下, 需要輸出"Can not put any one."
​ 對於每個操作②, 需要輸出該區間被清理的花的數量。

題解

​ 我們用線段樹來維護這個花瓶,線段樹的每一個結點,就相當於一個花瓶。

​ 這道題最關鍵的點就是,如何設定狀態,我們用sum來表示區間的剩餘可插花數,用lazy來標記區間修改的操作 lazy = -1,為不變,lazy = 0清除插花,lazy = 1待插花。

​ 對於操作①,我們線上段樹上遞迴的進行二分,如果當前區間在我們需要插花的區間,並且可插花數 <= 剩餘的待插花樹,那麼我們就在這個區間中二分的查詢需要最左邊的插花位置,和最右邊的插花位置,並與全域性變數 L,R 比較,最後輸出這次的L,R。

​ 對於操作②,相當於rangeQuery + rangeUpdate,在更新區間插花的同時,計數有那些結點插了花,並返回。

#include <bits/stdc++.h>
using namespace std;

const int N = 5e5 + 10;

struct Node
{
   int l,r;
   int sum,lazy;   ///sum為當前剩餘可插畫數
   ///lazy = -1,為不變,lazy = 0清除插花,lazy = 1待插花
}t[N << 2];
int L,R;

void push_up(int rt){ t[rt].sum = t[rt << 1].sum + t[rt << 1|1].sum; }

void build(int rt,int l, int r)
{
   t[rt] = {l,r,1,-1};
   if( l == r )    return ;
   int mid = (l + r) >> 1;
   build(rt << 1,l,mid),build(rt<<1|1,mid +1 ,r);
   push_up(rt);
}

void push_down(int rt)
{
   if( t[rt].lazy == -1 ) return;
   int lazy = t[rt].lazy ,l = t[rt].l,r = t[rt].r;
   int mid = l + r >> 1;

   t[rt<<1].sum = lazy * (mid - l +1);
   t[rt<<1|1].sum = lazy * (r - mid);
   t[rt << 1|1].lazy = t[rt << 1].lazy = lazy;
   t[rt].lazy = -1;
}

///查詢插花區間最左邊的位置
int findLeft(int rt)
{
   if( t[rt].l == t[rt].r ) return t[rt].r;
   push_down(rt);
   if( t[rt << 1].sum != 0 ) findLeft(rt << 1);
   else findLeft(rt << 1|1);
}
///查詢插花區間最右邊的位置
int findRight(int rt)
{
   if( t[rt].l == t[rt].r ) return t[rt].l;
   push_down(rt);
   if( t[rt << 1|1].sum != 0 )    findRight(rt <<1|1);
   else findRight(rt << 1);
}

void rangeUpdate(int rt,int l,int r,int &val)
{
   if( val == 0 || t[rt].sum == 0 ) return;
   if( l <= t[rt].l && r >= t[rt].r && t[rt].sum <= val   )
   {
       val -= t[rt].sum;
       /// 更新第一個和最後一個
       L = min(L,findLeft(rt)) ,  R = max(R,findRight(rt));
       t[rt].sum = 0;
       t[rt].lazy = 0;
       return ;
   }
   int mid = t[rt].l + t[rt].r >> 1;
   push_down(rt);
   if( l <= mid )  rangeUpdate(rt << 1,l,r,val);
   if( r > mid )   rangeUpdate(rt << 1|1,l,r,val);
   push_up(rt);
}

int rangeDelte(int rt,int l,int r)
{
   if( l <= t[rt].l && r >= t[rt].r )
   {
       int res = t[rt].r - t[rt].l + 1 - t[rt].sum;
       t[rt].sum = t[rt].r - t[rt].l + 1;
       t[rt].lazy = 1;
       return res;
   }

   int mid = (t[rt].l + t[rt].r) >> 1;
   int res = 0;
   push_down(rt);
   if( l <= mid )  res += rangeDelte(rt << 1,l,r);
   if( r > mid )   res += rangeDelte(rt << 1|1,l,r);
   push_up(rt);
   return res;
}

int main()
{
   ios::sync_with_stdio(false);
   cin.tie(0);

   int n,t,m,x,y,cmd,te;
   cin >> t;
   while(t--)
   {
       cin >> n >> m;
       build(1,1,n);
       while(m--)
       {
           cin >> cmd >> x >> y;
           if(cmd == 1)
           {
               L = n + 1, R = 0,te = y;
               rangeUpdate(1,x+ 1,n,y);
               if(te == y) cout << "Can not put any one." <<endl;
               else cout << L - 1 << ' ' <<R - 1 <<endl;

           }
           else cout << rangeDelte(1,x + 1,y + 1) << endl;
       }
       cout <<endl;
   }

   return 0;
}

F - Omkar and Bad Story

題意

​ Omkar收到了來自 Anton 的訊息 "你對問題A的解釋很混亂。再寫個詳細的說明 " 。正因如此, Omkar 給了你一個數組 a ,其有 n 個不同的數。如果一個數組(b)滿足任意的兩個元素 \(b_i,b_j\)\(|b_i - b_j|\)​ 在這個陣列中至少出現了一次 ,那麼這個陣列就是一個nice 陣列。此外,b中的所有元素必須是不同的。你能新增幾個整數(可能是0)到a來建立一個大小不超過300的陣列b 嗎。如果a已經是個 nice陣列了,你就不需要新增任何元素。

題解

如果原本數列中出現了負數,則直接輸出NO,因為你有一個負數存在的情況下

假設原數列為1 和-1
-1 - 1的絕對值為2
-1 - 2的絕對值為3
可以看出新出現的數只會不斷變大,所以是個無解

保證數列為正數的情況下直接輸出0至100就好了,

優化一下就是輸出最大的數

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
const int N = 1e5 + 10;


void solve()
{
    int n;
    cin>>n;
    int a[n];
    bool sanu=false;
    int maxi=INT_MIN;
    for(int i=0; i<n; i++){
        cin >> a[i];
        maxi=max(maxi, a[i]);
        if(a[i] < 0) sanu = true;
    }
    if(sanu){
        cout << "NO\n";
        continue;
    }else {
        cout << "YES\n";
        cout << maxi+1 << endl;
        for(int i = 0;i <= maxi ; i++) cout << i << " ";
        cout << endl;
    }

}

int main()
{
    ios::sync_with_stdio(false);
    cout.tie(nullptr);
    cin.tie(nullptr);

    int t;
    cin>> t;
    while(t--){
        solve();
    }

}