2020.11.25清北學堂訓練賽解題報告
難易程度
中等偏上,該會的題都拿到分了,依舊是被學長虐菜的一天
題目分析
T1 一
題目分析
- 擷取問題:使用substr擷取子串(位置,長度)
- 編號:用map當作容器,下標為字串型別,裡面存的為整數型別的,也就是這個變數的編號,對映到map上,輕鬆解決麻煩的變數判斷問題
- 判斷:手玩一下\(+=\)符號的出現次數可以看到
- 當為\(0\)時,輸出變數的值
- 當為\(1\)時,定義變數
- 當為\(2\)時,對變數進行修改
- 最後一下注意幾個修改時的問題
- 兩個整數為相加
- 兩個字串為相連線
- 被修改值為整數,修改值為字串則不進行處理
- 被需改值為字串,修改值為整數型別,將整數型別轉換成字元型別連線上去
程式碼實現
#include<iostream> #include<cstdio> #include<cstring> #include<queue> #include<stack> #include<algorithm> #include<cmath> #include<map> using namespace std; const int N=1e5+9; int vis[N],num[N];//訪問標記,儲存數字的 string car[N];//儲存字元的 int pd_num[N],pd_car[N];//判斷變數是字元還是數字 string s;//輸入的字串 int cnt;//標號 map<string,int> mp;//新增一個新標號 int n; int main() { cin>>n; while(n--) { cin>>s; int flag=0,chung=0;//總標記,擷取標記 int pd1=0,pd2=0; for(int i=0;i<s.length();i++) { if(s[i]=='='||s[i]=='+') { if(flag==0) chung=i; flag++; } if(s[i]=='"')//字元 { if(!pd1) pd1=i; else pd2=i; } } if(flag==0) { string sum=s.substr(0,s.length()); if(!vis[mp[sum]]) cout<<"no"<<endl; else { if(pd_num[mp[sum]]) cout<<num[mp[sum]]<<endl; else cout<<car[mp[sum]]<<endl; } } if(flag==1) { string sum=s.substr(0,chung);//擷取變數名字 mp[sum]=++cnt;//給一個標號 vis[mp[sum]]=1; if(!pd1)//這是一個數字 { pd_num[mp[sum]]=1; int he=0; for(int i=chung+1;i<s.length();i++) { int mat=int(s[i])-48; he*=10; he+=mat; } num[mp[sum]]=he; } else { pd_car[mp[sum]]=1; for(int i=pd1+1;i<=pd2-1;i++) { car[mp[sum]]+=s[i]; } } } if(flag==2)//加法操作 { string sum=s.substr(0,chung); if(!vis[mp[sum]]) continue; if(pd_num[mp[sum]]&&pd1) continue; if(!pd1&&pd_num[mp[sum]]) //如果兩者均為整數直接相加; { int he=0; for(int i=chung+2;i<s.length();i++) { int mat=int(s[i])-48; he*=10; he+=mat; } num[mp[sum]]+=he; } if(pd1&&pd_car[mp[sum]]) //如果兩者均為字串直接進行字串拼接。 { for(int i=pd1+1;i<=pd2-1;i++) { car[mp[sum]]+=s[i]; } } if(!pd1&&pd_car[mp[sum]]) //如果x是字串y是整數則將y轉換為字串進行字串拼接 { for(int i=chung+2;i<s.length();i++) { car[mp[sum]]+=s[i]; } } } } }
T2 二
題目分析
爆搜可以過!!!
- 給定好幾個長度相等的字串,要求找出滿足下列條件的字串個數
- 可以任意重定義 \(26\) 個字母的優先順序,並且任意
排列字串 - 其他字串經過任意排列以後的字典序都大於該字串
- 可以任意重定義 \(26\) 個字母的優先順序,並且任意
- 我們可以發現,每一個字串的原來排列的順序是沒有什麼貢獻的,我們可以先記錄一下每一個字串每個字元的出現次數,設 \(cnt_{i,x}\) 表示第 \(i\) 個字串的字元 \(x\) 出現的次數
- 因為資料範圍不大,所以我們考慮用暴力列舉合法的字串,在暴力列舉其他的字串檢查。
思路實現
假設當前的字串編號為 \(i\)
考慮是否存在一種字典序,是的這種字串的字典序最小。
字典序先由小到大,考慮這 \(26\)
注意的是此處的\(j\)表示填到這一位時,字典序仍有可能比 \(i\) 小的字串 \(j\)
每次填入一個字元 \(x\)都會削除一些滿足\(cnt_{i,x}>cnt_{j,x}\)的字串
我們可以發現這一個位置能填的字元可能有很多,但是舉幾組樣例就可以發現,填入不同的字元效果是相通的,他們填完之後可以削除的字串\(j\)的數量相同,於是隨便填就可以了
時間複雜度為\(O(26^2n^2)\)
實現的時候只需要填入字串\(i\)中的字元即可
程式碼實現
(待新增......)
T3 三
題目簡述
給定一個長度為\(n\)的序列\(a\) ,給定一個引數為\(k\)。設一共有\(M\)個區間\(p\),第\(i\)個區間為\(p_i=[l_i]\)滿足其中的逆序對數不小於\(k\),設\(f(p_i,p_j)\)表示區間\([l_i,r_i]\)與\([l_j,r_j]\)重複覆蓋的位置數量。求:
\[\sum ^{m}_{i=1} \sum^{m}_{j=i+1} f(p_i,p_j) \]題目分析
- 把題目拆分成兩部分,考慮如何求\(m\)這個區間
- 先考慮暴力列舉的方式,如果當前列舉的區間為\([l,r]\),發現只要區間擴發,逆序對數是不降的,區間縮小時,逆序對數是不增的,具有單調性,這可以看成一個類似於滑動視窗的問題,考慮用雙指標來維護
- 考慮列舉一下區間的左端點\(l\),設第一個滿足逆序對數\(\geq k\)的右端點為\(r\),顯然不小於\(r\)的位置成為這個區間的右端點也是合法的。
- 繼續考慮區間的左端點\(l\)右移,那麼\(r\)也一定會單調右移,考慮如何削除\(l\)對逆序對的影響,再加入新數的影響。
- 權值樹狀陣列來維護列舉的區間的值域
- 維護的時間複雜度為\(O(log n)\)
思路拓展
我們考慮一下把式子化簡一下
\[\sum^{m}_{i=1} \sum^{m}_{j=i+1} f(p_i,p_j)=\sum^{m}_{i=1} \sum^{m}_{j=i+1} \sum_{k\in p_i \cap p_j} 1= \sum^n_{i=1} \sum^{m}_{k\in p_i} \sum^{m}_{k \in p_j} 1 \]在考慮一下實際的意義,後面兩個\(\sum\)表示的是從覆蓋的位置\(k\)的區間裡找出兩個區間來,所以上面的式子可以化為
\[\sum^{n}_{i=1} (\frac{cnt_k}{2}) \]其中的\(cnt_k\)表示\(k\)被多少個區間覆蓋
在考慮求\(m\)個區間的過程中維護\(cnt\),如果當前列舉的區間為\([l,r]\),其對\(1~r\)的貢獻為\(n-r+1\),對於\(r+1\)的貢獻為\(n-r\),對於\(r+2\)的貢獻為\(n-r-1\),對\(n\)的貢獻為\(1\)
這就是一個區間加等差數列,二階查分維護即可。
求得\(cnt\)後列舉位置\(k\)計算和式即可,時間複雜度為\(O(n \log n)\)
T4 四
建議去世…………
賽後總結
正常的知識點考察和程式碼能力基本沒有問題,現在知識點已經不是關鍵,主要是講究的思維能力,在空餘時間做一些有思維難度的題目,鍛鍊一下自己的技巧和思維方式
知識點彙總
T1 模擬,字串
T2 字串, 字典序
T3 數論,單調佇列
T4 樹形dp