1. 程式人生 > 實用技巧 >大一寒假訓練碰到的有趣的題目

大一寒假訓練碰到的有趣的題目

收錄了部分有趣的題目

  字串:

  題目一: 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>
#include 
<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) using
namespace 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(); }
View Code

  題目三:前後綴+構造+思維題目 ,【考的時候確實不會,說起來容易做起來難(哭)】, 首先要會普通的抵消石子的遊戲,然後再一個一個點進行列舉看看更換之後是否能夠成功(最樸素的做法)。加入前後綴記錄優化,可以大幅降低執行時間。

  首先來看最普通的抵消石子:我們知道最後 能成功抵消的就只有一種情況,就是 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】來消去各項