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

题目链接

问题分析

吐槽一下这个预处理比DP还长的题……

首先对限制从小到大排序,然后不难发现对于每一种大小限制都是独立的。离散后考虑\(F[i][j]\)表示以\(i\)结尾,上一个音高为限制大小的位置\(j\)的方案种数。不难发现对于一些右端点相同的限制,左端点最右的限制才有效。这样就可以\(n^2\)动规了。

由于要离散化,所以细节很多。

参考程序

程序没有显式的离散化,并且大量使用结构体,所以又慢又长

#include <bits/stdc++.h>
#define LL long long
using namespace std;

void Work();
int main() { int TestCases; scanf( "%d", &TestCases ); for( ; TestCases--; ) Work(); return 0; }

const int Mod = 998244353;
const int MaxQ = 510;
struct seg {
    int Left, Right;
    seg() {}
    seg( int _Left, int _Right ) : Left( _Left ), Right( _Right ) {}
};
inline bool Cmp1( const seg X, const seg Y ) { return X.Left < Y.Left || X.Left == Y.Left && X.Right < Y.Right; }
struct query {
    seg Seg; int High;
    inline void Read() { scanf( "%d%d%d", &Seg.Left, &Seg.Right, &High ); return; }
    inline bool operator < ( const query Other ) const { return High < Other.High || High == Other.High && Cmp1( Seg, Other.Seg ); }
};
int Temp1[ MaxQ << 1 ], Temp2[ MaxQ << 1 ];
struct member {
    int Size, High; seg Seg[ MaxQ << 1 ];
    inline void Clear() { memset( Seg, 0, sizeof( Seg ) ); Size = High = 0; return; }
    inline void Import( const query Other ) { Clear(); Size = 1; High = Other.High; Seg[ Size ] = Other.Seg; return; }
    inline void Add( const query Other ) { Seg[ ++Size ] = Other.Seg; return; }
    inline void Copy( int Left, int Right ) { if( Left > Right ) return; ++Size; Seg[ Size ].Left = Left; Seg[ Size ].Right = Right; return; }
    void Unique( member &Goal ) {
        Goal.Clear(); Goal.High = High; int CoverNum = 0;
        for( int i = 1; i <= Size; ++i ) Temp1[ i ] = Seg[ i ].Left, Temp2[ i ] = Seg[ i ].Right;
        sort( Temp1 + 1, Temp1 + Size + 1 ); sort( Temp2 + 1, Temp2 + Size + 1 );
        int Link1 = 1, Link2 = 1, Last = 0;
        for( ; Link1 <= Size && Link2 <= Size; ) {
            if( Temp1[ Link1 ] <= Temp2[ Link2 ] ) {
                if( CoverNum ) Goal.Copy( Last, Temp1[ Link1 ] - 1 );
                Last = Temp1[ Link1++ ]; ++CoverNum;
            } else {
                if( CoverNum ) Goal.Copy( Last, Temp2[ Link2 ] );
                Last = Temp2[ Link2++ ] + 1; --CoverNum;
            }
        }
        for( ; Link1 <= Size; ) { if( CoverNum ) Goal.Copy( Last, Temp1[ Link1 ] - 1 ); Last = Temp1[ Link1++ ]; ++CoverNum; }
        for( ; Link2 <= Size; ) { if( CoverNum ) Goal.Copy( Last, Temp2[ Link2 ] ); Last = Temp2[ Link2++ ] + 1; ++CoverNum; }
        return;
    }
    void Delete( const member Done, member &Collect ) {
        Collect.Clear(); Collect.High = High;
        int Link1 = 1, Link2 = 1;
        for( ; Link1 <= Size && Link2 <= Done.Size; ) {
            if( Done.Seg[ Link2 ].Left > Seg[ Link1 ].Right ) {
                Collect.Copy( Seg[ Link1 ].Left, Seg[ Link1 ].Right );
                ++Link1; continue;
            }
            if( Done.Seg[ Link2 ].Right < Seg[ Link1 ].Left ) { ++Link2; continue; }
            if( Done.Seg[ Link2 ].Left <= Seg[ Link1 ].Left && Seg[ Link1 ].Right <= Done.Seg[ Link2 ].Right ) { ++Link1; continue; }
            if( Seg[ Link1 ].Left <= Done.Seg[ Link2 ].Left && Done.Seg[ Link2 ].Right <= Seg[ Link1 ].Right ) {
                Collect.Copy( Seg[ Link1 ].Left, Done.Seg[ Link2 ].Left - 1 ); Seg[ Link1 ].Left = Done.Seg[ Link2 ].Right + 1;
                ++Link2; continue;
            }
            if( Seg[ Link1 ].Left < Done.Seg[ Link2 ].Left ) { Collect.Copy( Seg[ Link1 ].Left, Done.Seg[ Link2 ].Left - 1 ); ++Link1; continue; }
            if( Seg[ Link1 ].Left > Done.Seg[ Link2 ].Left ) { Seg[ Link1 ].Left = Done.Seg[ Link2 ].Right + 1; ++Link2; continue; }
        }
        for( ; Link1 <= Size; ++Link1 ) Collect.Copy( Seg[ Link1 ].Left, Seg[ Link1 ].Right );
        return;
    }
    void Union( member Other ) {
        member Ans; Ans.Clear();
        int Link1 = 1, Link2 = 1;
        for( ; Link1 <= Size && Link2 <= Other.Size; ) {
            if( Seg[ Link1 ].Left < Other.Seg[ Link2 ].Left ) Ans.Seg[ ++Ans.Size ] = Seg[ Link1++ ];
            else Ans.Seg[ ++Ans.Size ] = Other.Seg[ Link2++ ];
        }
        for( ; Link1 <= Size; ) Ans.Seg[ ++Ans.Size ] = Seg[ Link1++ ];
        for( ; Link2 <= Other.Size; ) Ans.Seg[ ++Ans.Size ] = Other.Seg[ Link2++ ];
        Size = 1; Seg[ 1 ] = Ans.Seg[ 1 ];
        for( int i = 2; i <= Ans.Size; ++i )
            if( Seg[ Size ].Right + 1 == Ans.Seg[ i ].Left ) Seg[ Size ].Right = Ans.Seg[ i ].Right;
            else Seg[ ++Size ] = Ans.Seg[ i ];
        return;
    }
};
int n, Q, A, Ans;
query Query[ MaxQ ];
member Done, Now, Seperate, Collect;
int F[ MaxQ << 1 ][ MaxQ << 1 ], Before[ MaxQ << 1 ];
inline int FastPow( int a, int x ) { if( x <= 0 ) return 1; int Ans = 1; for( ; x; x >>= 1, a = 1ll * a * a % Mod ) if( x & 1 ) Ans = 1ll * Ans * a % Mod; return Ans; }
inline bool Cmp2( query X, query Y ) { return X.Seg.Right < Y.Seg.Right || X.Seg.Right == Y.Seg.Right && X.Seg.Left < Y.Seg.Left; }
inline int FindGreater( int x ) { for( int i = 1; i <= Collect.Size; ++i ) if( Collect.Seg[ i ].Left >= x ) return i; return Collect.Size + 1; }
inline int FindFewer( int x ) { for( int i = Collect.Size; i >= 1; --i ) if( Collect.Seg[ i ].Right <= x ) return i; return 0LL; }

