2023.4.24测试

xishanmeigao / 2023-08-07 / 原文

区间第2大

题目大意

\(a[1\dots n]\)\(1\dots n\) 的某个排列,对于所有 \(1\leq l<r\leq n\),将出 \(a[l\dots r]\) 中第二大数累加到 \(ans\),求 \(ans\)

\(n\leq 10^5\)

解题思路

  • 对于 \(a[i]\),考虑它被计算了多少次,容易看出只需找出它左边第一个比它大的数,左边第二个比它大的数,右边第一个,右边第二个即可

  • \(\mathrm{ST}\) 表 + 二分即可

竹子

题目大意

\(n\) 棵竹子从左往右排成一行,编号 \(1\)\(n\),第 \(i\) 棵竹子的高度是\(h_i\)

每天你需要砍掉 \(1\) 棵竹子,被砍的竹子的高度会变成 \(0\),假设你当天砍掉的是第 \(i\) 棵竹子,那么此次的代价是 \(h_{i-1}+h_i+h_{i+1}\)

我们认为 \(h_0=h_{n+1}=0\)

显然,总共有 \(n!\) 种不同的砍竹子的方案,有多少种不同的方案,使得砍完所有竹子的总代价最小?

输出能使得总代价最小的不同方案数。答案模 \(1000000007\)

解题思路

  • 蒟蒻我考场只会 \(O(n!)\) 的暴力

  • 第一次接触 插入\(\mathrm{dp}\) ,只考虑前 \(i\) 课竹子,后面的竹子等到插入时再考虑。设 \(f[i][j]\) 表示第 \(i\) 课竹子在前 \(i\) 棵竹子中是第 \(j\) 棵砍的,此时总代价的最小值,那么就有

    \[f[i][j]=\min\begin{cases}f[i-1][k]+h_i+h_i&1\leq k<j\\f[i-1][k]+h_i+h_{i-1}&j\leq k\leq i-1\end{cases} \]

  • 用一个前后缀预处理即可转移

  • \(g[i][j]\) 表示第 \(i\) 课竹子在前 \(i\) 棵竹子中是第 \(j\) 棵砍的,此时让总代价最小的方案数,随着 \(f\) 的转移进行转移即可

代码

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

const int N=4010;
const LL MOD=1000000007;

int n;
LL h[N],f[N][N],g[N][N];
LL ff[N][2],gg[N][2];

int main()
{
	memset(f,0x3f,sizeof(f));
	memset(ff,0x3f,sizeof(ff));
	
	scanf("%d",&n);
	for(int i=1; i<=n; i++)
		scanf("%lld",&h[i]);
	
	f[1][1]=h[1];
	g[1][1]=1;
	ff[1][0]=ff[1][1]=f[1][1];
	gg[1][0]=gg[1][1]=1;
	for(int i=2; i<=n; i++)
	{
		for(int j=1; j<=i; j++)
		{
			LL a=ff[j-1][0]+h[i]+h[i],b=ff[j][1]+h[i]+h[i-1];
			if(a==b)
				g[i][j]=(gg[j-1][0]+gg[j][1])%MOD;
			else if(a<b)
				g[i][j]=gg[j-1][0];
			else
				g[i][j]=gg[j][1];
			f[i][j]=min(a,b);
		}
		
		memset(ff,0x3f,sizeof(ff));
		memset(gg,0,sizeof(gg));
		
		for(int j=1; j<=i; j++)
			ff[j][0]=min(ff[j-1][0],f[i][j]);
		for(int j=i; j>=1; j--)
			ff[j][1]=min(ff[j+1][1],f[i][j]);
		for(int j=1; j<=i; j++)
		{
			if(f[i][j]==ff[j-1][0])
				gg[j][0]=(gg[j-1][0]+g[i][j])%MOD;
			else if(f[i][j]<ff[j-1][0])
				gg[j][0]=g[i][j];
			else
				gg[j][0]=gg[j-1][0];
		}
		for(int j=i; j>=1; j--)
		{
			if(f[i][j]==ff[j+1][1])
				gg[j][1]=(gg[j+1][1]+g[i][j])%MOD;
			else if(f[i][j]<ff[j+1][1])
				gg[j][1]=g[i][j];
			else
				gg[j][1]=gg[j+1][1];
		}
			
	}
	
	LL mn=1e14,ans=0;
	for(int i=1; i<=n; i++)
		mn=min(mn,f[n][i]);
			
	for(int i=1; i<=n; i++)
		if(f[n][i]==mn)
			ans=(ans+g[n][i])%MOD;
		
	printf("%lld",ans);
		
	return 0;
}

奶牛安家

题目大意

\(n\) 头奶牛,在一条数轴上选择自己的家。数轴上有 \(m\) 个整点,分别是 \(0\)\(m-1\)。每头奶牛都选择一个不同的整点作为的自己的家。第 \(i\) 头奶牛有一个安全距离 \(R[i]\),表示的意义是第 \(i\) 头奶牛与它的邻居的距离不能小于 \(R[i]\)。也就是说,如果奶牛 \(i\) 和奶牛 \(j\) 是邻居,那么这两头奶牛之间的距离不能小于 \(\max(R[i],R[j])\)。输出有多少种不同的合法安家方案,答案模 \(1000000007\)

