Educational Codeforces Round 83 (Rated for Div. 2)

D. Count the Arrays

Your task is to calculate the number of arrays such that:

each array contains n elements;
each element is an integer from 1 to m;
for each array, there is exactly one pair of equal elements;
for each array a, there exists an index i such that the array is strictly ascending before the i-th element and strictly descending after it (formally, it means that aj<aj+1, if j<i, and aj>aj+1, if j≥i).
Input
The first line contains two integers n and m (2≤n≤m≤2⋅105).

Output
Print one integer — the number of arrays that meet all of the aforementioned conditions, taken modulo 998244353.

题意:
计算满足如下要求的序列的数量。

1:序列长度为n。
2:序列中的元素为1~m。
3:序列中有且仅有一对数字相等。
4:序列有一个分界点,分界点左边的数字严格单调递增,右边的数字严格单调递减。
数据范围:2≤n≤m≤2e5。

对结果取mod 998244353。

思路:
首先特判,就是当n=2时,没有满足条件的序列,输出0。

这是一个满足条件的序列,满足图像成山峰型,且仅一对数字相等。

所以我们可以转化一下。
将其视为一个数字两两不同的递增序列,那满足这样条件的序列有多少种?
我们可以直接从m个数字中选n−1个出来,一定可以组成这样一个递增序列,所以答案是:
C n−1,m

但是还有许多情况漏解,比如说我原序列有两个1,
那么我们就会发现,除了最大的数字之外,所有的数字都可以裂化为两个放到最大数字的右边,所以此时我们要对答案乘上n−2。

此时还是会漏解,我们尝试把除了5之外的左边的数字移动到右边,那么有多少种方案?

首先在这n−2个数字当中,5不能右移过去,那也就是剩下n−3个数字,那么此时枚举往右放多少个数字,放0,1,2,3,…,n−3都是可以的,所以我们要对答案乘上:
C 0,n−3 + C 1,n−3 + C 2,n−3 + … + C n−3,n−3 = 2n−3

综上所述,答案为:
C (n−1,m) ×(n−2)×2n−3

#include <bits/stdc++.h>
#define MAX_INT  ((unsigned)(-1)>>1)
#define MIN_INT  (~MAX_INT)
#define db printf("where!\n");
using namespace std;
#define ll long long
const int maxn=1e5+5;
int read()
{
    int c=0;int flag=1;
    char s;
    while((s=getchar())>'9'||s<'0')if(s=='-')flag=-1;
    c=s-'0';
    while((s=getchar())<='9'&&s>='0') c=c*10+s-'0';
    return c*flag;
}
ll mod=998244353;
ll qp(ll a,ll b,ll p)
{
    ll ans=1;
    while(b){
        if(b%2) ans=ans*a%p;
        a=a*a%p;
        b>>=1;
    }
    return ans;
}
ll c(ll m,ll n)//以前用什么费小马定理写的排列组合的板子,现在直接拿来用了,忘了什么原理了,我真菜
{
    ll ans1=1,ans2=1,ans3=1;
    for(ll i=m;i>(m-n);i--){
        ans1=(ans1*i)%mod;
        ans1%=mod;
    }
    for(ll i=1;i<=n;i++){
        ans2=(ans2*i)%mod;
        ans2%=mod;
    }
    ll p=mod-2;
    while(p>0){
        if(p%2==1) ans3=(ans3*ans2)%mod;
        ans2=(ans2*ans2) %mod;
        p/=2;
    }
    return (ans1*ans3)%mod;
}
int main(void)
{
    ll n,m;
    cin>>n>>m;
    if(n==2){
        cout<<0;
        return 0;
    }
    ll ans=c(m,n-1);
    ans=(ans*(n-2))%mod;
    ll temp=qp(2,n-3,mod);
    ans=ans*temp%mod;
    cout<<ans;
    return 0;
}

E.阵列收缩

输出量标准输出
你会得到一个数组a1,a2,…,an。您可以任意次数执行以下操作:

选择一对相邻的等号元素。ai=ai+1(如至少有一对这样的配对)。
用一个元素替换为值ai+1.
在每个这样的操作之后,数组的长度将减少一个(并相应地重编号元素)。数组的最小可能长度是多少?a你能得到?

输入
第一行包含单个整数。n (1≤n≤500)-数组的初始长度a.

第二行包含n整数a1,a2,…,an (1≤ai≤1000)-初始数组a.

输出量
打印唯一的整数-执行上述任意次数的操作后可以得到的最小可能长度。

给定一个长度为 n 的序列 a,1≤n≤500,1≤ai≤1000

盗用一个大佬的思路
类似 2048 的玩法,每次可以将相邻的两个一样的数字 x 合并为 x + 1,求最后整个序列的最小长度为多少

一个比较显然的结论是本题的数据范围和做法是区间 dp,因为涉及到区间的合并问题

然后会发现直接做区间 dp 的话会很不好写,因为比较显然的结论是一个区间并不能由两个小区间去决定它的答案,可能是由很多个小区间才能合并组成,当区间的答案为 1 时才会去考虑合并还要考虑数字是多少

令 f i,j 表示 i→f i,j−1 这一段可以组成一个数 j,f i,j =0 表示从 i 为起点不能构成 j 这个数

首先 ai=j 时显然有 fi,j=i+1
fi,j=ff[i][j−1],j−1 就是往后枚举两个 j−1,注意要第一维枚举 j 顺序问题
然后 dpi,j 表示这个区间内至少需要多少个数表示,首先置为 inf,对于 f i,j 存在的点,则把 dp i,f[i][j]−1 置为 1 表示这个区间只需要一个数表示,然后做第一维枚举区间长度的区间 dp 即可,dp1,n 就是答案

#include <bits/stdc++.h>
#define MAX_INT  ((unsigned)(-1)>>1)
#define MIN_INT  (~MAX_INT)
#define db printf("where!\n");
using namespace std;
#define ll long long
int read()
{
    int c=0;int flag=1;
    char s;
    while((s=getchar())>'9'||s<'0')if(s=='-')flag=-1;
    c=s-'0';
    while((s=getchar())<='9'&&s>='0') c=c*10+s-'0';
    return c*flag;
}
const int maxn=1e5+5;
int n;
int a[505];
int f[505][2005];
int dp[505][505];
int main(void)
{
    cin>>n;
    memset(dp,0x3f,sizeof dp);
    for(int i=1;i<=n;i++) cin>>a[i],dp[i][i]=1;
    for(int j=1;j<=2500;j++){
        for(int i=1;i<=n;i++){
            if(a[i]==j) f[i][j]=i+1;
            else f[i][j]=f[f[i][j-1]][j-1];//从i开始等于j-1 的后区间再找一次等于j-1的
            if(f[i][j]) dp[i][f[i][j]-1]=1;
        }
    }
    for(int len=1;len<=n-1;len++){
        for(int i=1;i+len<=n;i++){
            int j=i+len;
            for(int k=i;k<j;k++){
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]);
            }
        }
    }
    cout<<min(n,dp[1][n]);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值