标签: 构造

  • CF1521C Nastia and a Hidden Permutation

    真是个沙比提,比赛的时候也想出来了,但是因为太迷糊了没写完

    首先我们先考虑如何确定下最大值所在的位置。

    执行询问$max(min(n-1,p_i),min(n,p_{i+1}))$,这个询问实际上等价于$max(min(n-1,p_i),p_{i+1})$。

    • 如果结果是n,那么说明$p_{i+1}=n$(很显然,max里第一项不会超过n-1)。
    • 如果结果比n-1要小,那么说明$p_i$和$p_{i+1}$都不是n。
    • 如果结果是n-1,那么就要讨论下了。这个时候n可能出现在这两个数里,也可能没出现。

    我们再构造另一个询问:$min(max(n-1,p_i),max(n,p_{i+1}))$,显然这个询问等价于$max(n-1,p_i)$,所以我们可以利用这个询问来检查$p_i$的值是不是n。

    在找到最大值的位置,设它为$maxpos$,那么我们对于每一个$i\neq maxpos$,构造询问$min(max(1,p_i),max(2,p_{maxpos}))$,这个询问等价于$p_i$,然后就可以求出来每个位置的值了。

    #include <algorithm>
    #include <iostream>
    using std::cin;
    using std::cout;
    using std::endl;
    using int_t = long long;
    int_t result[int_t(1e5)];
    int main() {
        std::ios::sync_with_stdio(false);
        int_t T;
        cin >> T;
        while (T--) {
            int_t n;
            cin >> n;
            int_t maxpos = -1;
            const auto check = [&](int_t i, int_t j) {
                cout << "? 1 " << i << " " << j << " " << n - 1 << endl;
                int_t x;
                cin >> x;
                if (x == n) {
                    maxpos = j;
                    return;
                }
                if (x < n - 1)
                    return;
                // x==n-1
                for (int_t _ = 1; _ <= 2; _++) {
                    cout << "? 2 " << i << " " << j << " "<< n - 1 << endl;
                    cin >> x;
                    if (x == n) {
                        maxpos = i;
                    }
                    std::swap(i, j);
                }
            };
            for (int_t i = 1; i <= n && i + 1 <= n && maxpos == -1; i += 2) {
                int_t j = i + 1;
    
                check(i, j);
            }
            if (maxpos == -1) {
                maxpos = n;
            }
            result[maxpos] = n;
            for (int_t i = 1; i <= n; i++) {
                if (i == maxpos)
                    continue;
                cout << "? 2 " << i << " " << maxpos << " 1" << endl;
                ;
                int_t x;
                cin >> x;
                result[i] = x;
            }
            cout << "! ";
            for (int_t i = 1; i <= n; i++) {
                cout << result[i] << " ";
            }
            cout << endl;
        }
        return 0;
    }
    

     

  • CF1153C Serval and Parenthesis Sequence

    普及题我都不会做…我还能说什么…

    首先原题等价于删掉首尾后,剩下的部分是一个合法的括号序列。

    所以先判掉首尾不合法。

    然后从第2个元素扫到第n-2个元素,统计出空白的位置和前缀和(记左括号为1,右括号为-1)

    如果前缀和为负,那么就先安排上一定数量的左括号。

    如果前缀和为正,那么就先安排上一定数量的右括号。

    如果前缀和为0,那么左右括号安排相等的数量。

    然后我们知道了左右括号各需要多少个,从头开始扫,遇到空位优先放左括号,放完了再放右括号。

    最后检查一遍是否合法即可。

    #include <assert.h>
    #include <cstdlib>
    #include <iostream>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 1e6;
    char str[LARGE + 1];
    int_t val[LARGE + 1];
    int_t n;
    void fail() {
        cout << ":(";
        exit(0);
    }
    int main() {
        cin >> n;
        scanf("%s", str + 1);
        int_t count = 0;
        int_t prefix = 0;
        for (int_t i = 2; i <= n - 1; i++) {
            if (str[i] == '(')
                val[i] = 1;
            else if (str[i] == ')')
                val[i] = -1;
            else
                count++;
            prefix += val[i];
        }
        int_t a = 0, b = 0;
        if (prefix < 0)
            a += -prefix;
        else
            b += prefix;
        int_t diff = count - a - b;
    
        if (diff < 0) {
            fail();
    
        } else if (diff % 2)
            fail();
        else {
            a += diff / 2;
            b += diff / 2;
        }
    #ifdef DEBUG
        cout << "a = " << a << " b = " << b << endl;
    #endif
        if (str[1] == ')') fail();
        if (str[n] == '(') fail();
        for (int_t i = 2; i <= n - 1; i++) {
            if (val[i] == 0) {
                if (a > 0) {
                    val[i] = 1;
                    a--;
                } else if (b > 0) {
                    val[i] = -1;
                    b--;
                }
            }
        }
        if (a || b) fail();
        val[1] = 1;
        val[n] = -1;
        prefix = 0;
        for (int_t i = 1; i < n; i++) {
            prefix += val[i];
            if (prefix == 0) fail();
        }
        prefix += val[n];
        if (prefix != 0) fail();
        for (int_t i = 1; i <= n; i++) {
            if (val[i] == 1)
                putchar('(');
            else if (val[i] == -1)
                putchar(')');
            else
                assert(false);
        }
        return 0;
    }

     

  • UOJ460 新年的拯救计划

    神仙构造题…没智商..

    考虑构造出一棵树$T_0$,对于$T_0$中的任意一条边$(u,v)$,我们在第k$(0<k\leq \lfloor\frac n 2\rfloor)$棵树中添加边$((u+k)~mod~n,(v+k)~mod~n)$。

    现在考虑什么样子的$T_0$能保证对于任意$k_1\neq k_2$,均满足$((u+k_1)~mod~n,(v+k_1)~mod~n)\neq ((u+k_2)~mod~n,(v+k_2)~mod~n)$。

    首先答案一定有$\lfloor \frac n 2 \rfloor$棵生成树,那么我们可以考虑把点集分成两半,每个点集里面的边全都从某个点出发,这样我们只需要变化这个出发点,其他所有边也会跟着平移,就可以保证所有边不重复。

    设$m=\lfloor \frac n 2\rfloor$,首先令点编号从0开始。

    首先连边$(0,m)$,这条边显然在任何一棵树中都不会重复。

    对于$1\leq i\leq m-1$,连边$(0,i)$.

    对于$ m<i<n$,连边$(m,i)$。

    这样在$0+k和m+k$不重复的情况下,任何生成树内一定不会出现重复边了。

    #include <algorithm>
    #include <iostream>
    #include <tuple>
    #include <utility>
    #include <vector>
    using int_t = long long int;
    using pair_t = std::pair<int_t, int_t>;
    using std::cin;
    using std::cout;
    using std::endl;
    std::vector<pair_t> edges;
    int main() {
        int_t n;
        cin >> n;
        int_t mid = n / 2;
        edges.push_back(pair_t(0, mid));
        for (int_t i = 1; i < mid; i++) {
            edges.push_back(pair_t(0, i));
        }
        for (int_t i = mid + 1; i < n; i++) {
            edges.push_back(pair_t(mid, i));
        }
        cout << n / 2 << endl;
        for (int_t i = 0; i < n / 2; i++) {
            for (const auto& edge : edges) {
                int_t a, b;
                std::tie(a, b) = edge;
                cout << (a + i) % n + 1 << " " << (b + i) % n + 1 << " ";
            }
            cout << endl;
        }
        return 0;
    }

     

  • 雅礼集训D4T2 gre

    很毒瘤。

    考虑递归构造。

    首先k=1的时候,答案为a。

    增加k时,将现有字符串中的字符全部顺移一位(aabaa变成bbcbb)

    然后新字符串先放两个a,然后遍历旧字符串,每次遇到一个b就放ba,直到放完,然后再放一个a。

    最后把不到n的部分拿a补上。

    #include <assert.h>
    #include <algorithm>
    #include <cstdio>
    #include <cstring>
    #include <iostream>
    #include <map>
    #include <vector>
    using int_t = long long int;
    using std::cerr;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 1e5;
    
    void process() {
        int_t n, k;
        cin >> n >> k;
        std::vector<char> buf;
        buf.push_back('a');
        for (int_t i = 1; i <= k - 1; i++) {
            std::vector<char> next{'a', 'a'};
            for (char chr : buf) {
                next.push_back(chr + 1);
                if (chr == 'a') {
                    next.push_back('a');
                }
            }
            next.push_back('a');
            buf = next;
        }
    #ifdef DEBUG
        for (char chr : buf) cout << chr << " ";
        cout << endl;
    #endif
        if (n < buf.size()) {
            printf("CiYe\n");
            return;
        }
        for (int_t i = 1; i <= n - buf.size(); i++) {
            printf("a");
        }
        for (char chr : buf) printf("%c", chr);
        cout << endl;
    }
    
    int main() {
        int_t T;
        cin >> T;
        while (T--) {
            process();
        }
    
        return 0;
    }

     

  • CF487C Prefix Product Sequence

    n为大于4的合数时无解。

    此时一定可以找出来两个小于n的数使他们的乘积是n的倍数。

    对于n=1,2,3,4特判。

    其他情况,令序列第一项为0,第$i(i<n)$项为$\frac i {i-1}$,第n项为n即可。

    #include <iostream>
    
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    int_t n;
    int_t inv(int_t base) {
        int_t result = 1;
        int_t index = n - 2;
        while (index) {
            if (index & 1) result = result * base % n;
            base = base * base % n;
            index >>= 1;
        }
        return result;
    }
    int main() {
        cin >> n;
        if (n <= 4) {
            if (n == 1) {
                cout << "YES\n1" << endl;
            } else if (n == 2) {
                cout << "YES\n1\n2" << endl;
            } else if (n == 3) {
                cout << "YES\n1\n2\n3" << endl;
            } else if (n == 4) {
                cout << "YES\n1\n3\n2\n4" << endl;
            }
            return 0;
        }
        for (int_t i = 2; i * i <= n; i++)
            if (n % i == 0) {
                cout << "NO" << endl;
                return 0;
            }
        cout << "YES" << endl;
        cout << 1 << endl;
        for (int_t i = 2; i < n; i++) cout << i * inv(i - 1) % n << endl;
        cout << n << endl;
        return 0;
    }

     

  • CF226D Table

    每次找一个和不为非负的行或者列取反,这样矩阵的和一定不会变小。

    出现了偶数次的行和列操作没有意义。

    #include <algorithm>
    #include <cstdio>
    #include <iostream>
    #include <vector>
    using int_t = long long int;
    using std::cin;
    using std::cout;
    using std::endl;
    
    const int_t LARGE = 100;
    int_t mat[LARGE + 1][LARGE + 1];
    int_t n, m;
    std::vector<int_t> rows, cols;
    
    int main() {
        cin >> n >> m;
        for (int_t i = 1; i <= n; i++) {
            for (int_t j = 1; j <= m; j++) cin >> mat[i][j];
        }
        while (true) {
            bool flag = false;
            for (int_t i = 1; i <= n; i++) {
                int_t sum = 0;
                for (int_t j = 1; j <= m; j++) sum += mat[i][j];
                if (sum < 0) {
                    rows.push_back(i);
                    flag = true;
                    for (int_t j = 1; j <= m; j++) mat[i][j] *= -1;
                }
            }
            for (int_t j = 1; j <= m; j++) {
                int_t sum = 0;
                for (int_t i = 1; i <= n; i++) sum += mat[i][j];
                if (sum < 0) {
                    cols.push_back(j);
                    flag = true;
                    for (int_t i = 1; i <= n; i++) mat[i][j] *= -1;
                }
            }
            if (!flag) break;
        }
        {
            static int_t count[LARGE + 1];
            for (int_t x : rows) count[x] ^= 1;
            int_t sum = std::count(count + 1, count + 1 + n, 1);
            cout << sum << " ";
            for (int_t i = 1; i <= n; i++)
                if (count[i]) cout << i << " ";
            cout << endl;
        }
        {
            static int_t count[LARGE + 1];
            for (int_t x : cols) count[x] ^= 1;
            int_t sum = std::count(count + 1, count + 1 + m, 1);
            cout << sum << " ";
            for (int_t i = 1; i <= m; i++)
                if (count[i]) cout << i << " ";
            cout << endl;
        }
        return 0;
    }