首页 > 其他分享 >题解 CF840C On the Bench

题解 CF840C On the Bench

时间:2023-07-17 21:47:53浏览次数:38  
标签:right limits cdot 题解 sum Bench int CF840C ll

这是一篇简洁易懂的良心题解,提供了多种做法。

对于两个数 \(x,y\),定义 \(x=n^2 \cdot tx,y=m^2 \cdot ty\)。如果 \(x\cdot y\) 为平方数,则说明 \(tx=ty\)。所以我们可以将每个数除去其平方因子,比较相邻两数是否相等即可。

F1:

定义 \(f_{i,j,k}\) 为插入 \(i\) 个数、有 \(j\) 对相邻相等的数、有 \(k\) 对相邻且与 \(a_i\) 相等的数。

定义 \(s_i\) 为在插入第 \(i\) 个数之前有多少个数与 \(a_i\) 相等。

转移分三种情况讨论:

  1. \(a_i\) 插入后与等于 \(a_i\) 的数相邻:

\[f_{i,j,k}=f_{i-1,j-1,k-1}\times (2\cdot s_i-k+1) \]

  1. \(a_i\) 插入后相邻的两个数相等:

\[f_{i,j,k}=f_{i-1,j+1,k}\times (j-k+1) \]

  1. \(a_i\) 插入后没发生任何变化:

\[f_{i,j,k}=f_{i-1,j,k}\times(i-2\cdot s_i+2\cdot k-j) \]

实现时可以把 \(a\) 数组排序,如果 \(a_i \neq a_{i-1}\),则将 \(f_{i-1,j,k}\) 全部加到 \(f_{i-1,j,0}\) 上,并清 \(0\)。

可以把第一维滚掉。复杂度 \(O(n^3)\)。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300+5,mod=1e9+7;
ll n,a[N],f[2][N][N];
void fen(ll &x){
  for(ll i=2;i*i<=x;++i){
    while(x%(i*i)==0){
      x/=i*i;
    }
  }
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;for(int i=1;i<=n;++i){cin>>a[i];fen(a[i]);}
  sort(a+1,a+n+1);
  int t=0,cnt=0;f[0][0][0]=1;
  for(int i=1;i<=n;++i){
    if(a[i]!=a[i-1]){
      for(int j=0;j<i;++j){
        for(int k=1;k<=min(j,cnt);++k){
          f[t^1][j][0]=(f[t^1][j][0]+f[t^1][j][k])%mod;
          f[t^1][j][k]=0;
        }
      }
      cnt=0;
    }
    for(int j=0;j<i;++j){
      for(int k=0;k<=min(cnt,j);++k){
        ll tmp=0;
        if(j&&k)tmp=f[t^1][j-1][k-1]*(2*cnt-k+1)%mod;
        tmp=(tmp+f[t^1][j][k]*(i-2*cnt+2*k-j)%mod+f[t^1][j+1][k]*(j-k+1)%mod)%mod;
        f[t][j][k]=(f[t][j][k]+tmp)%mod;
      }
    }
    ++cnt;memset(f[t^1],0,sizeof(f[t^1]));t^=1;
  }
  cout<<f[t^1][0][0]<<endl;
  return 0;
}

F2:

考虑将相同的数作为一个整体,切成多块插入。

定义 \(f_{i,j}\) 为前 \(i\) 组数插入后有 \(j\) 对相同相邻数的方案数。

定义 \(g_{i,j}\) 为 \(i\) 个不同的数分成 \(j\) 组的方案数。

定义 \(s_i\) 为第 \(i\) 组有多少数,\(k\) 为将第 \(i\) 组数分成几部分插入,\(sum_i\) 为 \(\sum\limits_{i=1}^{i}s_i\),\(p\) 为插入 \(k\) 部分后有几对原来相邻相同的数断开了。

\(g\) 的转移方程如下:

\[g_{i,j}=(i-1+j)\cdot g_{i-1,j}+j\cdot g_{i-1,j-1} \]

第一项为将 \(i\) 放到块中的方案数,第二项为将 \(i\) 独立成新的一块的方案数。

\(f\) 的转移方程如下:

\[f_{i,j+s_i-k-p}=\sum\limits_{k=0}^{s_i}\sum\limits_{p=0}^{\min(k,j)}\dbinom{j}{p}\cdot\dbinom{sum_{i-1}+1-j}{k-p}\cdot g_{s_i,k}\cdot f_{i-1,j} \]

