大一寒假訓練碰到的有趣的題目
收錄了部分有趣的題目:
字串:
題目一: 2008年老題,但是網上沒有題解【 vjudge 可以看 sourse code 】,最後被bs學長搞了。(第一次接觸01BFS。
暴力列舉:
題目二:
①思路問題:因為最大值的變化性,一直寫不出來【應該反思自己寫程式碼的能力了】,後來看了題解,發現對於最大值選擇與哪一個值匹配,我們是可以列舉的【TLE怕了,不敢寫,不敢想】,但是n=1000是完全可以寫的,而且題目還保證了:
【It is guaranteed that the total sum ofnnover all test cases doesn't exceed1000】
②碼力問題:我看到別人用multiset模擬時,我也試著寫了一遍,後來發現一直異常。。【原因出在了:s.erase(s.end())】
然後,也有很多人使用map來記錄每一個數的cnt,然後 模擬multiset【我不會啦。。】
③總結:學習到 #define iter(c) __typeof((c).begin()) 、 函式傳參傳vector等stl容器時,是按值傳遞的,不是傳地址,
也就是說,修改傳進來的vector不會改變原來的vector
#include <stdio.h> #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <map> #includeView Code<set> #define re register #define ll long long #define fi first #define se second #define iter(c) __typeof((c).begin()) #pragma GCC optimize(3) #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) #define max(a,b) ((a)>(b)?(a):(b)) #define min(a,b) ((a)<(b)?(a):(b)) #define me(a,b) memset(a,(b),sizeof a) usingnamespace std; const int maxn = 1e5+11; int n,b; vector<int>v; vector<pair<int,int> >vp; void solve() { multiset<int>s; v.clear(); bool flag = 1; cin>>n;n*=2; for(int i = 0;i < n;i++) cin>>b,v.emplace_back(b); sort(v.begin(),v.end()); for(int i = 0;flag&&i<v.size()-1;i++) { s.clear();vp.clear(); for(int j = 0;j < v.size()-1;j++) if(j!=i)s.insert(v[j]); int pre = v[n-1]; while(s.size()) { auto it = s.end(); it--;//必須--,因為s.end()是指向外面的外界 int bk = *it; int want = pre - bk; s.erase(it);//每erase一次,s.end()向前移一位 //s.erase(s.end()); if(s.count(want)) { s.erase(s.find(want)); vp.emplace_back(make_pair(bk,want)); pre = max(bk,want); }else break; } if(s.empty()) { cout<<"YES"<<endl; cout<<v[n-1]+v[i]<<endl; cout<<v[n-1]<<" "<<v[i]<<endl; for(int i = 0;i < vp.size();i++) cout<<vp[i].fi<<" "<<vp[i].se<<endl; flag = 0; break; } } if(flag) cout<<"NO"<<endl; } int main() { /*multiset<int>s; for(int i = 1;i <= 10;i++) s.insert(i); for(auto it=s.begin();it!=s.end();it++) cout<<*it<<" "; cout<<*s.end()<<endl;//10 s.end() 指向的元素值等於最後一個元素 //但是erase千萬不能s.erase(s.end()) 因為s.end()並不在s的範圍之內 auto it = s.end(); cout<<" s.end() = "<<*it<<endl;//10 --it; cout<<" s.end() = "<<*it<<endl;//10 --it; cout<<" s.end() = "<<*it<<endl;//9 --it; cout<<" s.end() = "<<*it<<endl;//8*/ int t;ios; cin>>t; while(t--) solve(); }
題目三:前後綴+構造+思維題目 ,【考的時候確實不會,說起來容易做起來難(哭)】, 首先要會普通的抵消石子的遊戲,然後再一個一個點進行列舉看看更換之後是否能夠成功(最樸素的做法)。加入前後綴記錄優化,可以大幅降低執行時間。
首先來看最普通的抵消石子:我們知道最後 能成功抵消的就只有一種情況,就是 a1 < a2> a3這樣的局面,此時
若 a1 + a3 == a2 那麼就是YES 。 所以我們可以開一個 suffix陣列,一個 prefix陣列,分別記錄如果不需要轉移的時候抵消到 i 所剩下的石子。
① s[ i ]表示從最後面(第n個)開始抵消到 i 時,i還剩下的石子數量
② p[ i ]表示從第一個開始抵消到 i 時還剩下的石子數量
③ 如果存在 p[ i ] == s[ i + 1 ] ,那麼就說明不需要轉移(superpower)就可以完成。(可以模擬一下)
再來看加上superpower的情況:因為我們轉移只是影響到 第i位 和 第i+1位,其它石子堆的抵消方式並不會產生改變,我們就特判一下就好了【不要忘了判斷 a 與 s,p 大小時加上等號,比如資料 : 2 2 1 8 9 7 7 】
題目四: 這道題也是前後綴,但是用線段樹維護最大最小值也能A(尬) 其實兩者的思路是一樣的,把字串化為直角座標系的一條曲線,然後斷開詢問區間(L,R)之後,將 R 右邊的曲線併到 L 左邊的斷口處 ,所以我們需要維護前區間最大最小值,然後維護後區間最大最小值,然後後區間最大最小值還要加上 L ,R 兩個斷口的在y軸上的差距。
維護區間最值顯然是線段樹的特長,但是使用(字尾+字首和)來維護最值我還是第一次見。。
CF題解各變數的含義:sur字尾區間相對最大值,sul字尾區間相對最小值,pr字首和,prr字首最大值,prl字首最小值
題目五:D. Yet Another Subarray Problem 動態規劃,定義dp[ i ][ j ]為以 i 結尾時,i 是所選區間的第 j 個數,如果是第一個數就要減 k ,如果不是就考慮繼承。
題目六:思維題 / 前後綴
解法一: 我們維護①左、右兩個指標(x,y),再②維護指標上下左右所能到達的最遠距離(maxl,maxr,maxup,maxdown),最後再③維護指標到上下左右界的最遠距離【bestl,bestr,bestdown,bestup】(就是指標在移動過程中所能達到離maxl,maxr,maxdown,maxup的最遠距離,這個最遠距離的最大一個就是矩陣的寬 和 高)。
bestl = max( abs(maxl - x) ) bestr = max(abs(maxr - x))
bestup = max( abs(maxup - y )) bestdown = max(abs(maxdown - y ))
#include <stdio.h> #include <iostream> #include <cstring> #include <algorithm> #include <queue> #include <map> #include <set> #define re register #define ll long long #define fi first #define se second #define debug(a) cout<<#a<<" = "<<a<<endl #define emp(a) emplace_back(a) #define iter(c) __typeof((c).begin()) #pragma GCC optimize(3) #define ios ios::sync_with_stdio(0),cin.tie(0),cout.tie(0) #define me(a,b) memset(a,(b),sizeof a) inline ll max(ll a,ll b){return a>b?a:b;} inline ll min(ll a,ll b){return a>b?b:a;} using namespace std; ll maxl,maxr,maxup,maxdown; ll bestr,bestl,bestup,bestdown; string s; int main() { ios; int t; cin>>t; while(t--) { cin>>s; ll len = s.size(); maxl = maxr = maxup = maxdown = 0; bestl = bestr = bestup = bestdown = 0; ll x = 0,y = 0,x1 = 0,y1 = 0; for(int i =0;i <len;i++) { if(s[i]=='W')y++; else if(s[i]=='S')y--; if(s[i]=='A')x--; else if(s[i]=='D')x++; maxr = max(maxr,x); maxl = min(maxl,x); maxup = max(maxup,y); maxdown = min(maxdown,y); bestl = max(abs(x-maxl),bestl); bestr = max(abs(x-maxr),bestr); bestup = max(abs(y-maxup),bestup); bestdown = max(abs(y-maxdown),bestdown); } ll n = maxup-maxdown+1,m = maxr-maxl+1; //debug(n),debug(m); if(bestl!=bestr&&bestup!=bestdown&&n>2&&m>2) cout<<((m>n)?(n-1)*m:(m-1)*n)<<endl; else if(bestl!=bestr&&m>2) cout<<(m-1)*n<<endl; else if(bestup!=bestdown&&n>2) cout<<(n-1)*m<<endl; else cout<<n*m<<endl; } }View Code
解法二(前後綴思路):【一位神牛的講解(轉)】
這個字元不是隨便可以加的,因為會出現,你加了一個字元 使左邊的最小值往右邊移了一下,但是導致最右邊往右移了,得不償失 首先我們考慮垂直方向,我們假設它在0這個位置,那麼他所移動的 位置就是差值為1的點,我們在一個位置前面加了W,那麼他後面所有點的座標都加一 如果你在一個位置前加了s,那麼他後面的所有點的座標會減一, 你要保證可以減小,那麼你加一的時候,後面不能有最大值,並且前面不能有最小值 減一的時候後面的不能有最小值,並且前面不能有最大值,否則都不可以。 所以要找那種,可以加的情況,就是加了可以減少,不會增加也不會不變的那種 因為垂直和水平情況一樣,我們只考慮一邊 我們必須明確你要加一,那麼後面的全部加一,你要減一那麼後面的全部減一 垂直 假如它達到了很多次最大值和很多次最小值 1、當最後一個最大值在第一個最小值前面的時候,我們只要將後面的值加一,最小值減少 而你改其他的位置,要麼會使值變大,要麼會使值不變 2、當最後一次最小值出現在第一個最大值之前時,只要將最小值後面的減一,那麼值也會變小 其他兩種情況改了和沒改一樣,或者造成變大
題目七: D. Extreme Subtraction 思維 / 差分 / 貪心(易理解的貪心):
這裡講一下差分的做法:【記住,差分陣列一般用於區間加減操作】
原陣列: a0 a1 a2 a3 ................ an
差分陣列:d0 d1 d2 d3 .................dn dn+1
其中 d0 = a0 , d1 = a1 - a0 , d2 = a2 - a1 ,d3 = a3 - a2 ............................ , dn+1 = - an
根據差分陣列的性質,在差分陣列兩個端點加減既可以完成在原陣列加減的任務
那麼我們就只需要將 d中小於0的加一,同時 d1 - 1, d中大於0的減一 ,同時dn+1 + 1
如 1 10 9 10 10 ---> 差分陣列 : 1 9 -1 1 0 -10 【分別從 d1 到 dn+1】 1 和 -1 抵消,-10 和 9+1抵消,所以這個數列是YES的
題目又說左右兩個端點【即a1,an可以自減,所以就不用管了】,我們只需要將 d(2~n)化為0,然後判斷以下左右兩個端點是否大於等於0就可以了。【綜上,這道題不是差分板子題我直播吃掉電腦螢幕】
#include <bits/stdc++.h> using namespace std; typedef long long ll; const int maxn=3e4+7; int n,m,p; int a[maxn],b[maxn]; int main(){ int t,T=0; cin>>t; while(t--) { ll sum=0; scanf("%lld",&n); for(int i=1;i<=n;i++) { scanf("%lld",&a[i]); b[i]=a[i]-a[i-1]; } for(int i=2;i<=n;i++) { if(b[i]<0) sum-=b[i]; } if(a[1]>=sum) cout<<"YES"<<endl; else cout<<"NO"<<endl; } return 0; }V使用d【1】來消去各項
#include<bits/stdc++.h> using namespace std; #define x first #define y second typedef long long ll; int qsm(int a,int b,int c) { int res=1; while(b) { if(b&1) res=res*a%c; b>>=1; a=a*a%c; } return res; } const int N=30010; int a[N]; int d[N]; int n; bool isok() { int res=0; for(int i=2;i<=n;i++) if(d[i]>0)res+=d[i]; return d[n+1]+res<=0; } int main() { int t; cin>>t; while(t--) { cin>>n; for(int i=1;i<=n;i++) cin>>a[i],d[i]=a[i]-a[i-1]; d[n+1] = -a[n]; if(isok()) cout<<"YES"<<endl; else cout<<"NO"<<endl; } }V使用d【n+1】來消去各項