1. 程式人生 > >開學第六測

開學第六測

與他 ide 分隔 什麽 text 序列 內容 fclose cor

          開學第六測

  又一次考試... 成績出來後心情無比的down

  為什麽??因為爆零了 2333

  每次考完試都會發題解 但是被點名必須要寫之後,就不是很想寫了

  接下來是正題: 

技術分享圖片

 T1 期望得分:20??

    (本題相當於 洛谷 P3365)

【題目描述】

  小Y在學樹論時看到了有關二叉樹的介紹:在計算機科學中,二叉樹是每個結點最多有兩個子結點的有序樹。通常子結點被稱作“左孩子”和“右孩子” 。二叉樹被用作二叉搜索樹和二叉堆。隨後他又和他人討論起了二叉搜索樹。
  什麽是二叉搜索樹呢?二叉搜索樹首先是一棵二叉樹。設key[p]表示結點p上的數值。對於其中的每個結點p,若其存在左孩子lch,則key[p]>key[lch];若其存在右孩子rch, 則key[p]<key[rch];註意,本題中的二叉搜索樹應滿足對於所有結點,其左子樹中的key小於當前結點的key,其右子樹中的key大於當前結點的key。

  小Y與他人討論的內容則是,現在給定一棵二叉樹,可以任意修改結點的數值。修改一個結點的數值算作一次修改,且這個結點不能再被修改。若要將其變成一棵二叉搜索樹, 且任意時刻結點的數值必須是整數(可以是負整數或0) ,所要的最少修改次數。

  相信這一定難不倒你!請幫助小Y解決這個問題吧。

【 輸入格式】

  第一行一個正整數 n 表示二叉樹結點數。結點從 1~n 進行編號。

  第二行 n 個正整數用空格分隔開,第 i 個數 ai 表示結點 i 的原始數值。

  此後 n - 1 行每行兩個非負整數 fa, ch,第 i + 2 行描述結點 i + 1 的父親編號 fa,以及父子關系 ch,(ch = 0 表示 i + 1 為左兒子,ch = 1 表示 i + 1 為右兒子)。

  結點 1 一定是二叉樹的根。

【 輸出格式】

  僅一行包含一個整數,表示最少的修改次數。

【 樣例輸入】

  3
  2 2 2
  1 0
  1 1

【 樣例輸出】

  2

【 數據範圍】

  20 % :n <= 10 , ai <= 100.

  40 % :n <= 100 , ai <= 200

  60 % :n <= 2000 .

  100 % :n <= 10 ^ 5 , ai < 2 ^ 31.

思路:20% :暴力。

   40% :可以用 DP 或者貪心或者神奇的暴力等其他奇怪的方法完成。

   60% :正解的 LIS 打成 O(n ^ 2)。

   100% :首先求出這顆二叉樹的中序遍歷,那麽問題就轉換成用最少的修改次數使這個整數序列嚴格單調遞增。於是很自然的想到了 LIS,但單純用 LIS 是有一些問題的,比如這種情況:2 3 1 4, LIS 為 2 3 4,答案求出來為 1,但由於整數的限制,應該要修改 2 次。即直接 LIS 求出的答案是在非嚴格遞增的情況下的答案。所以我們將原序列稍加修改,一個常見的將嚴格遞增整數序列映射成非嚴格遞增整數序列的技巧就是將如下序列:

      a1, a2, a3, a4 ... an 映射成:

      a1 - 1, a2 - 2, a3 - 3, a4 - 4 ... an - n. (這種方法常見於計數類問題)。
這樣映射後求最長不下降子序列的長度就沒問題了。

技術分享圖片
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 1e5 + 3;
int n, fa, d, sum, qr, l, r, mid, top, stk[N], f[N], a[N], b[N], lc[N], rc[N];
bool vis[N];

char ch;
int read() {
    while (ch = getchar(), ch < 0 || ch > 9);
    int res = ch - 48;
    while (ch = getchar(), ch >= 0 && ch <= 9) res = res * 10 + ch - 48;
    return res;
}