int Dp( int Left, int Right ) {
    if( Collect.Size == 0 ) return 0LL;
    sort( Query + Left, Query + Right + 1, Cmp2 );
    for( int i = Left; i <= Right; ++i ) {
        Query[ i ].Seg.Left = FindGreater( Query[ i ].Seg.Left );
        Query[ i ].Seg.Right = FindFewer( Query[ i ].Seg.Right );
        if( Query[ i ].Seg.Left > Query[ i ].Seg.Right ) return 0LL;
    }
    memset( Before, 0, sizeof( Before ) );
    for( int i = Left; i <= Right; ++i ) Before[ Query[ i ].Seg.Right ] = max( Before[ Query[ i ].Seg.Right ], Query[ i ].Seg.Left );
    for( int i = 1; i <= Collect.Size; ++i ) Before[ i ] = max( Before[ i ], Before[ i - 1 ] );
    memset( F, 0, sizeof( F ) );
    F[ 0 ][ 0 ] = 1;
    for( int i = 1; i <= Collect.Size; ++i ) {
        int Fuint = FastPow( Collect.High, Collect.Seg[ i ].Right - Collect.Seg[ i ].Left + 1 );
        int None = FastPow( Collect.High - 1, Collect.Seg[ i ].Right - Collect.Seg[ i ].Left + 1 );
        Fuint = ( Fuint - None + Mod ) % Mod;
        for( int j = 0; j < i; ++j ) {
            if( j >= Before[ i ] ) F[ i ][ j ] = ( F[ i ][ j ] + 1ll * F[ i - 1 ][ j ] * None % Mod ) % Mod;
            F[ i ][ i ] = ( F[ i ][ i ] + 1ll * F[ i - 1 ][ j ] * Fuint % Mod ) % Mod;
        }
    }
    int Ans = 0;
    for( int i = 0; i <= Collect.Size; ++i ) Ans = ( Ans + F[ Collect.Size ][ i ] ) % Mod;
    return Ans;
}

