题解:洛谷 P10138([USACO24JAN] Cowmpetency G)

Link

1. Description

对于一个长度为 \(n\),所有元素均在 \([1,c]\) 的范围内的序列 \(val\),询问满足 \(q\) 个限制的序列有多少个。

限制形如 \((a,h)\),要求 \(val_h\) 是第一个严格大于 \(\max_{i=1}^{a} val_i\) 的值位置,题给条件满足至少存在一个合法序列。

2. Solution

考虑限制 \((a,h)\),设 \(x=\max_{i=1}^a val_i,y=\begin{cases}0\ (a+1>h-1)\\ \max_{i=a+1}^{h-1} val_i\end{cases},z=val_h\),那么有 \(y\le x<z\)

因此对于 \(h\) 这个位置,一定是严格前缀最大值,对于 \([a+1,h-1]\) 这些位置,一定不是严格前缀最大值。

我们不妨使用一个数组 \(opt\) 表示整个序列的限制情况,当 \(opt_i>0\) 时,这个位置一定是严格前缀最大值,当 \(opt_i<0\) 时,这个位置一定不是严格前缀最大值,当 \(opt_i=0\) 时,这个位置没有限制。

所以对于一个限制 \((a,h)\),将 \([a+1,h-1]\) 这一段减一,然后在 \(h\) 这个位置加一即可。由于必存在一个合法序列,所以不会出现一个位置同时进行了加一和减一两种操作的情况,也就是既一定又不一定的情况。

然后考虑 dp,定义 \(f_{i,j}\) 表示考虑前 \(i\) 项,此时前缀最大值为 \(j\) 的方案数,那么转移可以分为两种:这一项是或不是严格前缀最大值。

当这一项是严格前缀最大值时,由于前缀最大值是 \(j\),所以这一项必须是 \(j\),且前 \(i-1\) 项的前缀最大值严格小于 \(j\),那么就有 \(\sum_{k=1}^{j-1} f_{i-1,k}\) 种情况,可以使用前缀和优化,定义 \(g_{i,j}=\sum_{k=1}^j g_{i,k}\),那么就有 \(g_{i-1,j-1}\) 种情况。

当这一项不是严格前缀最大值时,前 \(i-1\) 项的前缀最大值一定是 \(j\),这一项可以取 \(1\sim j\) 的任一一个,那么就有 \(f_{i-1,j}\times j\) 种情况,无需优化。

因此,转移可以写为:

\[f_{i,j}= \begin{cases} f_{i-1,j}\times j\ (opt_i<0)\\ f_{i-1,j}\times j+g_{i-1,j-1}\ (opt=0)\\ g_{i-1,j-1}\ (opt>0)\\ \end{cases} \]

此时的时空复杂度为 \(O(nc)\),考虑优化。

我们发现,有很多段 \(opt\) 序列的值是一样的,所以我们考虑将 \(opt\) 分成若干段连续的区间,那么区间数是 \(O(q)\) 的,那么 \(f_{i,j}\) 表示考虑前 \(i\) 段,此时前缀最大值为 \(j\) 的方案数。

所以此时只需考虑一段 \((l,r,opt)\) 怎么快速转移。

\(opt<0\) 的时候,显然 \(f_{i,j}=f_{i-1,j}\times j^{r-l+1}\)

\(opt>0\) 的时候,由于 \(opt>0\) 的位置一共就只有 \(O(q)\) 个,所以可以直接暴力转移。

\(opt=0\) 的时候,就需要分类讨论了:

  1. 前缀最大值出现在前 \(i-1\) 段,那么这 \(r-l+1\) 个值可以取 \([1,j]\) 的任一一个值,所以一共有 \(f_{i-1,j}\times j^{r-l+1}\) 种。
  2. 前缀最大值出现在第 \(i\) 段,那么这 \(r-l+1\) 个值中至少有一个为 \(j\),考虑反转问题,在 \(r-l+1\) 个值中一个 \(j\) 都没有的情况有 \((j-1)^{r-l+1}\) 种,而一共有 \(j^{r-l+1}\) 种情况,所以这 \(r-l+1\) 个值中至少有一个为 \(j\) 情况有 \(j^{r-l+1}-(j-1)^{r-l+1}\) 种,而前 \(i-1\) 段的前缀最大值小于 \(j\),就有 \(\sum_{k=1}^{j-1} f_{i-1,k}\) 种情况,同样可以使用前缀和优化,定义 \(g_{i,j}=\sum_{k=1}^{j} f_{i,k}\)。那么就一共有 \(g_{i-1,j-1}\times [j^{r-l+1}-(j-1)^{r-l+1}]\) 种情况。

