程序員進階之算法練習(九十四)

題目1

題目鏈接
題目大意:
有n個整數組成的數組a,現在可以對數組a的元素任意打亂順序,要求滿足:
假設打亂后的數組是b,要滿足:??1+??2=??2+??3=…=?????1+????=k
也就是相鄰兩個數字的和相同。

輸入:
第一行,整數?? 表示t個樣例 ?? (1≤??≤100)
每個樣例兩行
第一行整數??(2≤??≤100)
第二行n個整數??1,??2,…,????(1≤????≤1e5).

輸出:
每個樣例一行,通過打亂順序生成滿足要求的數組,如果有解,則輸出YES;如果無解,則輸出NO;

Examples
input
5
2
8 9
3
1 1 2
4
1 1 4 5
5
2 3 3 3 3
4
100000 100000 100000 100000

output
Yes
Yes
No
No
Yes

題目解析:
將兩兩相鄰的數字相加,a1+a2, a2+a3...
全部累加,得到(a1+a2)+(a2+a3)+(a3+a4)...
最終就是a1+2x(a2+a3+...+a[n-1])+an
這樣只要枚舉a1和an的值,就可以通過數組和sum,快速得到(a2+a3+...a[n-1])=sum-a1-an;
那么兩數相加和k = (a1 + 2x(sum-a1-an) + an) / (n - 1);

a1是枚舉,那么a2=k-a1,以此類推枚舉a3,在這個過程中如果發現數組b不存在當前的數字,那么則無解。
但是走到這步的時候,發現了數組的規律: a1, k - a1, a1, k-a1
其實不用復雜計算,最終數組結果必然只有2個數字。
那么只需要統計數組a的元素,如果大于2個元素則必然無解。
只有1個元素,或者2個元素的數量相差不超過1,就有解。

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            int sum = 0;
            map<int, int> h;
            vector<int> vec;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
                sum += a[i];
                h[a[i]]++;
                if (h[a[i]] == 1) vec.push_back(a[i]);
            }
            int ans = 0;
            if (vec.size() == 1) ans = 1;
            else if (vec.size() == 2) {
                if (abs(h[vec[0]] - h[vec[1]]) <= 1) ans = 1;
            }
            cout << (ans ? "YES" : "NO") << endl;
        }
    }
}
ac;

題目2

題目鏈接
題目大意:
有兩個0、1組成的字符串s和t,長度分別為n和m;
如果字符串a滿足
對所有的 all ??=1,2,…,???1 ,有 ????≠????+1,則可以認為字符串a是good;

現在允許在字符串s中任意位置插入若干次字符串t,問最終能否生成一個good的字符串;

輸入:
第一行,整數?? 表示t個樣例 ?? (1≤??≤100)
每個樣例3行
第1行整數?? and ?? (1≤??,??≤50 )
第2、3行分別是字符串s和t

輸出:
每個樣例一行,如果可以生成good字符串,則輸出YES;否則輸出NO;

Examples
input
5
1 1
1
0
3 3
111
010
3 2
111
00
6 7
101100
1010101
10 2
1001001000
10

output
Yes
Yes
No
No
No

題目解析:
按照題目要求,good字符串就是字符0和1交替的字符串,比如說10101或者0101;
那么字符串中如果存在00或者11,就表示不滿足要求;

由于題目可以插入字符串t,那么首先對字符串t做同樣的檢查,要求不含有00和11;
字符串t要插入到字符串s中,且只會插入到00和11,那么必然字符串首尾必然相同,要么都是0、要么都是1;

字符串s如果僅存在00或者11,那么可以考慮插入符合要求的字符串t;
00只會插入首尾都是1的good字符串t;
11只會插入首尾都是0的good字符串t;

class Solution {
    static const int N = 201010;
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n, m;
            cin >> n >> m;
            string s, t;
            cin >> s >> t;
            int flag[2] = {0};
            for (int i = 1; i < n; ++i) {
                if (s[i] == s[i - 1]) flag[s[i] - '0'] = 1;
            }
            int ans = 0;
            if (flag[0] + flag[1] == 0) ans = 1;
            else if (flag[0] + flag[1] == 2) ans = 0;
            else {
                int sec[2] = {0};
                for (int i = 1; i < m; ++i) if (t[i] == t[i - 1]) sec[t[i] - '0'] = 1;
                if (sec[0] + sec[1] == 0 && t[0] == t[m - 1] && (flag[t[0] - '0']== 0)) {
                    // 不含00和11,并且頭尾相同,并且互補
                    ans = 1;
                }
            }
            cout << (ans ? "YES" : "NO") << endl;
        }
    }
}
ac;

