zoukankan      html  css  js  c++  java
  • 模拟测试20190707 [排序//划艇//放棋子]

    苟蒻没有什么学问,但还是能爆个零的

    注:灰色背景为本人自说自话,可跳过

    T1:「SDOI2015」排序(链接

    [一道考场上连暴力都没打出来的题,再次提醒了我看题的重要性(看成了每种操作可以进行无数次真是对不起了啊)]

    我们手模几个点就会发现如果一种序列成立,那么他的全排列一定成立

    则我们每种序列只需要搜一次,然后就可以把它的阶乘累加进答案

    则不需管顺序,可以直接从小操作到大操作搜索

    我们观察发现小的操作进行的区间一定被大的包含,则小的操作进行之后这个小区间的顺序一定不变

    那我们在搜索的时候,判定当前序列在下一个操作下是否连续,以及有几个不连续

    若没有,则不需进行操作,直接向下搜索

    若有一个,对这个区间进行交换并向下搜索

    若有两个,则一共有4种交换方法,枚举判断是否可行并向下搜索

    复杂度O(4n)期望得分100分

    #include <iostream>
    #include <cstdio>
    const int mod = 1e9 + 9;
    #define ll long long
    using namespace std;
    int a[5000], n, jie[15] = { 1 }, ans, t[15] = { 1 }, is[15];
    inline bool judge(int x, int y) {
        for (int i = 1; i < t[x]; i++)
            if (a[y + i] != a[y + i - 1] + 1)
                return 1;
        return 0;
    }
    inline void swaps(int x, int y, int z) {
        for (int i = 0; i < t[z]; i++) swap(a[x + i], a[y + i]);
    }
    void dfs(int x, int num) {
        if (x == n) {
            ans += jie[num];
            return;
        }
        int tt = 0, ttt = 0;
        for (int i = 1; i <= t[n]; i += t[x + 1])
            if (judge(x + 1, i)) {
                if (!tt)
                    tt = i;
                else if (!ttt)
                    ttt = i;
                else
                    return;
            }
        if (!tt)
            dfs(x + 1, num);
        else if (!ttt) {
            swaps(tt, tt + t[x], x);
            dfs(x + 1, num + 1);
            swaps(tt, tt + t[x], x);
        } else {
            for (int i = 0; i <= 1; i++)
                for (int j = 0; j <= 1; j++) {
                    swaps(tt + i * t[x], ttt + j * t[x], x);
                    if (!judge(x + 1, tt) && !judge(x + 1, ttt))
                        dfs(x + 1, num + 1);
                    swaps(tt + i * t[x], ttt + j * t[x], x);
                }
        }
    }
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= (1 << n); i++) scanf("%d", &a[i]);
        for (int i = 1; i <= n; i++) jie[i] = jie[i - 1] * i, t[i] = t[i - 1] << 1;
        dfs(0, 0);
        printf("%d", ans);
    }
    View Code

     T2:「APIO2016」划艇(链接

    [考试的时候想出来一个暴力,用树状数组优化了一下,但并没有注意到子任务这个玩意,而数组又开不了那么大,直接爆零

    旁边cbx非常机智地使用了动态开点线段树(下面会讲),但是因为线段树没有开4倍只得了9分(鼓掌)]

    9分算法: 

    第一个子任务保证a[i]==b[i],则可以用一个类似最长上升子序列的dp

    dp[i]=∑dp[j]  (a[j]<a[i]),

    复杂度O(n2)期望得分9分

    31分算法: 

    设dp[i][j],表示在第i所学校派出了j艘划艇,则dp[i][j]=1+∑ktdp[k][t](1<=k<=i-1,1<=t<=j-1)

    我们发现这个式子后面很明显是一个前缀和,则式子可以变成dp[i][j]=sum[j-1]

    j很大,为了不开数组,前缀和我们考虑用数据结构维护,但是不能用树状数组,因为数组开不了

    权值线段树是个好东西,而且我们发现连dp数组都不用开,直接add(get(j-1),j)就好了

    最开始给0节点+1,最后输出get(max)-1

    复杂度O(nlogmax)期望得分31分

    #include <iostream>
    #include <cstdio>
    const int mod = 1e9 + 7;
    #define ll long long
    using namespace std;
    int a[510], b[510], n, ma;
    int da[20000000], ls[20000000], rs[20000000], cnt;
    inline int q(int x) {
        if (x >= mod)
            x -= mod;
        if (x < 0)
            x += mod;
        return x;
    }
    void insert(int &x, int y, int l, int r, int is) {
        if (!x)
            x = ++cnt;
        if (l == r) {
            da[x] = q(da[x] + y);
            return;
        }
        int mid = l + r >> 1;
        if (is <= mid)
            insert(ls[x], y, l, mid, is);
        else
            insert(rs[x], y, mid + 1, r, is);
        da[x] = q(da[ls[x]] + da[rs[x]]);
    }
    int get(int x, int y, int L, int R) {
        if (!x)
            return 0;
        int mid = L + R >> 1, ans = 0;
        if (R <= y)
            return da[x];
        ans = (ans + get(ls[x], y, L, mid)) % mod;
        if (y > mid)
            ans = (ans + get(rs[x], y, mid + 1, R)) % mod;
        return ans;
    }
    inline int Max(int x, int y) { return x < y ? y : x; }
    inline int Min(int x, int y) { return x < y ? x : y; }
    int main() {
        scanf("%d", &n);
        for (int i = 1; i <= n; i++) scanf("%d%d", &a[i], &b[i]), ma = Max(ma, b[i]);
        int ans, root = 0;
        insert(root, 1, 0, ma, 0);
        for (int i = 1; i <= n; i++) {
            for (int j = b[i]; j >= a[i]; j--) insert(root, get(root, j - 1, 0, ma), 0, ma, j);
        }
        printf("%d", q(get(root, ma, 0, ma) - 1));
    }
    View Code

    100分算法:

    首先是一个我们以前没有接触过的东西——区间离散化

    把区间的左右端点读入,sort并去重,剩下了cnt个数,就得到了cnt-1个区间

    记录每个区间的长度,把原来的a[i],b[i],换成在这个数组中的位置+1

    好了,我们成功把1e9的区间变成了至多1000个小区间,那么这有什么用呢

    设dp[i][j]表示第i所学校选择派出第j个区间里某个数的划艇

    对于比j小的区间,显然dp[i][j]+=∑∑dp[k][t](1<=k<=i-1,1<=t<=j-1)

    那对于j这个区间怎么办呢

    我们来解决这样一个问题:给你x个相同的区间,每个区间你可以选择选或不选,在你选出的所有区间中,每个取出一个数,并且取出的数严格递增

    考虑一个小数据,在3个区间里取数,发现总数为C(3,0)*C(L,0)+C(3,1)*C(L,1)+C(3,2)*C(L,2)+C(3,3)*C(L,3)=C(L+3,3)

    扩展一下就得到了C(L+x,x)

    回到我们的问题,给你x个相同的区间,每个区间你可以选择选或不选,至少选两个,在你选出的所有区间中,每个取出一个数,并且取出的数严格递增

    还是考虑3个区间,总数为C(3,2)*C(L,2)+C(3,3)*C(L,3)=C(L+1,3)=C(L+3-2,3)

    扩展一下得到C(L+x-2,x)

    则对于有交集的区间,dp[i][j]+=∑m(num+L-2,num)×∑ktdp[k][t](1<=k<=m-1,1<=t<=j-1,num表示m到i有几个包含j的数)

    这个dp看起来复杂度O(n5)不可做,但我们发现和31分算法一样,这里出现了两个从1开始的∑,其实就是矩阵的一个角

    而且我们发现单点状态对以后没有什么用,则dp数组可以直接维护前缀和

    那么dp就变成了dp[i][j]+=/dp[i-1][j-1]*L[j]

                       ∑mC(num+L-2,num)×dp[m-1][j-1](num表示m到i有几个包含j的数)

    每个学校处理完了之后再维护成前缀和就好了

    复杂度O(n3)期望得分100

     

    #define ll long long
    #include <iostream>
    #include <cstdio>
    #include <set>
    #include <algorithm>
    const int mod = 1e9 + 7;
    using namespace std;
    int a[510], b[510], f[1010], cnt = 0, L[1010], dp[510][1010], ni[1010];
    set<int> has;
    int main() {
        int n;
        scanf("%d", &n);
        ni[1] = 1;
        for (int i = 2; i <= 500; i++) ni[i] = 1ll * (mod - mod / i) * ni[mod % i] % mod;
        for (int i = 1; i <= n; i++) {
            scanf("%d%d", &a[i], &b[i]);
            has.insert(a[i]);
            has.insert(b[i] + 1);
        }
        for (set<int>::iterator it = has.begin(); it != has.end(); it++) f[++cnt] = *it;
        for (int i = 2; i <= cnt; i++) L[i] = f[i] - f[i - 1];
    
        for (int i = 1; i <= n; i++) {
            a[i] = upper_bound(f+1, f + cnt + 1, a[i]) - f;
            b[i] = upper_bound(f+1, f + cnt + 1, b[i]) - f;
            // cout<<a[i]<<" "<<b[i]<<endl;
        }
        for (int i = 1; i <= cnt; i++) dp[0][i] = 1;
        for (int i = 1; i <= n; i++) {
            dp[i][1] = 1;
            for (int j = a[i]; j <= b[i]; j++) {
                dp[i][j] = (ll)dp[i - 1][j - 1] * L[j] % mod;
                int can = 1;
                ll c = L[j] - 1;
                for (int k = i - 1; k; k--)
                    if (b[k] >= j && a[k] <= j) {
                        can++;
                        c = c * (L[j] + can - 2) % mod * ni[can] % mod;
                        if (!c)
                            break;
                        dp[i][j] = ((ll)dp[i][j] + (ll)c * dp[k - 1][j - 1] % mod) % mod;
                    }
            }
            for (int j = 2; j <= cnt; j++)
                dp[i][j] = ((ll)dp[i][j] + dp[i][j - 1] + dp[i - 1][j] - dp[i - 1][j - 1] + mod) % mod;
        }
        printf("%lld
    ", ((ll)dp[n][cnt] - 1 + mod) % mod);
        return 0;
    }
    View Code

     

    T3:「CQOI2011」放棋子(链接

    [一道在考场上想出正解的题,但是因为一部分不会处理只拿到30分]

     dp很好想,我们发现当前几种棋子放完之后,下一种棋子能放当且仅当剩下了完全空的几行几列,而且行列的位置没有影响

    则我们设dp[i][j][k]表示放第i种棋子,剩下了j行k列,g[i][j][k]表示用第i种棋子放满i×j的矩阵(每行每列都有棋子)的方案数

    dp[i][j][k]=∑pqdp[i-1][p][q]*g[p-j][q-k][i]×C(p,j)*C(q,k)(j<=p<=n,k<=q<=m)

    然后我们考虑g怎么处理,发现直接求出很麻烦,考虑容斥

    g[i][j][k]=C(i*j,num[k])-∑pqg[p][q][k](0<=p<=i,0<=q<=j)

    预处理或者一边dp一边处理都行

    复杂度O(cn4)期望得分100分

    #include <iostream>
    #include <cstdio>
    const int mod = 1e9 + 9;
    #define ll long long
    using namespace std;
    int C[1000][1000], num[12], f[12][50][50], g[50][50][15];
    inline int md(int x) {
        if (x >= mod)
            x -= mod;
        return x;
    }
    int main() {
        int n, m, c, sum = 0;
        scanf("%d%d%d", &n, &m, &c);
        for (int i = 1; i <= c; i++) scanf("%d", &num[i]), sum += num[i];
        C[0][0] = 1;
        for (int i = 1; i <= n * m; i++) {
            C[i][0] = 1;
            for (int j = 1; j <= i; j++) C[i][j] = md(C[i - 1][j] + C[i - 1][j - 1]);
        }
        if (c == 1) {
            printf("%d", C[n * m][num[1]]);
            return 0;
        }
        f[0][n][m] = 1;
        for (int i = 1; i <= c; i++)
            for (int j = 0; j <= n; j++)
                for (int k = 0; k <= m; k++)
                    if (j * k >= num[i]) {
                        g[j][k][i] = C[k * j][num[i]];
                        for (int p = 0; p <= j; p++)
                            for (int q = 0; q <= k; q++)
                                if (p < j || q < k)
                                    g[j][k][i] = md(1ll * g[j][k][i] -
                                                    1ll * C[j][p] * C[k][q] % mod * g[p][q][i] % mod + mod);
                    }
        for (int i = 1; i <= c; i++)
            for (int j = 0; j <= n; j++)
                for (int k = 0; k <= m; k++)
                    for (int p = j; p <= n; p++)
                        for (int q = k; q <= m; q++)
                            f[i][j][k] = md(1ll * f[i][j][k] + 1ll * f[i - 1][p][q] * g[(p - j)][(q - k)][i] %
                                                                   mod * C[p][j] % mod * C[q][k] % mod);
        ll ans = 0;
        for (int i = 0; i <= n; i++)
            for (int j = 0; j <= m; j++) ans = md(ans + f[c][i][j]);
        printf("%lld", ans);
    }
    View Code

    emmm......聊聊这次考试的经历,刚开始用了将近1h打第一题的暴力,没看题所以没打出来

    然后专攻第二题,打了个树状数组,没注意子任务,Wa 0

    最后看的第三题,发现dp非常水,然后非常开心得码了出来,但是两个变量名写错了,再加g数组没处理出来Wa 10

    简直苟蒻标配经历&分数

    以后加油吧

    ${color{Teal} 只}$${color{Teal} 是}$${color{Teal} 拼}$${color{Teal} 凑}$${color{Teal} 出}$${color{Teal} 与}$${color{Teal} 你}$${color{Teal} 在}$${color{Teal} 一}$${color{Teal} 起}$${color{Teal} 的}$${color{Teal} 时}$${color{Teal} 间}$
  • 相关阅读:
    安装gmsll
    常用LInux命令和操作
    yum 一键安装 jdk
    Linux目录详解,软件应该安装到哪个目录
    安装npm
    linux安装mysql (rpm + yum)
    springboot 打包jar 运行找资源文件
    rpm包安装java jar开机自启
    centos7设置服务开机自启
    linux(centos7) nginx 配置
  • 原文地址:https://www.cnblogs.com/mikufun-hzoi-cpp/p/11158750.html
Copyright © 2011-2022 走看看