所以,当 \(opt=0\) 的时候,\(f_{i,j}=f_{i-1,j}\times j^{r-l+1}+g_{i-1,j-1}\times [j^{r-l+1}-(j-1)^{r-l+1}]\)

那么我们只需要根据所有限制求出 \(opt\) 序列分成的 \((l,r,opt)\),然后转移即可,注意两个区间有交的情况,此时需要合并。

3. Code

/*by qwer6*/
/*略去缺省源和快读快写*/
const int N=105,M=1e4+5,mod=1e9+7;
int n,m,c,ans,cnt;
int f[N*3][M];
struct Line{
    int l,r,opt;
    bool operator <(const Line &a){
        return l<a.l;
    }
}a[N<<1],b[N<<1];
int sub(int x,int y){
    x-=y;
    return x<0?x+mod:x;
}
int add(int x,int y){
	x+=y;
	return x>=mod?x-mod:x;
}
int mul(int x,int y){
	long long res=1ll*x*y;
	return res>=mod?res%mod:res;
}
int binpow(int a,int b){
    int res=1;
    while(b){
        if(b&1)res=mul(res,a);
        a=mul(a,a);
        b>>=1;
    }
    return res;
}
int init(){
    int tmp=0,_l=a[1].l,_r=a[1].r,_opt=a[1].opt;
    for(int i=2,l=a[1].l,r=a[1].r;i<=cnt;i++){
        if(a[i].opt!=_opt||a[i].l>min(a[i].r,_r)){//必然视为两份  
            b[++tmp]={_l,_r,_opt};
            _l=a[i].l,_r=a[i].r,_opt=a[i].opt;
        }else _r=max(a[i].r,_r);
    }
    b[++tmp]={_l,_r,_opt};
    int ttmp=tmp,las=1;
    for(int i=1;i<=tmp;i++){
        if(las<=b[i].l-1)b[++ttmp]={las,b[i].l-1,0};
        las=b[i].r+1; 
    }
    if(las<=n)b[++ttmp]={las,n,0};
    sort(b+1,b+ttmp+1);
    return ttmp;
}
void DP(int idx,int l,int r,int opt){
    int len=r-l+1;
    if(opt==-1){
        for(int i=1;i<=c;i++)
            f[idx][i]=mul(f[idx-1][i],binpow(i,len));
    }else if(opt==0){
        for(int j=1,sum=0,x;j<=c;j++){
            sum=add(sum,f[idx-1][j-1]),x=binpow(j,len);
            f[idx][j]=mul(sum,sub(x,binpow(j-1,len)));
            f[idx][j]=add(f[idx][j],mul(f[idx-1][j],x));
        }
    }else{
        for(int j=1,sum=0;j<=c;j++){
            sum=add(sum,f[idx-1][j-1]);
            f[idx][j]=sum;
        }
        for(int i=2;i<=len;i++){
            for(int j=1,sum=0;j<=c;j++){
                sum=add(sum,f[idx][j-1]);
                f[idx][j]=sum;
            }
        }
    }
}
signed main(){
	read(n),read(m),read(c);
	for(int i=1,x,y;i<=m;i++){
        read(x),read(y);
        if(x+1<=y-1)a[++cnt]={x+1,y-1,-1};
        a[++cnt]={y,y,1};
    }
    sort(a+1,a+cnt+1);
    cnt=init();
    f[0][0]=1;
    for(int i=1;i<=cnt;i++)
        DP(i,b[i].l,b[i].r,b[i].opt);
    for(int i=1;i<=c;i++)ans=add(ans,f[cnt][i]);
    write(ans);
}
posted @ 2025-05-03 10:42  陈牧九  阅读(35)  评论(0)    收藏  举报