UOJ Round #1 题解

题解:

质量不错的一套题目啊。。(题解也很不错啊)

t1:

首先暴力显然有20分,把ai相同的缩在一起就有40分了

然后会发现由于原来的式子有个%很不方便处理

so计数题嘛 考虑一下容斥

最终步数=初始步数-使用tab键减少的步数=(x-1)*sigma(ai/x)

这个显然就很好维护了

我们考虑对于ai/x只会有根号ai个取值

然后每个值分别实现区间加区间减 这样用差分来做就是n根号x 线段树nlogn根号x

复杂度稍微大了点

满分做法就是枚举x,然后枚举y,计算a[i]/x=y的数有几个,计算用前缀和处理一下

复杂度的话是n/1+n/2+n/3+...=nlogn的

t2:

感觉跟zjoi的dp挺像的。。(虽然简单一点)

首先要先弄出一波结论

就是若我们使用了ai,且ai<aj,那么再使用j就没有任何影响了

所以我们可以先将元素降序排列

令f[i][j]表示前i个,%的值为j是否可行

转移就是从f[i][j]------->f[i+1][j%a[i+1]]

这样第一问就解决了

对于第二问

首先排序是一样的

我们可以令f[j][k] 表示 当前%的值为j,其中有k个值比j大且还没有放(比j小的是一定还没放的)

那么转移就是f[j][k]--->f[j%a[now]][k+sum[now]-sum[j]] (now代表比j小的元素)

              或者f[j][k]--->f[j][k-1]

这样复杂度是n^3的

考虑优化状态

令f[i]表示当前%的值为i的方案数

那么考虑转移 f[i]---->f[j] 我们会发现,对于i-j之间的元素(设有y个),只有插入在这个点之后就行了,设x=比j小的元素个数

其实就是x+1个空里插y个数

那就是乘以A(y-1,x+y)

 

#include <bits/stdc++.h>
using namespace std;
#define ll long long 
const ll mo1=998244353;
#define N 11100
ll jc1[N*3],jc2[N*3],n,m,a[N],f[1100][N],dp[N];
ll sum[N];
ll x,y,ans;
bool cmp(ll x,ll y)
{
  return(x>y);
}
ll get_gcd(ll a,ll b,ll &x,ll &y)
{
 // cout<<a<<" "<<b<<endl;
  if (b==0)
  {
    x=1; y=0; return(b);
  }
  ll xx=get_gcd(b,a%b,y,x);
  y-=x*(a/b);
  return xx;
}
ll get_ans(ll x,ll y)
{
 // cout<<x<<" "<<y<<endl;
  if (y==0) return(1);
  ll ans=(jc1[x+y-1]*jc2[x-1])%mo1;
 // cout<<jc1[x+y-1]<<" "<<jc2[x-1]<<" "<<ans<<endl;
  return ans;
}
int main()
{
  freopen("noip.in","r",stdin);
  freopen("noip.out","w",stdout);
  std::ios::sync_with_stdio(false);
  cin>>n>>m;
  for (ll i=1;i<=n;i++)
    cin>>a[i];
  sort(a+1,a+n+1,cmp);
  f[1][m]=1;
  for (ll i=1;i<=n-1;i++)
  {
    for (ll j=0;j<=m;j++)
      if (f[i][j])
        f[i+1][j%a[i]]=1,f[i+1][j]=1;
  }
  for (ll j=0;j<=m;j++)
    if (f[n][j])
      f[n+1][j%a[n]]=1;
  ll j;
  for (j=m;j>-1;j--)
    if (f[n+1][j]) break;
  ans=j; cout<<ans<<endl;
  jc1[0]=jc2[0]=jc1[1]=jc2[1]=1;
  for (ll i=2;i<=11000;i++)
  {
    get_gcd(mo1,i,x,y);
    x=(x+mo1)%mo1;
    jc1[i]=jc1[i-1]*i;
    jc1[i]%=mo1;
    jc2[i]=jc2[i-1]*y;
    jc2[i]%=mo1;
  }
  dp[m]=1;
  for (ll i=1;i<=n;i++) sum[a[i]]++;
  for (ll i=1;i<=10000;i++) sum[i]+=sum[i-1];
  ll num=0;
  for (ll i=1;i<=n;i++) if (a[i]>m) num++;
  for (ll i=m;i>=ans;i--)
  if (dp[i])
  {
    ll j;
    for (j=1;j<=n;j++)
      if (a[j]<=i) break;
    x=j;
    for (ll j=x;j<=n;j++)
    {
      dp[i%a[j]]+=dp[i]*get_ans(sum[i%a[j]]+1,sum[i]-1-sum[i%a[j]]);
      get_ans(n-j+1,j-x); 
      dp[i%a[j]]%=mo1;
    }
  }
  ll ans1=dp[ans];
 // cout<<ans1<<"XXX"<<endl;
  ans1=ans1*get_ans(n-num+1,num);
  ans1%=mo1;
  cout<<(ans1+mo1)%mo1<<endl;
  return 0;
}

 

转载于:https://www.cnblogs.com/yinwuxiao/p/8620641.html

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值