模拟测试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} 间}$
原文地址:https://www.cnblogs.com/mikufun-hzoi-cpp/p/11158750.html