void Bfs() {
    int x; stk[top = 1] = 1;
    while (top) {
        x = stk[top];
        if (lc[x] && !vis[lc[x]]) {
            stk[++top] = lc[x];
            continue;
        }
        b[++sum] = a[x]; b[sum] -= sum;
        vis[x] = true; --top;
        if (rc[x] && !vis[rc[x]]) {
            stk[++top] = rc[x];
            continue;
        }
    }
    return ;
}

int main() {
//    freopen("binary.in", "r", stdin);
//    freopen("binary.out", "w", stdout);
    n = read();
    for (int i = 1; i <= n; ++i) a[i] = read();
    for (int i = 2; i <= n; ++i) {
        fa = read(); d = read();
        (d ? rc[fa] : lc[fa]) = i;
    }
    Bfs();
    f[qr = 1] = b[1];
    for (int i = 2; i <= n; ++i) {
        if (b[i] >= f[qr]) f[++qr] = b[i];
        else {
            l = 1; r = qr;
            while (l <= r) {
                mid = l + r >> 1;
                if (f[mid] <= b[i]) l = mid + 1;
                else     r = mid - 1;
            }
            f[l] = b[i];
        }
    }
    cout << n - qr << endl;
//    fclose(stdin); fclose(stdout);
    return 0;
}
AC code

T2 期望得分:30 or 60 ??

【題目描述】

  小 H 是個善於思考的學生,現在她又在思考一個有關序列的問題。
  她的面前浮現出一個長度為 n 的序列{ai},她想找出一段區間[L, R](1 <= L <= R <= n)。

  這個特殊區間滿足,存在一個 k(L <= k <= R),並且對於任意的 i(L <= i <= R),ai 都能被 ak 整除。這樣的一個特殊區間 [L, R]價值為 R - L。

  小 H 想知道序列中所有特殊區間的最大價值是多少,而有多少個這樣的區間呢?這些區間又分別是哪些呢?你能幫助她吧。

【 輸入格式】

  第一行,一個整數 n.

  第二行,n 個整數,代表 ai.

【 輸出格式】

  第一行兩個整數,num 和 val,表示價值最大的特殊區間的個數以及最大價值。

  第二行 num 個整數,按升序輸出每個價值最大的特殊區間的 L.

【 樣例輸入1】

  5

  4 6 9 3 6

【 樣例輸出1】

  1 3

  2

【 樣例輸入2】

  5

  2 3 5 7 11

【 樣例輸出2】

  5 0

  1 2 3 4 5

【 數據範圍】

  30 % : 1 <= n <= 30 , 1 <= ai <= 32.

  60 % :1 <= n <= 3000 , 1 <= ai <= 1024.

  80 % : 1 <= n <= 300000 , 1 <= ai <= 1048576.

  100 % :1 <= n <= 500000 , 1 <= ai < 2 ^ 31.


思路1:30% :暴力枚舉判斷。O(n^4)。
   60% :特殊區間的特點實際上就是區間最小值等於這個區間的 GCD,於是暴力或遞推算出每個區間的最小值與 GCD。而對於最大價值,可以通過二分來進行求解。復雜度 O(n^2)。

    100%:在 60%的基礎上,最小值與 GCD 都使用 RMQ 算法來求解,對於這道題推薦使用ST 表。最大價值仍然使用二分。復雜度 O(nlogn)。

思路2:因為 L <= k <= R,所以可以從每一個數開始向前、向後擴展,計算有多少個數可以被這個數整除。

  例:數列 24 12 6 5 8 7 中,從6向前擴展時,12可以被6整除,而在擴展12時已知24能被12整除,所以6也能擴展到24。

  那麽每個數只需要被擴展一次即可,整體思路有些類似於記憶化,時間復雜度是比思路1更優的,是O(n)。

思路3:線段樹維護每個區間的GCD,然後二分( 好像AC不了)

技術分享圖片
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

const int N = 5e5 + 3, M = 21;
int n, m, a, ans, l, r, mid, sum;
int A[N], f[N][M], g[N][M], p[M];

inline int Gcd(const int &x, const int &y) {
    return y == 0 ? x : Gcd(y, x % y);
}

