2023.4.24测试

区间第2大

题目大意

a[1n]1n 的某个排列,对于所有 1l<rn,将出 a[lr] 中第二大数累加到 ans,求 ans

n105

解题思路

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

  • ST 表 + 二分即可

竹子

题目大意

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

每天你需要砍掉 1 棵竹子,被砍的竹子的高度会变成 0,假设你当天砍掉的是第 i 棵竹子,那么此次的代价是 hi1+hi+hi+1

我们认为 h0=hn+1=0

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

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

解题思路

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

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

    f[i][j]=min{f[i1][k]+hi+hi1k<jf[i1][k]+hi+hi1jki1

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

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

解题思路

  • 显然最终的答案和奶牛的排列有关。如果我们知道最后奶牛之间的距离限制总和 s,那么我们就可以使用插板法求出答案,为 Cms+n1n

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

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

    • f[i][j][k]f[i+1][j+1][k]

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

    • f[i][j][k]j(j1)f[i+1][j1][k+R[i+1]2]

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

    • f[i][j][k]2jf[i+1][j][k+R[i+1]]

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

  • 做完 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;
}
posted @   xishanmeigao  阅读(66)  评论(0编辑  收藏  举报
阅读排行:
· 地球OL攻略 —— 某应届生求职总结
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 提示词工程——AI应用必不可少的技术
· .NET周刊【3月第1期 2025-03-02】
点击右上角即可分享
微信分享提示