UOJ 22 [UR #1]外星人

DP+推式子+组合数+逆元

我们发现如果ai<=aj,那么x mod ai = x mod ai mod aj,即模了小的数,大的数模不模就都一样。于是我们发现x的值的变化仅和小于等于x的a有关。

于是可以记f[i]表示x=i的最大答案,这个可以O(nx)DP出来,第一问解决。

我们发现从i变化到i%a的过程中需要的序列里,(i%a , i]区间里的所有模数可以任意排布在a的后面而不会影响结果(因为这里面的数已经严格大于i%a),于是我们记g[i]表示i得到最大值且仅考虑小于等于i的模数的方案数,可以通过组合数学的知识推出转移式。最后再考虑上大于x的模数即可。

好像别人打的代码都比我的短啊……

#include<cstdio>
#include<algorithm>
#define N 1005
#define X 5005
#define MOD 998244353
using namespace std;
bool from[N][X];
const int INF = 1<<29;
long long inv[X], fac[X], inc[N], g[X];
int a[N], f[X], pre[X];
void init()
{
    fac[0]=1;
    fac[1]=1;
    inv[1]=1;
    inc[0]=1;
    inc[1]=1;
    for(int i = 2;  i < N; i++)
    {
        fac[i]=fac[i-1]*i%MOD;
        inv[i]=MOD-(MOD/i)*inv[MOD%i]%MOD;
        inc[i]=inc[i-1]*inv[i]%MOD;
    }
}
int main()
{
    int n, x;
    scanf("%d%d",&n,&x);
    init();
    for(int i = 1; i <= n; i++)
        scanf("%d",&a[i]);

    for(int i = 1; i <= n; i++)
        pre[a[i]]++;
    for(int i = 0; i < X; i++)
        pre[i]+=pre[i-1];

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

    for(int i = 0; i < a[1]; i++)
    {
        f[i]=i;
        g[i]=1;
    }

    for(int i = a[1]; i <= x; i++)
    {
        f[i]=-INF;
        for(int j = 1; j <= n && a[j]<=i; j++)
            if(f[i]<f[i%a[j]])f[i]=f[i%a[j]];
        for(int j = 1; j <= n && a[j]<=i; j++)
            if(f[i]==f[i%a[j]])
                (g[i] += g[i%a[j]] * fac[pre[i]-1-pre[i%a[j]]] %MOD * fac[pre[i]-1] %MOD * inc[pre[i%a[j]]] %MOD * inc[pre[i]-1-pre[i%a[j]]] % MOD)%=MOD;
    }   

    g[x] = fac[pre[X-1]-pre[x]] * g[x] %MOD * fac[pre[X-1]] %MOD * inc[pre[x]] %MOD * inc[pre[X-1]-pre[x]] % MOD ;

    printf("%d\n%lld\n",f[x],(g[x]+MOD)%MOD);

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值