bool check(int len) {
    int q = log2(len--), k = n + 1 - p[q], j;
    for (int i = 1; i <= k; ++i) {
        j = i + len;
        if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
            return true;
    }
    return false;
}

char ch;
inline int read() {
    while (ch = getchar(), ch < 0 || ch > 9);
    int res = ch - 48;
    while (ch = getchar(), ch >= 0 && ch <= 9) res = res * 10 + ch - 48;
    return res;
}

char s[10];
inline void print(int x) {
    int res = 0;
    if (x == 0) putchar(0);
    while (x) {
        s[++res] = x % 10;
        x /= 10;
    }
    for (int i = res; i; --i) putchar(s[i] + 0);
    putchar( );
    return ;
}

int main() {
//    freopen("pair.in", "r", stdin);
//    freopen("pair.out", "w", stdout);
    n = read(); m = log2(n);
    for (int i = 1; i <= n; ++i) {
        a = read();
        f[i][0] = g[i][0] = a;
    }
    for (int i = 0; i <= m; ++i) p[i] = 1 << i;
    for (int j = 1; j <= m; ++j) {
        int k = n + 1 - p[j];
        for (int i = 1; i <= k; ++i) {
            f[i][j] = min(f[i][j - 1], f[i + p[j - 1]][j - 1]);
            g[i][j] = Gcd(g[i][j - 1], g[i + p[j - 1]][j - 1]);
        }
    }
    l = 1; r = n;
    while (l <= r) {
        mid = l + r >> 1;
        if (check(mid)) l = mid + 1;
        else     r = mid - 1;
    }
    ans = r;
    if (ans == 1) {
        printf("%d %d\n", n, 0);
        for (int i = 1; i < n; ++i)
            print(i);
        printf("%d\n", n);
    }
    else {
        int q = log2(ans--), k = n + 1 - p[q], j;
        for (int i = 1; i <= k; ++i) {
            j = i + ans;
            if (min(f[i][q], f[j - p[q] + 1][q]) == Gcd(g[i][q], g[j - p[q] + 1][q]))
                A[++sum] = i;
        }
        printf("%d %d\n", sum, ans);
        for (int i = 1; i < sum; ++i)
            print(A[i]);
        printf("%d\n", A[sum]);
    }
//    fclose(stdin); fclose(stdout);
    return 0;
}
思路1 code 技術分享圖片
#include<cstring>
#include<cstdio>
#include<cmath>
using namespace std;

const int MAXN = 1e6 + 10;
int N, a[MAXN];
int ans[MAXN], mx, L[MAXN], R[MAXN], happen[MAXN];

inline int read() {
    char c = getchar(); int x = 0, f = 1;
    while(c < 0 || c > 9){if(c == -) f = -1; c = getchar();}
    while(c >= 0 && c <= 9) x = x * 10 + c - 0, c = getchar();
    return x * f;
}
void GetL(int now) {
    int cur = now;
    while(cur >= 1 && a[cur] % a[now] == 0) 
        L[now] = L[cur], cur = L[cur] - 1;
}
void GetR(int now) {
    int cur = now;
    while(cur <= N && a[cur] % a[now] == 0) 
        R[now] = R[cur], cur = R[cur] + 1;
}
int main() {
//    freopen("pair.in", "r", stdin);
//    freopen("pair.out", "w", stdout);
    N = read();
    for(int i = 1; i <= N; i++) a[i] = read(); 
    for(int i = 1; i <= N; i++) {
        L[i] = i;
        GetL(i);
    }
    for(int i = N; i >= 1; i--) {
        R[i] = i;
        GetR(i);
    }
    for(int i = 1; i <= N; i++) {
        ans[i] = R[i] - L[i];
        if(ans[i] > mx) mx = ans[i];
    }
    int num = 0;
    for(int i = 1; i <= N; i++)
        if(ans[i] == mx && !happen[L[i]])
            num++, happen[L[i]] = 1;
    memset(happen, 0, sizeof(happen));
    printf("%d %d\n", num, mx);
    for(int i = 1; i <= N; i++)
        if(ans[i] == mx && !happen[L[i]])
            printf("%d ", L[i]), happen[L[i]] = 1;
//    fclose(stdin); fclose(stdout);
    return 0;
}
思路2 code 技術分享圖片
#include<iostream>
#include<cstdio>
#include<map>
#define N 1000005
using namespace std;
int gcd(int i, int j) { return j ? gcd(j, i % j) : i; }