題目3

題目鏈接
題目大意:
有一堆石子,初始的時候有若干個;
有兩個人在玩游戲,每個人輪流移除石子,每次可以移除a個石子或者b個石子,無法移除者失敗;
假設兩個都采用了理想最優策略,現在已知a和b,問最初的時候有多少個石子,能夠保證后手者勝利。

輸入:
第一行,整數?? 表示t個樣例 ?? (1≤??≤100)
每個樣例一行,整數?? and ?? (1≤??<??≤100)

輸出:
每個樣例一行,輸出初始化狀態的石子數量?? (1≤??≤1e6 );

Examples
input
3
1 4
1 5
9 26

output
2
6
3

題目解析:
只要讓先手者不能取,那么后手者勝利。
a>1,則直接n=1,先手無解;
a=1,則n=a+b,先手不管取a或者b,后手跟著取b或者a即可;

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int x, y;
            cin >> x >> y;
            if (x > 1) cout << 1 << endl;
            else cout << x + y << endl;
        }
    }
}
ac;

題目4

題目鏈接
題目大意:
首先定義MEX,對于一個區間,這個區間內未出現過的最小正整數就是這個區間的MEX值;
[2,3,1,5,4]的MEX值就是6;

現在需要構造一個的整數數組,要求滿足:
1、數組的元素是由1到n的排列組成;
2、數組所有的子區間,MEX值是素數的盡可能多;

輸入:
第一行,整數?? 表示t個樣例 ?? (1≤??≤10000)
每個樣例一行,整數?? (1≤??≤200000).

輸出:
每個樣例一行,輸出1到n的n個整數。

Examples
input
3
2
1
5

output
2 1
1
5 2 1 4 3

題目解析:
根據題目的要求,如果某個區間中,數字1沒有出現,那么必然MEX值為1,1不是素數;
那么1必然要被包括在區間內;

想要讓區間的MEX為素數,那么這個區間內就不要包括某個素數,這里選擇2和3;(因為最小,最容易滿足)
只要2不出現在區間中,那么區間的MEX值必然為2;
比如說1xxx2,區間1x 1xx 1xxx都滿足要求;

為了讓結果盡可能大,應該要讓數字均勻分布在1的左右兩邊;
形成 2 xxxx 1 xxxxx 3這樣的布局,就可以讓結果盡可能的多。

class Solution {
    static const int N = 201010;
    int a[N];
public:
    void solve() {
        for (int i = 2; i < N; ++i) {
            int m = sqrt(i) + 1, find = 0;
            for (int j = 2; j <= m && j < i; ++j) {
                if (i % j == 0) find = 1;
            }
            a[i] = !find;
        }
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            if (n == 1) cout << "1" << endl;
            else if (n == 2) cout << "2 1" << endl;
            else {
                vector<int> ans;
                ans.push_back(2);
                for (int i = 0; i < (n - 3) / 2; ++i) ans.push_back(4 + i);
                ans.push_back(1);
                for (int i = (n - 3)/ 2; i < n - 3; ++i) ans.push_back(4 + i);
                ans.push_back(3);
                //            for (int i = 2; i <= n; ++i) if (!a[i]) ans.push_back(i);
                for (int i = 0; i < ans.size(); ++i) cout << ans[i] << " ";
                cout << endl;
            }
        }
    }
}
ac;

題目5

題目鏈接
題目大意:
有n個整數的數組,現在可以對數組進行操作:
1、選擇某個位置的元素;
2、移除該元素;
3、如果該元素原來左右兩邊均有數字,那么這兩個數字也會移除,合并成一個新的數字;(如果左右兩邊只有1個數字,則不會觸發合并)
如下圖所示:

現在想知道,經過若干次操作后,數組剩下一個元素時,這個元素最大值為多少?

輸入:
第一行,整數?? 表示t個樣例 ?? (1≤??≤10000)
每個樣例第一行整數?? (1≤??≤20000).
接下來n個整數 ??1,…,????(?1e9≤????≤1e9).

輸出:
每個樣例一行,輸出結果的最大值。

Examples
input
3
6
-3 1 4 -1 5 -9
5
998244353 998244353 998244353 998244353 998244353
1
-2718

