[LOJ#2331]「清华集训 2017」某位歌姬的故事

[LOJ#2331]「清华集训 2017」某位歌姬的故事

试题描述

IA是一名会唱歌的女孩子。

IOI2018就要来了,IA决定给参赛选手们写一首歌,以表达美好的祝愿。这首歌一共有 \(n\) 个音符,第iii个音符的音高为 \(h_i\)。IA的音域是 \(A\),她只能唱出 \(1\sim A\) 中的正整数音高。因此 \(1\le h_i\le A\)。

在写歌之前,IA需要确定下这首歌的结构,于是她写下了 \(Q\) 条限制,其中第 \(i\) 条为:编号在 \(l_i\) 到 \(r_i\) 之间的音符的最高音高为 \(m_i\)。在确定了结构之后,她就可以开始写歌了。不过她还是想知道,一共有多少种可能的歌曲满足她的所有限制?她听说你还有 \(9\) 个月就要去IOI了,于是希望你帮她计算一下这个值。

输入

从标准输入读入数据。

输入的第一行包含一个整数 \(T(T\le 20)\),代表测试数据的组数。

每组数据的第一行包含三个正整数 \(n,Q,A\)。接下来 \(Q\) 行,每行三个整数 \(l_i,r_i,m_i\),表示一条限制。保证 \(1\le l_i\le r_i\le n\),\(1\le m_i\le A\)。

输出

输出到标准输出。

输出文件只有一行,表示可能的歌曲数目。这个数可能很大,请将答案模 \(998244353\) 输出。

输入示例1

1
3 2 3
1 2 3
2 3 2

输出示例1

3

输入示例2

2
4 2 4
1 2 3
2 3 4
7 3 74
3 6 56
2 5 56
3 7 70

输出示例2

20
160326468

数据规模及约定

对于 \(100\%\) 的数据,\(n \le 9 \times 10^8\),\(Q \le 500\),\(A \le 9 \times 10^8\),\(m \le A\)。

题解

首先套路肯定要将所有区间按照音高限制从小打到排序,然后将每个位置的最高音高处理出来,然后对于每一个不同的音高限制拿出所有对应的位置和限制区间做一个 dp,不同音高之间的方案数用乘法连接。

至于那个 dp,方法有很多,我的方法是设 \(f(i, j)\) 表示满足了前 \(i\) 个区间,处理到了位置 \(j\),且 \(j\) 上放的是最大值,\(j\) 后不能再出现最大值。转移本来是 \(O(n)\) 的,可以用前缀和优化至 \(O(1)\)(确切地说是 \(O(\mathrm{log}n)\) 因为有个快速幂)。

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>
#include <algorithm>
using namespace std;
#define rep(i, s, t) for(int i = (s); i <= (t); i++)
#define dwn(i, s, t) for(int i = (s); i >= (t); i--)

int read() {
    int x = 0, f = 1; char c = getchar();
    while(!isdigit(c)){ if(c == ‘-‘) f = -1; c = getchar(); }
    while(isdigit(c)){ x = x * 10 + c - ‘0‘; c = getchar(); }
    return x * f;
}

#define maxm 510
#define maxn 2010
#define MOD 998244353
#define LL long long

struct Seg {
    int l, r, h;
    Seg() {}
    Seg(int _1, int _2, int _3): l(_1), r(_2), h(_3) {}
    bool operator < (const Seg& t) const { return h != t.h ? h < t.h : r < t.r; }
} qs[maxm];
int n, cntl, getl[maxn], cntn, num[maxn], len[maxn], lim[maxn], pos[maxn], val[maxn];

int Pow(int a, int b) {
    int ans = 1, t = a;
    while(b) {
        if(b & 1) ans = (LL)ans * t % MOD;
        t = (LL)t * t % MOD; b >>= 1;
    }
    return ans;
}
int Inv(int a) { return Pow(a, MOD - 2); }

int cq, cn, f[2][maxn], sf[2][maxn], le[maxn], sl[maxn], inv[maxn];
Seg s[maxm];
void dp(int& mul, int h) {
    if(h == 1) return ;

    memset(f, 0, sizeof(f));
    memset(sf, 0, sizeof(sf));
    int cur = 0;
    f[0][0] = sf[0][0] = 1; inv[0] = 1;
    rep(i, 1, cn) sf[cur][i] = sf[cur][i-1], sl[i] = sl[i-1] + le[i], inv[i] = Inv(Pow(h - 1, sl[i]));
    s[0].r = 0;
    rep(i, 1, cq) {
        cur ^= 1;
        rep(j, 0, s[i].l - 1) f[cur][j] = sf[cur][j] = 0;
        rep(j, s[i].l, s[i].r) {
            f[cur][j] = f[cur^1][j];
            if(j > s[i-1].r) {
                f[cur][j] += (LL)Pow(h - 1, sl[s[i-1].r]) * Pow(h, sl[j-1] - sl[s[i-1].r]) % MOD * sf[cur^1][s[i-1].r] % MOD * (Pow(h, le[j]) - Pow(h - 1, le[j]) + MOD) % MOD;
                if(f[cur][j] >= MOD) f[cur][j] -= MOD;
            }
            sf[cur][j] = (sf[cur][j-1] + (LL)f[cur][j] * inv[j] % MOD) % MOD;
        }
        rep(j, s[i].r + 1, cn) sf[cur][j] = sf[cur][j-1], f[cur][j] = 0;
    }

    mul = (LL)mul * sf[cur][cn] % MOD * Pow(h - 1, sl[cn]) % MOD;
    return ;
}

void work() {
    n = read(); int q = read(), m = read();
    cntl = cntn = 0;
    rep(i, 1, q) {
        int l = read(), r = read(), h = read();
        qs[i] = Seg(l, r, h);
        getl[++cntl] = l; getl[++cntl] = r; val[i] = h;
    }
    sort(qs + 1, qs + q + 1);
    getl[++cntl] = 1; getl[++cntl] = n;
    sort(getl + 1, getl + cntl + 1);
    cntl = unique(getl + 1, getl + cntl + 1) - getl - 1;
    rep(i, 1, cntl) {
        num[++cntn] = getl[i]; len[cntn] = 1;
        if(i < cntl && getl[i] + 1 < getl[i+1]) num[++cntn] = getl[i] + 1, len[cntn] = getl[i+1] - getl[i] - 1;
    }
    memset(lim, 0, sizeof(lim));
    rep(i, 1, q) {
        int l = qs[i].l = lower_bound(num + 1, num + cntn + 1, qs[i].l) - num;
        int r = qs[i].r = lower_bound(num + 1, num + cntn + 1, qs[i].r) - num;
        int mx = 0; bool has = 0;
        rep(j, l, r)
            if(!lim[j]) lim[j] = qs[i].h, has = 1;
            else mx = max(mx, lim[j]);
        if(!has && mx < qs[i].h) return (void)puts("0");
    }

    sort(val + 1, val + q + 1);
    int ans = 1, i = 1, tq = unique(val + 1, val + q + 1) - val - 1;
    rep(v, 1, tq) {
        cq = 0;
        while(i <= q && qs[i].h == val[v]) s[++cq] = qs[i++];
        cn = 0;
        rep(j, 1, cntn) if(lim[j] == val[v]) le[pos[j] = ++cn] = len[j];
        rep(j, 1, cq) {
            while(lim[s[j].l] != val[v]) s[j].l++;
            while(lim[s[j].r] != val[v]) s[j].r--;
            s[j].l = pos[s[j].l], s[j].r = pos[s[j].r];
        }
        dp(ans, val[v]);
    }
    rep(i, 1, cntn) if(!lim[i]) ans = (LL)ans * Pow(m, len[i]) % MOD;
    printf("%d\n", ans);
    return ;
}

int main() {
    int T = read();
    while(T--) work();

    return 0;
}
时间: 2024-11-11 11:46:40

[LOJ#2331]「清华集训 2017」某位歌姬的故事的相关文章

「清华集训 2017」某位歌姬的故事

题目链接 问题分析 吐槽一下这个预处理比DP还长的题-- 首先对限制从小到大排序,然后不难发现对于每一种大小限制都是独立的.离散后考虑\(F[i][j]\)表示以\(i\)结尾,上一个音高为限制大小的位置\(j\)的方案种数.不难发现对于一些右端点相同的限制,左端点最右的限制才有效.这样就可以\(n^2\)动规了. 由于要离散化,所以细节很多. 参考程序 程序没有显式的离散化,并且大量使用结构体,所以又慢又长. #include <bits/stdc++.h> #define LL long

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主

[LOJ#2325]「清华集训 2017」小Y和恐怖的奴隶主 试题描述 "A fight? Count me in!" 要打架了,算我一个. "Everyone, get in here!" 所有人,都过来! 小Y是一个喜欢玩游戏的OIer.一天,她正在玩一款游戏,要打一个Boss. 虽然这个Boss有 \(10^{100}\) 点生命值,但它只带了一个随从--一个只有 \(m\) 点生命值的"恐怖的奴隶主". 这个"恐怖的奴隶主&qu

[LOJ#2328]「清华集训 2017」避难所

[LOJ#2328]「清华集训 2017」避难所 试题描述 "B君啊,你当年的伙伴都不在北京了,为什么你还在北京呢?" "大概是因为出了一些事故吧,否则这道题就不叫避难所了." "唔,那你之后会去哪呢?" "去一个没有冬天的地方." 对于一个正整数 \(n\),我们定义他在 \(b\) 进制下,各个位上的数的乘积为 \(p = F(n, b)\). 比如 \(F(3338, 10) = 216\). 考虑这样一个问题,已知 \

[LOJ#2327]「清华集训 2017」福若格斯

[LOJ#2327]「清华集训 2017」福若格斯 试题描述 小d是4xx9小游戏高手. 有一天,小d发现了一个很经典的小游戏:跳青蛙. 游戏在一个 \(5\) 个格子的棋盘上进行.在游戏的一开始,最左边的两个格子上各有一个向右的青蛙,最右边的两个格子上各有一个向左的青蛙. 每次移动可以选取一个青蛙,向这只青蛙的前方移动一格到空格子中或跳过前方的一个不同朝向的青蛙并移动到空格子中. 为了使你更好地理解这个游戏,我们下发了一个游戏demo作为参考(注意:这个demo中的棋盘大小和题目中并不相同).

Loj #6703 -「清华集训 2017」生成树计数

\[ \sum _{\sum v_i = n-2} \prod (a_i ^{v_i+1} * (v_i+1) ^ m /v_i!) *(\sum (v_i+1)^m) \] 将 \(\sum (v_i+1)^m\) 中的贡献分开算. 我们有两个生成函数. 第一个: \[ \sum _i a^{i+1} * (v_i+1)^m x^i / i! \] 第二个: \[ \sum _i a^{i+1} * (i+1)^{2m} x^i / i! \] 先将 \(\prod a\) 算到前面. 令:

UOJ#346. 【清华集训2017】某位歌姬的故事 动态规划

原文链接www.cnblogs.com/zhouzhendong/p/UOJ346.html 题解 首先按照 $m_i$ 的大小排个序. 如果某一个区间和一个 m 值比他小的区间有交,那么显然可以将这个区间控制的区域删除掉重合的那一段. 如果一个区间被删没了,那么显然答案为 0 . 在这个处理之后,一个区间可能会变得不连续.那么我们就将它前后相连,变成连续的. 接下来问题变成了对每一种权值的区间算答案. 这个东西离散化之后大力DP即可. 注意特判权值为 1 的区间. 写起来好像有点麻烦. 时间复

@loj - [email&#160;protected] 「清华集训 2017」生成树计数

目录 @[email protected] @[email protected] @正文@ @补充@ @accepted [email protected] @[email protected] @[email protected] 在一个 s 个点的图中,存在 s - n 条边,使图中形成了 n 个连通块,第 i 个连通块中有 \(a_i\) 个点. 现在我们需要再连接 n - 1 条边,使该图变成一棵树.对一种连边方案,设原图中第 i 个连通块连出了 \(d_i\) 条边,那么这棵树 T 的

loj2322 「清华集训 2017」Hello world!

https://loj.ac/problem/2322 先吐槽一下,sb数据毁我青春败我前程. 首先,一个数开根开不了多少次. 当我们把它开到1的时候,我们以后就不需要开他了,我们可以利用并查集跳过他,这是套路. 但是这个每次走$k$步,让人很头痛. 于是乎……分块 首先,对于$k$比较大的情况,我们可以暴力跳.否则, 我们对于每个$k$建一棵树,第$i$棵树上$x$的父亲是原树上$x$的第$i$个祖先 然后树剖,用线段树或者树状数组维护一下就好了. 然后!!打开榜,rk1,啥子,并查集+暴力跳

LibreOJ #2325. 「清华集训 2017」小Y和恐怖的奴隶主(矩阵快速幂优化DP)

哇这题剧毒,卡了好久常数才过T_T 设$f(i,s)$为到第$i$轮攻击,怪物状态为$s$时对boss的期望伤害,$sum$为状态$s$所表示的怪物个数,得到朴素的DP方程$f(i,s)=\sum \frac{1}{sum+1}*(f(i+1,s')+[s==s'])$ 状态数只有$C_{8+3}^3=165$个,所以就可以矩乘优化啦.再加上一个用于转移的$1$,矩阵大小是$166*166$的,因为多组询问,所以可以先把$2$的所有次幂的矩阵都预处理出来. 然后会发现复杂度是$O(T*166^3