void Work() {
    scanf( "%d%d%d", &n, &Q, &A );
    for( int i = 1; i <= Q; ++i ) Query[ i ].Read();
    sort( Query + 1, Query + Q + 1 );
    Done.Clear();
    Ans = 1;
    for( int i = 1, j; i <= Q; i = j + 1 ) {
        j = i; Now.Import( Query[ j ] );
        for( ; j < Q && Query[ j + 1 ].High == Now.High; Now.Add( Query[ ++j ] ) );
        Now.Unique( Seperate );
        Seperate.Delete( Done, Collect );
        Done.Union( Collect );
        Ans = 1ll * Ans * Dp( i, j ) % Mod;
    }
    if( !Done.Size ) Ans = 1ll * Ans * FastPow( A, n ) % Mod; else {
        Ans = 1ll * Ans * FastPow( A, Done.Seg[ 1 ].Left - 1 ) % Mod;
        Ans = 1ll * Ans * FastPow( A, n - Done.Seg[ Done.Size ].Right ) % Mod;
        for( int i = 1; i < Done.Size; ++i ) Ans = 1ll * Ans * FastPow( A, Done.Seg[ i + 1 ].Left - Done.Seg[ i ].Right - 1 ) % Mod;
    }
    printf( "%d\n", Ans );
    return;
}

原文地址:https://www.cnblogs.com/chy-2003/p/11290617.html

时间: 2024-10-10 03:59:29

「清华集训 2017」某位歌姬的故事的相关文章

[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_

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

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

[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中的棋盘大小和题目中并不相同).

「清华集训 2017」无限之环

无限之WA https://www.luogu.org/problemnew/show/P4003 本题如果知道是网络流的话,其实建图不算特别神奇,但是比较麻烦. 数据范围过大,插头dp不能处理,而且是一个网格图,考虑网络流. 先看是不是二分图? 每个格子只会和相邻四个格子发生关系 所以,黑白染色正好. i+j为偶数左部点,i+j为奇数右部点 不漏水是什么? 每个管道的四个口都能和别的接好. S向左,右向T连口数的容量,费用为0的边 考虑怎么能把左右连在一起. 考虑要上下左右四个方向匹配,那么必

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

loj2322 「清华集训 2017」Hello world!

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

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\) 算到前面. 令: