UOJ #22 外星人

描述

2044年,Picks建成了人类第一台基于量子理论的银河系信息传递机。

Picks游遍了宇宙,雇用了 n 个外星人来帮他作为信息传递机的中转站。我们将外星人依次编号为 1n ,其中 i 号外星人有 a_i 根手指。

外星人都是很低级的,于是Picks花费了很大的精力,才教会他们学会扳手指数数。

Picks现在准备传递 x 个脉冲信号给VFleaKing,于是他把信号发给 1 号外星人,然后 1 号外星人把信号发送给 2 号外星人, 2 号外星人把信号发送给 3 号外星人,依次类推,最后 n 号外星人把信号发给VFleaKing。

但是事情没有Picks想象的那么顺利,由于外星人手指个数有限,所以如果 i 号外星人收到了 t 个脉冲信号,他会错误的以为发送过来的是 t \mod a_i 个脉冲信号,导致只发送了 t \mod a_i 个脉冲信号出去。

Picks希望他发送出去的脉冲信号数量 x 与VFleaKing收到的脉冲信号数量 y 的差的绝对值尽量小。于是他决定通过重新排列这些外星人的顺序来达到这一目的。请你求出与 x 之差最小的 y 。除此之外,请求出有多少种排列外星人的方式能达到最优解,你只需要输出方案数对 9982443537×17×223+1 ,一个质数)取模后的结果。

输入格式

第一行两个正整数 n,x

接下来一行有 n 个正整数 a_i ,表示 i 号外星人的手指数。

输出格式

第一行一个整数表示最优情况下VFleaKing收到的脉冲数量。

第二行一个整数表示达到最优情况的方案数。

这些外星人原来并不是一个种族的=_=

题解(参考官方题解)

首先考虑 \Theta(n^2) 解决第一个问

枚举当前的脉冲信号个数,然后枚举一遍所有外星人,得出传递到下一步的脉冲信号个数

若最小的 a_iL ,那么最后的结果一定 <L,只需要找到可行的最大的小于 L 的个数即可

虽然题目有限制,每一个外星人只能使用 1 次,而我们的枚举会把每个外星人枚举 n 次,看似会重复使用,但是实际上每个外形人也只能使用 1 次。使用 a_i 过后的结果一定小于 a_i,因此再多使用一次也不会改变结果。

考虑较高复杂度解决第二个问

我们对于当前的 x,相同的 x 仍然对应不同的情况,因为对于 a_i>=x ,使用后是不会影响 x 的结果的。所以说我们对于每一个 x ,需要记录当前还有多少个 a_i>=x,如此一来我们就能够得到一个使用 a_i>=x 递推

F[x][i]+=F[x][i+1]

而对于另外的 a_i<=x ,则有

F[x \mod a_i][i+C[x]-C[a_i]]+=F[x][i]

此处 C[i] 表示 <=ia_i 的个数

C[a_i]-C[x] 对应的则是增加的 a_k>=(x \mod a_i) 的个数

大概算一下吧

①枚举 x

②枚举 i

③枚举 a_i<x

共三层,复杂度约为 \Theta(xn^2)

尝试简化

上面的两维状态尝试先减去第一维,也就是说记录当前某一类数字的个数,我发现我做不到

于是就按照官方题解把第二维省去

f[x] 表示当前剩余的脉冲信号个数

那么我们就要想如何快速进行剩余的 a_i>x 数字个数的讨论

由于省去了第二维,所以我们在当前讨论 x\to x \mod a_i 的时候就把 C[a_i]-C[x] 的数字讨论掉,这样子才不会出现多余的那一维

对于 f[x] ,剩余的位子实际上为 C[x] 个,而下一个位子必定由 a_i 占领,而由 a_i 占领之后,其余的 a_k\in[a_i,x] 则可以任意占领后面的位子,只有 a_k<a_i 的数字必须保持一定的顺序,或者说它们的顺序由后面的的讨论决定,我们当前无法确定这些数字的位置

因此这 a_k\in [a_i,x] 的排列方案数为 \frac{(C[x]-1)!}{C[a_i]!}

递推式满足

f[x \mod a_i]+=f[x]\cdot\frac{(C[x]-1)!}{C[a_i]!}

时间复杂度减少为 \Theta(xn),空间复杂度减少为 \Theta(x)

附上代码

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const long long mod=998244353;

bool mark[5678];
long long inv[5678],fac[5678],f[5678];
int n,x,mn=1e9,mx=0,C[5678],A[1234];

int main()
{
	scanf("%d%d",&n,&x);
	inv[1]=inv[0]=fac[0]=1;
	for(int i=2;i<=x;i++)inv[i]=-(mod/i)*inv[mod%i]%mod;
	for(int i=2;i<=x;i++)inv[i]=(inv[i]*inv[i-1]%mod+mod)%mod;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&A[i]);
		C[A[i]]++;
		mn=min(mn,A[i]);
		mx=max(mx,A[i]);
	}
	for(int i=1,mix=max(mx,x);i<=mix;i++)C[i]+=C[i-1];
	for(int i=1,mix=max(mx,n);i<=mix;i++)fac[i]=fac[i-1]*i%mod;
	sort(A+1,A+n+1); 
	f[x]=fac[C[mx]]*inv[C[x]]%mod;
	mark[x]=1;int t=x;
	for(x=x;x;x--)
	if(mark[x])
	{
		for(int k=1;k<=n&&A[k]<=x;k++)
			{
				mark[x%A[k]]=1;
				f[x%A[k]]=(f[x%A[k]]+f[x]*fac[C[x]-1]%mod*inv[C[x%A[k]]])%mod;
			}
	}
	for(int i=mn-1;i>=0;i--)
		if(mark[i])
		{
			printf("%d\n%lld",i,f[i]<0?f[i]+mod:f[i]);
			return 0;
		}
}

复制代码

转载于:https://juejin.im/post/5a92755c5188257a76634884

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值