output
9
2994733059
-2718

題目解析:
這個題目最終實現不復雜,但是在分析過程踩了一下坑,這里特意做一個記錄,簡單回顧下出問題點。

最開始的思路:
簡化題目,只考慮有正數的情況,是否有簡單的策略:
每次會發生可能兩個行為,數字移除+數字合并,數字移除會減少一個數字并影響最終的和,數字合并不會直接影響最終的和,但是仍然會對結果有影響,因為我們減少了一個可移除的數字選擇;
比如說【10,5,1,5,10】這樣的數組,當我們先選擇1時,剩下【10, 10,10】,最終結果是20;如果只選擇移除整數5,那么最終結果是21;
那么就不能采用簡單的從最小開始移除的合并策略(尤其此時還沒引入負數)。
觀察區間的特性,我們知道對于每一個新增的數字,有兩個選擇:
1、直接移除,此時在最右邊,原來區間不會發生任何事情;
2、原區間右邊最右整數被移除,當前數字被進行合并;
根據兩個選擇可以得到對應的狀態轉移方案。

到這里整體思路還算清晰,但是出現一個問題,當進行決策1之后,該位置的數字必須全部摒棄。
那么每個位置應該有兩個狀態:
1、當前數字是被移除;(dp[i][0])
2、當前數字是保留的狀態;(dp[i][1])
dp[i][0]可以由dp[i-1][1]轉移,dp[i][1]可以由dp[i-1][0]轉移,轉移方程:
dp[i][0] = dp[i-1][1];
dp[i][1] = max(0LL, dp[i-1][0]) + a[i];

看著一切美好,但是在遇到下面這串樣例的時候,問題就出現了:
5 -1 -2 -3 6

這里動態規劃會出問題,因為當a[i]=6時,我們選擇保留元素6,那么就會remove -3,結果就是-2和6合并了。
我們知道最優解是先選擇數字-2,生成5 -4 6;再選擇-4,得到結果11;
因為這里的決策無后效性并不滿足。

舍棄動態規劃,從題目的操作特性來分析,對于相鄰的兩個數字,必然不能融合。
奇數位置的數字,只能和奇數位置融合;
偶數位置的數字,只能和偶數位置融合;

那么是否可以做到任意奇數位置的整數都能融合?
首先,連續的奇數位置必然是可以融合,因為只間隔著1個數字,選擇remove該整數即可;
對于不間斷的奇數位置,我們只需考慮間隔1、2個整數的情況皆可,其他間隔情況都可以由這2個情況來推出。
間隔1個整數的情況,比如說 [1,-2,-2,-2,1],奇數位置整數是[1,-2,1],我們可以選擇第一次remove最中間的-2,得到[1, -4, 1],再選擇中間的整數-4,即可得到2;
間隔2個整數的情況,比如說 [1,-2,-2,-3,-3,-1,1],同樣的操作,我們一直選擇remove最中間的整數,由于每次我們remove1個整數,融合減少1個整數,最終一定會剩下[1, x, 1]這樣的結果,也即是必然可以融合得到2;

那么這個題目就比較簡單了,只需要考慮奇數位置、偶數位置上選擇若干個整數,得到的最小和。

class Solution {
    static const int N = 201010;
    lld dp[N][2], a[N];
public:
    void solve() {
        int t;
        cin >> t;
        while (t--) {
            int n;
            cin >> n;
            for (int i = 0; i < n; ++i) {
                cin >> a[i];
//                if (i == 0) dp[0][1] = a[i];
//                if (i == 1) {
//                    dp[1][0] = dp[0][1];
//                    dp[1][1] = a[1];
//                }
//                if (i >= 2) {
//                    dp[i][0] = dp[i-1][1];
//                    dp[i][1] = max(0LL, dp[i-1][0]) + a[i];
//                }
//                ans = max(ans, dp[i][1]);
//                cout << i << " " << dp[i][0] << " " << dp[i][1] << endl;
            }
            if (n == 1) cout << a[0] << endl;
            else {
                lld ans[2] = {a[0], a[1]};
                for (int i = 2; i < n; ++i) {
                    if (a[i] < 0) ans[i%2] = max(ans[i%2], a[i]);
                    else {
                        ans[i%2] = max(a[i], ans[i%2] + a[i]);
                    }
                }
                cout << max(ans[0], ans[1]) << endl;
                
            }
            
        }
    }
}
ac;
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容