【NOI2018模拟3.10】子序列

17 篇文章 0 订阅
4 篇文章 0 订阅

Description:

这里写图片描述

题解:

好把我竟然打了lj的题解方法。

代码长常数大。

大概就是用个优先队列来维护。

每次提出最小的。

考虑两种转移:
1.在它后面加个最小的。

2.把它的最后一个删掉,找个次小的。

第一种转移预处理。

可以用主席树来维护第二种转移。

比较大小的话就用trie上倍增。

Code:

#pragma G++ optimize (2)
#include<map>
#include<queue>
#include<cstdio>
#include<cstring>
#define ll long long
#define fo(i, x, y) for(int i = x; i <= y; i ++)
#define fd(i, x, y) for(int i = x; i >= y; i --)
#define low(a) ((a) & -(a))
using namespace std;

const int C = 1e5;
const int N = 1e5 + 5, M = 1e7 + 5;

int n, k, seed, mo, a[N];

int t[M][2], w[M], tt;

struct tree {
    int pl, pr, rt; 
    void dg(int &i, int x, int y) {
        if(!i) i = ++ tt;
        if(x == y) {if(!w[i]) w[i] = pr; return;}
        int m = x + y >> 1;
        if(pl <= m) dg(t[i][0], x, m); else dg(t[i][1], m + 1, y);
        w[i] = w[t[i][0]] | w[t[i][1]];
    }
    int dd(int &i, int x, int y) {
        if(!w[i] || y < pl || x > pr) return 0;
        if(x == y) return w[i];
        int m = x + y >> 1,  p = dd(t[i][0], x, m);
        return p ? p : dd(t[i][1], m + 1, y);
    }
    int fi(int l, int r) {pl = l; pr = r; return dd(rt, 1, C);}
} tr[N];

int find(int x, int l, int r) {
    int ans = n + 1; x = n - x + 1;
    while(x) {
        int p = tr[x].fi(l, r);
        if(p && (a[p] < a[ans] || a[p] == a[ans] && p < ans)) ans = p;
        x -= low(x);
    }
    return ans;
}

int ss[N * 2];
int fa[17][N * 2];
map<int, int> son[N * 2]; int td = 1, dep[N * 2], z[N * 2];
int add(int x, int c) {
    if(son[x][c]) return son[x][c];
    son[x][c] = ++ td;
    dep[td] = dep[x] + 1; z[td] = c;
    fa[0][td] = x; fo(i, 1, 16) fa[i][td] = fa[i - 1][fa[i - 1][td]];
    ss[td] = ((ll) ss[x] * seed + c) % mo;
    return td;
}

struct node {
    int x, f, t;
    node(int X, int F, int T) {x = X, f = F, t = T;}
    node() {}
};

int log2[N];

bool operator < (node a, node b) {
    int t1 = a.t, t2 = b.t, p = max(log2[dep[t1]], log2[dep[t2]]);
    if(dep[t1] > dep[t2]) {
        fd(i, p, 0) if(dep[fa[i][t1]] >= dep[t2]) t1 = fa[i][t1];
    }
    else
    fd(i, p, 0) if(dep[fa[i][t2]] >= dep[t1]) t2 = fa[i][t2];
    if(t1 == t2) return dep[a.t] > dep[b.t];
    fd(i, p, 0) if(fa[i][t1] != fa[i][t2]) t1 = fa[i][t1], t2 = fa[i][t2];
    return z[t1] > z[t2];
} //remember have rev

priority_queue<node> q;

int fi[N], nt[N], te[N];

void read(int &n)
{
    char ch=' ';
    for(; ch < '0' || ch > '9'; ch = getchar());
    for(;ch>='0' && ch<='9';ch=getchar())n=n*10+ch-48;
}

void write(int x) {
    if(x == 0) putchar('0');
    int d[10]; d[0] = 0;
    while(x) d[++ d[0]] = x % 10, x /= 10;
    for(; d[0]; d[0] --) putchar(d[d[0]] + '0');
    putchar('\n');
}

