【清华集训】某位歌姬的故事

题目

题目描述
IA 是一名会唱歌的女孩子。

IOI2018 就要来了,IA 决定给参赛选手们写一首歌,以表达美好的祝愿。这首歌一共有 nn 个音符,第 ii 个音符的音高为 h_ih
i

​​。IA 的音域是 AA,她只能唱出 1\sim A1∼A 中的正整数音高。因此 1\le h_i\le A1≤h
i

≤A。

在写歌之前,IA 需要确定下这首歌的结构,于是她写下了 QQ 条限制,其中第 ii 条为:编号在 l_il
i

到 r_ir
i

之间的音符的最高音高为 m_im
i

。在确定了结构之后,她就可以开始写歌了。不过她还是想知道,一共有多少种可能的歌曲满足她的所有限制?她听说你还有 9 个月就要去 IOI 了,于是希望你帮她计算一下这个值。

输入格式
从标准输入读入数据。

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

每组数据的第一行包含三个正整数 n,Q,An,Q,A。接下来 QQ 行,每行三个整数 l_i,r_i,m_il
i

,r
i

,m
i

​​,表示一条限制。保证 1\le l_i\le r_i\le n, 1\le m_i\le A1≤l
i

≤r
i

≤n,1≤m
i

≤A。

输出格式
输出到标准输出。

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

输入输出样例
输入 #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
说明/提示
样例1解释 以下是三种可能的歌曲:(3,1,2),(3,2,1),(3,2,2)(3,1,2),(3,2,1),(3,2,2)。

0

思路

设 f[i][j]f[i][j] 表示表示满足了前 ii 个区间,处理到了位置 jj ,且 jj 上放的是最大值,并且 jj 后不能再出现最大值的方案数。

列出方程后可以发现转移是 O(n)O(n) 的,总复杂度就是 O(n^2logn)

使用前缀和优化将转移变成 O(1),总复杂度变为 O(nlogn)可过。

代码

#include<bits/stdc++.h>
#define N 505
#define mod 998244353
using namespace std;
typedef long long ll;
int T;
int n,m,A;
struct P{
    int l,r,h;
}a[N];
int p[N*2],top,up[N*2];
pii b[N*2];
int bin(int x)
{
    if(!x)  return 0;
    return lower_bound(p+1,p+top+1,x)-p;
}
int id[N*2],topx,mx[N*2];
int bin1(int x)
{
    int l=1,r=topx,pos=-1;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(id[mid]<=x)
        {
            pos=mid;
            l=mid+1;
        }
        else r=mid-1;
    }
    return pos;
}
int bin2(int x)
{
    int l=1,r=topx,pos=-1;
    while(l<=r)
    {
        int mid=(l+r)/2;
        if(id[mid]>=x)
        {
            pos=mid;
            r=mid-1;
        }
        else l=mid+1;
    }
    return pos;
}
int quick_pow(int x,int p)
{
    int an=1,po=x;
    while(p)
    {
        if(p&1)  an=1ll*an*po%mod;
        po=1ll*po*po%mod;
        p>>=1;
    }
    return an;
}
int g1[N*2],g2[N*2],f[N*2],sum[N*2],len[N*2];
int solve(int h)
{
    for(int i=1;i<=topx;i++)  mx[i]=0;
    sort(id+1,id+topx+1);
    for(int i=1;i<=m;i++)
    {
        if(a[i].h==h)
        {
            int r=bin1(a[i].r),l=bin2(a[i].l);
            mx[r]=max(mx[r],l);
        }
    }
    for(int i=1;i<=topx;i++)  mx[i]=max(mx[i-1],mx[i]);
    for(int i=1;i<=topx;i++)  g1[i]=quick_pow(h-1,p[id[i]]-p[id[i]-1]);
    for(int i=1;i<=topx;i++)  g2[i]=(quick_pow(h,p[id[i]]-p[id[i]-1])-g1[i]+mod)%mod;
    for(int i=1;i<=topx;i++)  len[i]=p[id[i]]-p[id[i]-1]+len[i-1];
    g2[topx+1]=1;
    f[0]=1;sum[0]=1;
    for(int i=1;i<=topx+1;i++)
    {
        if(mx[i-1])  f[i]=1ll*(sum[i-1]-1ll*sum[mx[i-1]-1]*quick_pow(h-1,len[i-1]-len[mx[i-1]-1])%mod+mod)*g2[i]%mod;
        else         f[i]=1ll*sum[i-1]*g2[i]%mod;
        sum[i]=(1ll*sum[i-1]*g1[i]+f[i])%mod;
    }
    return f[topx+1];
}
int ans;
int main()
{
    T=read();
    while(T--)
    {
        top=0;ans=1;
        n=read();m=read();A=read();
        for(int i=1;i<=m;i++)
        {
            a[i].l=read();a[i].r=read();a[i].h=read();
            if(a[i].l!=1)  p[++top]=a[i].l-1;
            p[++top]=a[i].r;
        }
        p[++top]=n;
        sort(p+1,p+top+1);
        top=unique(p+1,p+top+1)-p-1;
        for(int i=1;i<=top;i++)  up[i]=A;
        for(int i=1;i<=m;i++)
        {
            a[i].l=bin(a[i].l-1)+1;a[i].r=bin(a[i].r);
            for(int j=a[i].l;j<=a[i].r;j++)  up[j]=min(up[j],a[i].h);
        }
        int bzx=0;
        for(int i=1;i<=m;i++)
        {
            int bz=1;
            for(int j=a[i].l;j<=a[i].r;j++)
              if(up[j]==a[i].h)  bz=0;
            if(bz==1)
            {
                bzx=1;
                break;
            }
        }
        if(bzx)
        {
            puts("0");
            continue;
        }
        for(int i=1;i<=top;i++)  b[i]=mp(up[i],i);
        sort(b+1,b+top+1);
        for(int i=1;i<=top;i++)
        {
            topx=0;
            int R=i-1;
            while(R<top&&b[R+1].fi==b[i].fi)
            {
                R++;
                id[++topx]=b[R].se;
            }
            ans=1ll*ans*solve(b[i].fi)%mod;
            i=R;
        }
        printf("%d\n",ans);
    }
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值