map<int, int> ma;
int n, k[N];
int tot, ans, sum;
int t[N << 2];
int Answer[N];

void build(int l, int r, int ret) {
    if(l == r) { t[ret] = k[l]; return ; }
    int mid = (l+r) >> 1;
    build(l, mid, ret << 1);
    build(mid+1, r, ret << 1 | 1);
    t[ret] = gcd(t[ret << 1], t[ret << 1 | 1]);
}

int query(int l, int r, int ret, int L, int R) {
    if(l>=L && r<=R) return t[ret];
    int mid = (l+r) >> 1;
    if(L<=mid && R>mid) return gcd(query(l, mid, ret << 1, L, R), query(mid+1, r, ret << 1 | 1, L, R));
    if(L<=mid) return query(l, mid, ret << 1, L, R);
    if(R>mid) return query(mid+1, r, ret << 1 | 1, L, R);
}

bool check(int mid){
    int tmp;
    for(int i = 1; i + mid <= n; ++i){
        tmp = query(1, n, 1, i, i + mid);
        if(tmp>1 && ma.find(tmp) != ma.end()) {
            ans = max(ans, mid);
            return true;
        }
    }
    return false;
}

int main(){
//    freopen("pair.in","r",stdin);
//    freopen("pair.out","w",stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) {
        scanf("%d", &k[i]);
        ma[k[i]] = ++tot;
    }
    build(1, n, 1);
    int l = 1, r = n, mid;
    while(l <= r) {
        mid = (l+r) >> 1;
        if(check(mid)) l = mid+1;
        else r = mid-1;
    }
    int tmp, num = 0;
    for(int i = 1; i+ans <= n; ++i) {
        tmp = query(1, n, 1, i, i + mid);
        if(ma.find(tmp)!=ma.end() || ans==0) {
            Answer[++num] = i;
        }
    }
    printf("%d %d\n", num, ans);
    for(int i = 1; i <= number; ++i)
        printf("%d ", Answer[i]);
    putchar(\n);
//    fclose(stdin); fclose(stdout);
    return 0;
}
思路3 code

T3 期望得分:0 (連題目都沒看懂 2333)

【 題目描述】

  給定一個{0, 1, 2, 3, … , n - 1}的排列 p。一個{0, 1, 2 , … , n - 2}的排列 q 被認為是優美的排列,當且僅當 q 滿足下列條件:

    對排列 s = {0, 1, 2, 3, ..., n - 1}進行 n – 1 次交換。

    1. 交換 s[q0],s[q0 + 1]

    2. 交換 s[q1],s[q1 + 1]

    .....

    最後能使得排列 s = p.

  問有多少個優美的排列,答案對 10^9+7 取模。

【 輸入格式】

  第一行,一個整數 n.

  第二行 ,n 個整數代表排列 p.

【 輸出格式】

  僅一行表示答案。

【 樣例輸入】

  3

  1 2 0

【 樣例輸出】

  1

【樣例解釋】

  q = {0,1} {0,1,2} ->{1,0,2} -> {1, 2, 0}

  q = {1,0} {0,1,2} ->{0,2,1} -> {2, 0, 1}

【 數據範圍】

  30 % : 1 <= n <= 10.

  100 % :1 <= n <= 50.