int main() {
    freopen("sequence.in", "r", stdin);
    freopen("sequence.out", "w", stdout);
    scanf("%d %d %d %d", &n, &k, &seed, &mo);
    fo(i, 1, n) read(a[i]);
    fo(i, 1, 16) log2[1 << i] = 1;
    fo(i, 1, n) log2[i] += log2[i - 1];
    a[n + 1] = 1e9;
    fd(i, n, 1) {
        nt[i] = fi[a[i]];
        fi[a[i]] = i;
    }
    {
        int xx = n + 1;
        fd(i, n, 0) {
            te[i] = xx;
            if(a[i] <= a[xx]) xx = i;
        }
    }
    fo(i, 1, n) {
        int x = n - i + 1;
        while(x <= n) {
            tr[x].pl = a[i]; tr[x].pr = i;
            tr[x].dg(tr[x].rt, 1, C);
            x += low(x);
        }
    }
//  return 0;
    q.push(node(0, 0, 1));
    fo(ii, 1, k + 1) {
        node c = q.top(); q.pop(); 
        if(c.x) write(ss[c.t]);
        int x = te[c.x];
        if(x <= n) q.push(node(x, c.x, add(c.t, a[x])));
        if(!c.x) continue;
        x = nt[c.x];
        if(!x) x = find(c.f + 1, a[c.x] + 1, C);
        if(x <= n) q.push(node(x, c.f, add(fa[0][c.t], a[x])));
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 子序列是指从一个序列中取出任意数量的元素,而不改变它们在原序列中的相对顺序所得到的新序列。换句话说,子序列是原序列的一个部分,可以是连续或非连续的。 对于给定的一个序列,请你求出它的子序列的个数。 解题思路: 我们可以使用动态规划的思想来解决这个问题。假设原序列的长度为n。首先,我们定义一个长度为n的数组dp,其中dp[i]表示以第i个元素结尾的子序列的个数。 初始化时,dp数组的所有元素都为1,因为每个元素本身也是一个子序列。 然后,我们从第二个元素开始遍历原序列。对于当前遍历到的元素,我们需要计算以它结尾的子序列的个数。遍历到第i个元素时,我们需要向前遍历前面的元素,若前面的某个元素小于第i个元素,则第i个元素可以接在这个元素的后面,形成一个新的子序列。此时,我们可以利用dp数组来直接求出以前面的这个元素结尾的子序列的个数,并将它们累加到dp[i]中。 最后,我们将dp数组中所有元素的值相加,即可得到原序列的子序列的个数。 例如,对于序列1 2 3 4,其子序列的个数为15,具体的子序列是(1)、(2)、(3)、(4)、(1 2)、(1 3)、(1 4)、(2 3)、(2 4)、(3 4)、(1 2 3)、(1 2 4)、(1 3 4)、(2 3 4)、(1 2 3 4)。 这就是使用动态规划求解子序列个数的方法。 希望对你有帮助! ### 回答2: 子序列是指从给定序列中删除若干个元素后所得到的序列,而被删除的元素的顺序保持不变。例如,对于序列[1, 2, 3, 4, 5],它的子序列可以为[1, 2, 3]、[2, 4, 5]等。那么现在我们来解答关于子序列的NOI教师培训试题。 试题:给定一个长度为n的正整数序列a,若存在一个长度为m的序列b(b中元素值可以不连续)是a的子序列,并且b满足b中各个元素之和可以整除k,输出序列b的最大长度m。 解答: 首先,我们可以使用动态规划的思想来解决这个问题。定义一个dp数组,dp[i]表示以第i个元素结尾的子序列的最大长度。初始化dp数组的所有元素为1。 然后,我们遍历序列a,对于每个元素a[i],再遍历它之前的元素a[j](0 <= j < i),如果a[i]可以整除k,说明可以将a[j]添加到以a[i]结尾的子序列中,此时更新dp[i] = max(dp[i], dp[j] + 1)。最后,找出dp数组中的最大值,即为题目所求的结果。 例如,对于序列a = [1, 2, 3, 4, 5],k = 3,执行上述算法得到dp数组为[1, 1, 1, 1, 2],最大值为2,因此输出结果为2。 该算法的时间复杂度为O(n^2),在n较小的情况下可以接受。如果希望进一步优化时间复杂度,可以考虑使用动态规划+哈希表的方法,将时间复杂度降低到O(n)。 以上就是关于NOI教师培训试题子序列的解答,希望能对您有所帮助! ### 回答3: 子序列是指从一个给定的序列中选择出若干个元素,这些元素在原序列中保持相对顺序不变,但不一定连续。例如,对于序列1 2 4 3,它的子序列可以是1 4、2 3、1 2 3、4等。 求一个序列的最长递增子序列是一个经典问题。给定一个整数序列,我们要找出一个最长的递增子序列,其中递增表示:对于任意的i和j,如果i < j,则ai < aj。例如,对于序列2 1 4 3,它的最长递增子序列是1 3,长度为2。 解决这个问题的动态规划算法可以描述如下: 1. 创建一个辅助数组dp,dp[i]表示以第i个元素结尾的最长递增子序列的长度。 2. 初始化dp数组,将dp的所有元素都初始化为1,表示每个元素本身就是一个递增子序列,长度为1。 3. 从第2个元素开始遍历原序列,依次计算每个元素结尾的最长递增子序列的长度。 4. 对于每个元素,从它之前的元素中找到比它小的元素,如果找到,就更新dp[i]为dp[j]+1,表示以当前元素结尾的最长递增子序列长度增加1。 5. 遍历完整个序列后,dp数组中的最大值即为原序列的最长递增子序列的长度。 上述算法的时间复杂度是O(n^2),其中n是序列的长度。还有其他更优化的算法,可以将时间复杂度降到O(nlogn),例如使用二分查找或贪心算法。 对于NOI教师培训试题,子序列问题是一个较为常见的题型,可以使用上述动态规划算法进行求解。在解题过程中,需要注意理解子序列的含义,以及动态规划算法的思想。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值