复杂度 \(O(n^2\sum{s_i})\),即 \(O(n^3)\)。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300+10,mod=1e9+7;
ll n,m,s[N],sum[N],a[N],f[N][N],g[N][N],c[N][N];
void fen(ll &x){
  for(ll i=2;i*i<=x;++i){
    while(x%(i*i)==0){
      x/=i*i;
    }
  }
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;for(int i=1;i<=n;++i){cin>>a[i];fen(a[i]);}
  sort(a+1,a+n+1);
  for(int i=1;i<=n;++i){
    m+=(a[i]!=a[i-1]);
    ++s[m];
  }
  for(int i=1;i<=m;++i)sum[i]=sum[i-1]+s[i];
  g[0][0]=f[0][0]=c[0][0]=1;
  for(int i=1;i<=n+5;++i){
    c[i][0]=1;
    for(int j=1;j<=i;++j){
      g[i][j]=((i-1+j)*g[i-1][j]%mod+j*g[i-1][j-1]%mod)%mod;
      c[i][j]=(c[i-1][j-1]+c[i-1][j])%mod;
    }
  }
  for(int i=1;i<=m;++i){
    for(int j=0;j<=max(sum[i-1]-1,0LL);++j){
      for(int k=0;k<=s[i];++k){
        for(int p=0;p<=min(k,j);++p){
          f[i][j+s[i]-k-p]=(f[i][j+s[i]-k-p]+c[j][p]*c[sum[i-1]+1-j][k-p]%mod*g[s[i]][k]%mod*f[i-1][j]%mod)%mod;
        }
      }
    }
  }
  cout<<f[m][0]<<endl;
  return 0;
}

F3:

神仙容斥。

我们同样是考虑将相同数字分为一组,对于每一组再分块。

定义 \(s_i\) 为第 \(i\) 组元素个数,\(b_i\) 为分成几块,\(B\) 为 \(\sum\limits_{i=1}^{m}b_i\)。

那么第 \(i\) 组分块排列的方案数为:\(s_i!\cdot\dbinom{s_i-1}{b_i-1}\)。即全排列后插板分块。

那么答案的计算式如下:

\[ans=\sum\limits_{B=1}^{n}[\sum\limits_{i=1}^{m}b_i=B]\left[(-1)^{n-B}\cdot\frac{B!}{\prod\limits_{i=1}^{m}(b_i!)}\cdot \prod\limits_{i=1}^{m}s_i!\cdot\dbinom{s_i-1}{b_i-1}\right] \]

因为 \(n,m,s_i\) 是已知的,我们提到前面整理一下:

\[ans=\left(\prod\limits_{i=1}^{m}s_i\right)\cdot\sum\limits_{B=1}^{n}\left[(-1)^{n-B}\cdot \sum{b}[\sum\limits_{i=1}^{m}b_i=B]\left(\prod\limits_{i=1}^{m}\dbinom{s_i-1}{b_i-1}\cdot\frac{1}{b_i!}\right)\right] \]

容易发现,在 \(B\) 确定的情况下,后面那些是可以预处理的。

定义 \(f_{i,j}=\sum{b}[\sum\limits_{w=1}^{i}b_w=j]\left(\prod\limits_{w=1}^{i}\dbinom{s_w-1}{b_w-1}\cdot\frac{1}{b_w!}\right)\)。

转移可以枚举 \(b_i\) 大小,方程如下:

\[f_{i,j}=\sum\limits_{b_i=1}^{\min(j-i+1,s_i)}\left[f_{i-1,j-b_i}\cdot\dbinom{s_i-1}{b_i-1}\cdot\frac{1}{b_i!}\right] \]

所以最终答案为:

\[ans=\left(\prod\limits_{i=1}^{m}s_i\right)\cdot\left[\sum\limits_{B=m}^{n}B!\cdot (-1)^{n-B}\cdot f_{m,B}\right] \]

预处理复杂度 \(O(n\sum s_i)\),即 \(O(n^2)\)。查询复杂度 \(O(n)\)。

总复杂度 \(O(n^2)\)。

code:

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=300+5,mod=1e9+7;
ll n,m,a[N],s[N],fac[N],inv[N],f[N][N];
void fen(ll &x){for(ll i=2;i*i<=x;++i)while(x%(i*i)==0)x/=i*i;}
ll C(int x,int y){
  if(y>x||y<0)return 0;
  return fac[x]*inv[x-y]%mod*inv[y]%mod;
}
int main(){
  ios::sync_with_stdio(false);cin.tie(0);cout.tie(0);
  cin>>n;for(int i=1;i<=n;++i){cin>>a[i];fen(a[i]);}
  sort(a+1,a+n+1);
  fac[0]=fac[1]=inv[0]=inv[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;
  }
  for(int i=2;i<=n;++i)inv[i]=inv[i-1]*inv[i]%mod;
  for(int i=1;i<=n;++i){
    m+=(a[i]!=a[i-1]);
    ++s[m];
  }
  ll t=1,sum=0,ans=0;
  for(int i=1;i<=m;++i)t=t*fac[s[i]]%mod;
  f[0][0]=1;
  for(int i=1;i<=m;++i){
    sum+=s[i];
    for(int j=1;j<=sum;++j){
      for(int k=1;k<=min((ll)j-i+1,s[i]);++k){
        f[i][j]=(f[i][j]+f[i-1][j-k]*C(s[i]-1,k-1)%mod*inv[k]%mod)%mod;
      }
    }
  }
  for(int i=m;i<=n;++i){
    ll tmp=t*fac[i]%mod*f[m][i]%mod,op=1;
    if((n-i)&1)op=-1;
    ans=(ans+op*tmp+mod)%mod;
  }
  cout<<ans<<endl;
  return 0;
}