思路:30%:枚舉所有排列,判定即可

   100%:考慮倒著處理, 比如交換 (i, i + 1), 那麽前面的所有數不管怎麽交換都無法到後面去(下標恒小於等於 i),後面的數也是一樣到不了前面。說明這最後一次交換前,就要求對於所有的 x <= i, y > i,px < py。所以交換前左邊的數是連續的,右邊也是連續的。由於交換前, 前面和後面的數是互相不幹涉的, 所以就歸結成了兩個子問題。 於是我們可以用記憶化搜索來解決這個問題。

   設 dp[ n ][ low ] 代表長度為 n,H 是{ low, low + 1, …, low + n - 1 }的排列,且 H 是 p 的子序列,在 H 上優美序列的個數。

   我們枚舉交換哪兩個相鄰元素( k, k+1 ), 然後判斷 k 前面的所有數是否都小於後面的所有數,如果是則進行轉移 dp[ n ][ low ] += dp[ k ][ low ] * dp[ n – k ][ low + k ] * C( n – 2, n – 1 - k )。即前面的 k 個元素與後面的 n - k 個元素是兩個獨立的子問題,前面是{ low ... low + k - 1 }的排列,後面是{ low + k ... low + n - 1 }的排列,C( n - 2, n - 1 - k)代表的是在交換( k, k + 1 )前左右兩邊還分別要進行 n - 2 次交換,而每次交換左邊與交換右邊是不同方案,這相當於 n - 2個位置選擇 n - 1 - k 個位置填入,故還需要乘上 C( n - 2, n - 1 - k )。時間復雜度為 O( n^4 )。

技術分享圖片
#include<algorithm>
#include<iostream>
#include<cstring>
#include<cstdio>
#define MAXN 51
#define mod 1000000007
using namespace std;
int n, ans;
int vis[MAXN];
int p[MAXN], q[MAXN], s[MAXN];
int judge() {
    for(int i = 0; i < n; i++) s[i] = i;
    for(int i = 0; i < n-1; i++)
        swap(s[q[i]], s[q[i]+1]);
    for(int i = 0; i < n; i++)
        if(s[i] != p[i]) return false;
    return true;
}
void dfs(int tot) {
    if(tot == n-1){
        if(judge())    ans++;
        return ;
    }
    for(int i = 0; i < n;i++)
        if(!vis[i]) {
            vis[i] = 1; q[tot] = p[i];
            dfs(tot + 1);
            vis[i] = 0;
        }
}
int main() {
//    freopen("swap.in","r",stdin);
//    freopen("swap.out","w",stdout);
    scanf("%d", &n);
    for(int i = 0; i < n; i++) scanf("%d", &p[i]);
    dfs(0);
    printf("%d\n", ans % mod);
//    fclose(stdin); fclose(stdout);
}
30分暴力 技術分享圖片
#include <algorithm>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <cmath>
using namespace std;

typedef long long ll;
const int N = 52, Mod = 1e9 + 7;
int n, p[N], dp[N][N], C[N][N];

int Dfs(int len, int low) {
    if(dp[len][low] != -1) return dp[len][low];
    if(len == 1) return dp[len][low] = 1;
    int &res = dp[len][low]; res = 0;
    int t[N], m = 0, j, k;
    for(int i = 1; i <= n; ++i)
        if(p[i] >= low && p[i] < low + len)
            t[++m] = p[i];
    for(int i = 1; i < m; ++i) {
        swap(t[i], t[i + 1]);
        for(j = 1; j <= i; ++j)
            if (t[j] >= low + i) break;
        for(k = i + 1; k <= m; ++k)
            if (t[k] < low + i) break;
        if(j > i && k > m) {
            ll tmp = (ll)Dfs(i, low) * Dfs(m - i, low + i) % Mod;
            tmp = tmp * C[m - 2][i - 1] % Mod;
            res = (res + tmp) % Mod;
        }
        swap(t[i], t[i + 1]);
    }
    return res;    
}

int main() {
//    freopen("swap.in", "r", stdin);
//    freopen("swap.out", "w", stdout);
    scanf("%d", &n);
    for(int i = 1; i <= n; ++i) scanf("%d", &p[i]);
    memset(dp, -1, sizeof(dp));
    for(int i = 0; i <= n; ++i) {
        C[i][0] = 1;
        for(int j = 1; j <= i; ++j)
            C[i][j] = (C[i - 1][j - 1] + C[i - 1][j]) % Mod;
    }
    Dfs(n, 0);
    if(dp[n][0] != -1) cout << dp[n][0] << endl;
    else puts("0");
//    fclose(stdin); fclose(stdout);
    return 0;
}
滿分做法

開學第六測