解题思路

  • 显然最终的答案和奶牛的排列有关。如果我们知道最后奶牛之间的距离限制总和 \(s\),那么我们就可以使用插板法求出答案,为 \(C_{m-s+n-1}^{n}\)

  • 现在考虑如何计算限制长度和为 \(s\) 的方案数。我们先将奶牛按 \(R\) 从小到大排序,将限制抽象成一条链,那这条链可以是由多条链连接在一起的。因为我们排了序,那么后面的奶牛参与到链的拼接时就无需考虑大小关系。

  • 考虑 \(\mathrm{dp}\),设 \(f[i][j][k]\) 表示到第 \(i\) 头奶牛,目前的链数为 \(j\),链的总长度为 \(k\)。那么考虑我为人人\(f[i][j][k]\) 可以对哪些状态作出贡献

    • \(f[i][j][k]\rightarrow f[i+1][j+1][k]\)

      表示第 \(i+1\) 头奶牛单独成链

    • \(f[i][j][k]*j*(j-1)\rightarrow f[i+1][j-1][k+R[i+1]*2]\)

      表示用第 \(i+1\) 头奶牛连接任意两条链

    • \(f[i][j][k]*2*j\rightarrow f[i+1][j][k+R[i+1]]\)

      表示将第 \(i+1\) 头奶牛任意接到一条链的头或尾

  • 做完 \(\mathrm{dp}\) 后组合数求解即可。注意因为组合数涉及阶乘和除法的运算,所以需要做预处理,还要求逆元。因为模数 \(1000000007\) 是质数,所以可以用费马小定理来求。

代码

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

const int N=50,M=100010;
const LL MOD=1000000007;

int G,n,m,r[N];
LL pw[M+N],f[N][N][N*N];
LL ans;

LL ksm(int a,LL b)
{
	if(b==0)
		return 1;
	if(b==1)
		return a;
	LL tmp=ksm(a,b/2);
	if(b%2==0)
		return tmp*tmp%MOD;
	return tmp*tmp%MOD*a%MOD;
}

LL C(int a,int b)
{
	if(a<b)
		return 0;
	return pw[a]*ksm(pw[a-b],MOD-2)%MOD*ksm(pw[b],MOD-2)%MOD;
}

int main()
{
	pw[0]=1;
	for(int i=1; i<=M+N-20; i++)
		pw[i]=(pw[i-1]*i)%MOD;
		
	scanf("%d",&G);
	while(G--)
	{
		memset(f,0,sizeof(f));
		
		scanf("%d%d",&m,&n);
		for(int i=1; i<=n; i++)
			scanf("%d",&r[i]);
		
		sort(r+1,r+1+n);
		
		f[0][0][0]=1;
		for(int i=0; i<n; i++)
		{
			for(int j=0; j<=i; j++)
			{
				for(int k=0; k<=40*n; k++)
				{
					f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k])%MOD;
					if(j>=2)
						f[i+1][j-1][k+r[i+1]*2]=(f[i+1][j-1][k+r[i+1]*2]+f[i][j][k]*1LL*j%MOD*(j-1)%MOD)%MOD;
					if(j>=1)
						f[i+1][j][k+r[i+1]]=(f[i+1][j][k+r[i+1]]+f[i][j][k]*1LL*2*j%MOD)%MOD;
				}
			}
		}
		
		ans=0;
		for(int i=0; i<=40*n; i++)
			ans=(ans+f[n][1][i]*C(m-i+n-1,n)%MOD)%MOD;
		
		printf("%lld\n",ans);
	}
	
	return 0;
}

加强版 [COCI2021-2022#2] Magneti

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

const int N=60,M=10010;
const LL MOD=1000000007;

int G,n,m,r[N];
LL pw[M+N],f[N][N][M];
LL ans;

LL ksm(LL a,LL b)
{
    if(b==0)
        return 1;
    if(b==1)
        return a;
    LL tmp=ksm(a,b/2);
    if(b%2==0)
        return tmp*tmp%MOD;
    return tmp*tmp%MOD*a%MOD;
}

LL C(int a,int b)
{
    if(a<b)
        return 0;
    return pw[a]*ksm(pw[a-b],MOD-2)%MOD*ksm(pw[b],MOD-2)%MOD;
}

int main()
{
    pw[0]=1;
    for(int i=1; i<=M-10; i++)
        pw[i]=(pw[i-1]*i)%MOD;


    memset(f,0,sizeof(f));

    scanf("%d%d",&n,&m);
    for(int i=1; i<=n; i++)
        scanf("%d",&r[i]);

    sort(r+1,r+1+n);

    f[0][0][0]=1;
    for(int i=0; i<n; i++)
    {
        for(int j=0; j<=i; j++)
        {
            for(int k=0; k<=m; k++)
            {
                f[i+1][j+1][k]=(f[i+1][j+1][k]+f[i][j][k])%MOD;
                if(j>=2 && k+r[i+1]*2<=m)
                    f[i+1][j-1][k+r[i+1]*2]=(f[i+1][j-1][k+r[i+1]*2]+f[i][j][k]*1LL*j%MOD*(j-1)%MOD)%MOD;
                if(j>=1 && k+r[i+1]<=m)
                    f[i+1][j][k+r[i+1]]=(f[i+1][j][k+r[i+1]]+f[i][j][k]*1LL*2*j%MOD)%MOD;
            }
        }
    }

    ans=0;
    for(int i=0; i<=m; i++)
        ans=(ans+f[n][1][i]*C(m-i+n-1,n)%MOD)%MOD;

    printf("%lld\n",ans);

    return 0;
}