标签:right,limits,cdot,题解,sum,Bench,int,CF840C,ll
From: https://www.cnblogs.com/HQJ2007/p/17561317.html

相关文章

  • 题解 P3803 【模板】多项式乘法(FFT)
    感觉题解区不是写的太高深,就是写的太高深。所以给初中、小学和幼儿园的萌新准备一篇简单易懂的良心题解~前置知识一、多项式的系数表示法和点值表示法。\(A(x)=\sum\limits_{i=0}^{n-1}a_i\cdotx^i\)系数:\((a_0,a_1,a_2...a_{n-2},a_{n-1})\)。点值:\((x_0,y_0),(x_1,y_1)...(......
  • 题解 CF41D
    基础DP题。定义\(f_{i,j,k}\)表示从第一行走到\((i,j)\),且数字总和模\(p\)等于\(k\)。转移方程为:\[f_{i+1,j-1,(k+a_{i+1,j-1})\bmodp}=\max(f_{i,j,k}+a_{i+1,j-1})\]\[f_{i+1,j+1,(k+a_{i+1,j+1})\bmodp}=\max(f_{i,j,k}+a_{i+1,j+1})\]同时还需要定义\(g_{i,j......
  • 题解 P6091 【模板】原根
    题解太高深,自己整理一份。阶的定义:若\(\gcd(a,m)=1\),则使\(a^l\equiv1\pmod{m}\)的最小的\(l\)称为\(a\)关于模\(m\)的阶,记为\(\operatorname{ord}_ma\)。阶的性质:根据欧拉定理,\(a^{\varphi(m)}\equiv1\pmod{m}\),所以\(\operatorname{ord}_ma\mid\varphi(m)\)......
  • 题解 CF417D
    \(m\le20\),状压DP。首先可以根据每个人的\(k\)从小到大排序。定义\(f_{i,j}\)表示考虑到第\(i\)个人,完成了\(j\)状态的题目,不考虑显示器所需的最小价格。转移显然为\(f_{i,j|s_i}=\min(f_{i-1,j}+x_i)\)。最终答案为\(ans=\min\limits_{i=1}^{n}f_{i,S}+b\cdotk_......
  • 题解 CF985E
    贪心+DP。先从小到大排序。定义\(f_i\)表示序列\([1,i]\)能否分组。转移为\(f_i|=f_j[a_i-a_j\led,j\lei-k+1]\)。右区间可以直接算出来,左区间可以二分或根据单调性弄个指针。定义\(sum_i=\sum\limits_{t=1}^{i}f_t\),前缀和减一下判断是否为正即可。复杂度\(O(n\lo......
  • 题解 P4815 [CCO2014] 狼人游戏
    看题目限制,可以发现如果将机器人作为点,指控和保护关系作为边,可以建出一个森林,就下来就是传统的树形背包了。设\(f_{i,j,0/1}\)表示当前点为\(i\),子树内有\(j\)个狼人,当前点是否为狼人的方案数。初始化:\(f_{u,0,0}=f_{u,1,1}=1\)当前点为狼:指控:\(f_{u,j,1}=f_{u,j-k,......
  • 题解 [ABC276F] Double Chance
    很容易想到分类。我们可以把\(1\)到\(i-1\)的球分为两类,一类是权值小于\(val_i\),另一类是权值大于\(val_i\)。对于第一类,\(sum\)加上小于\(val_i\)的球的个数乘以\(val_i\)。对于第二类,\(sum\)加上所有大于\(val_i\)的球的权值。这显然可以用两个树状数组维护。......
  • 题解 P5426 [USACO19OPEN]Balancing Inversions G
    来一篇简单易懂的良心题解。由于数值不是\(0\)就是\(1\),我们可以考虑将逆序对的统计方式化简。以左区间为例,设\(x\)为\(1\)的个数,\(p_i\)为第\(i\)个\(1\)的坐标,则逆序对个数为\(\sum\limits_{i=1}^{x}n-p_i-(x-i)\)。也就是\((n-x)\cdotx+\frac{x\cdot(x+1)}{......
  • 题解 Friendly Spiders
    FriendlySpiders带有技巧的最短路。如果\(u\)能到\(v\),说明\(\gcd(u,v)>1\),也就是有相同因子。所以我们考虑对于每个数\(u\),向他的所有质因子连一条长度为\(1\)的边,这样我们从\(u\)到\(v\)需要走两步,最终答案除以\(2\)即可。每次遇到一个新的因子,都要新建节点。......
  • 题解 The Human Equation
    TheHumanEquation思维题。我们考虑每次\(a\)数组加一减一对于其前缀和\(sum\)的影响。可以发现,假设相邻两次加一和减一的位置分别为\(l\)和\(r\),那么\(sum\)在\([l,r)\)内会加一。先减后加也同理。所以,一次加减操作相当于将\(sum\)若干段连续序列加一或减一。......