Codeforces 题目传送门 & 洛谷题目传送门

这是一道 *2500 的 D1C,可个人认为难度堪比某些 *2700 *2800。

不过嘛,*2500 终究还是 *2500,还是被我自己想出来了

双倍经验 P4448 [AHOI2018初中组]球球的排列 哦

u1s1 其实一年以前做过 P4448,不过好像直到我 AC 这道题之后才发现这俩题一模一样,并且似乎这次用的方法和上次还不太一样

跑题了跑题了

首先有个性质:\(\forall a,b,c\in\mathbb{N}^+\) 若 \(ab,bc\) 均为完全平方数,\(ac\) 也是完全平方数,因为假设 \(ab=x^2,bc=y^2\),则 \(ac=\dfrac{a^2b^2c^2}{ab·bc}=(\dfrac{abc}{xy})^2\),也就是说 \(ac\) 是一个有理数的平方,而又显然 \(ac\in\mathbb{N}^+\),故 \(ac\) 为完全平方数。

考虑 \(\forall i,j\in[1,n],i\ne j\),若 \(a_ia_j\) 为完全平方数,就在 \(i,j\) 之间连一条边,那么根据之前的推论,若 \((i,j),(j,k)\) 之间均存在边,那么 \((i,k)\) 间也存在边,也就是说得到的图是一个个团的并集

考虑将同一个团中的点染上同一个颜色,于是原题等价于给出 \(k\) 种颜色,第 \(i\) 种颜色的球有 \(c_i\) 个(每种颜色的球互不相同),其中 \(\sum\limits_{i=1}^kc_i=n\),求将这 \(n\) 个球排成一行,其中相邻两个球颜色不同的方案数。

考虑 \(dp\),\(dp_{i,j}\) 表示将前 \(i\) 种颜色的球排成一行,其中有 \(j\) 个相邻位置颜色相同的方案数。

考虑从 \(dp_i\) 转移到 \(dp_{i+1}\),我们枚举第 \(i+1\) 种颜色分成了多少组,设为 \(x\),再枚举这 \(x\) 组中有多少组摆在了原本颜色相同的两球之间,设为 \(y\),那么这样一来会少掉 \(y\) 个颜色相同的相邻位置,但同时由于 \(i+1\) 种颜色分为了 \(x\) 组,又会多出 \(c_{i+1}-x\) 对颜色相同的相邻位置。算下这样划分的方案数,设 \(s_{i,j}\) 表示将 \(i\) 个不同的球划分成 \(j\) 组,组内有序但组与组之间无序的方案数,那么将第 \(i+1\) 种颜色的球划分为 \(x\) 组的方案数为 \(s_{c_{i+1},x}\),从 \(x\) 组中选出 \(y\) 组的方案数为 \(\dbinom{x}{y}\),这 \(y\) 组进一步“消灭”颜色相同的位置的方案数为 \(k^{\underline{y}}\),将剩余的 \(x-y\) 组填入剩余的 \((\sum\limits_{t=1}^ic_t)+1\) 个空隙中的方案数为 \(((\sum\limits_{t=1}^ic_t)+1)^{\underline{x-y}}\),故我们有状态转移方程式 \(dp_{i,j}\times s_{c_{i+1},x}\times \dbinom{x}{y}\times k^{\underline{y}}\times ((\sum\limits_{t=1}^ic_t)+1)^{\underline{x-y}}\rightarrow dp_{i+1}{j-y+c_{i+1}-x}\)。最终答案即为 \(dp_{k,0}\)。

最后考虑怎样求 \(s_{i,j}\),考虑最后一个球的摆法,若它单独成一组,则 \(dp_{i,j}\leftarrow dp_{i-1,j-1}\),若它与前面的求合并为一组,则它有 \(i-1+j\) 个空隙可以插入,\(dp_{i,j}\leftarrow dp_{i-1,j}\times(i+1-j)\)。

算下时间复杂度,乍一看四重循环,\(n^4\),但实际上 \(\sum\limits_{i=1}^kc_i=n\),故复杂度是 \(n^3\) 的,可以通过此题。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=300;
const int MOD=1e9+7;
int n,a[MAXN+5],f[MAXN+5];
bool issqr(ll x){int _sqrt=sqrt(x);return (1ll*_sqrt*_sqrt==x);}
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
void merge(int x,int y){x=find(x);y=find(y);if(x^y) f[x]=y;}
int siz[MAXN+5],b[MAXN+5],bn=0,s[MAXN+5][MAXN+5];
int dp[MAXN+5][MAXN+5],fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
fac[0]=ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
s[0][0]=1;
for(int i=1;i<=n;i++) for(int j=1;j<=i;j++){
s[i][j]=(s[i-1][j-1]+1ll*(i-1+j)*s[i-1][j])%MOD;
}
}
int binom(int x,int y){
if(x<0||y<0||x<y) return 0;
return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;
}
int main(){
scanf("%d",&n);init_fac(n+1);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(issqr(1ll*a[i]*a[j]))
merge(i,j);
for(int i=1;i<=n;i++) siz[find(i)]++;
for(int i=1;i<=n;i++) if(find(i)==i) b[++bn]=siz[i];
// for(int i=1;i<=bn;i++) printf("%d\n",b[i]);
dp[0][0]=1;int sum=0;
for(int i=0;i<bn;i++){
for(int j=0;j<=sum-i;j++){
for(int k=1;k<=b[i+1];k++){
int ways=s[b[i+1]][k];
for(int l=0;l<=min(j,k);l++){
int ways2=1ll*binom(k,l)*fac[l]%MOD*binom(j,l)%MOD;
ways2=1ll*ways2*binom(sum+1-j,k-l)%MOD*fac[k-l]%MOD;
ways2=1ll*ways2*ways%MOD;ways2=1ll*dp[i][j]*ways2%MOD;
// printf("%d %d %d %d %d\n",i,j,k,l,ways2);
dp[i+1][j-l+b[i+1]-k]=(dp[i+1][j-l+b[i+1]-k]+ways2)%MOD;
}
}
}
sum+=b[i+1];
}
// for(int i=1;i<=bn;i++) for(int j=0;j<=n;j++) printf("%d %d %d\n",i,j,dp[i][j]);
printf("%d\n",dp[bn][0]);
return 0;
}

事实上本题还有更优秀的做法

下面就是我没想到的部分了

我们考虑将每种颜色定义一个 \(b_i\) 表示将这种颜色的球强制分为 \(b_i\) 组并要求同一组的球必须绑在一起。

考虑对于固定的 \(b_1,b_2,\dots,b_k\) 怎样计算这样填的方案数,首先对于每种颜色,将其分为 \(b_i\) 组的方案数为 \(c_i!\dbinom{c_i-1}{b_i-1}\times\),其次将这捆绑后的 \(B=\sum\limits_{i=1}^kb_i\) 组排列起来的方案数为 \(\dfrac{B!}{\prod\limits_{i=1}^kb_i!}\),故总方案数为 \(\prod\limits_{i=1}^k(c_i!\dbinom{c_i-1}{b_i-1})·\dfrac{B!}{\prod\limits_{i=1}^kb_i!}=B!\prod\limits_{i=1}^kc_i!·\prod\limits_{i=1}^k\dbinom{c_i-1}{b_i-1}\dfrac{1}{b_i!}\),我们考虑枚举 \(B\),那显然前两项都是常数,只需考虑第二个 \(\prod\) 中的东西就行了。

还是考虑 \(dp\),\(dp_{i,j}\) 表示考虑到前 \(i\) 个球,它们的 \(b_i\) 加起来等于 \(j\) 的 \(\prod\limits_{i=1}^k\dbinom{c_i-1}{b_i-1}\dfrac{1}{b_i!}\) 的和。

转移就枚举 \(b_i=l\),那么显然有 \(dp_{i,j}=\sum dp_{i-1,j-l}\dbinom{c_i-1}{l-1}\dfrac{1}{l!}\)。

那么最终 \(\sum\limits_{i=1}^k=B\) 的答案即为 \(dp_{k,B}\)。

但是这样计算有个问题就是会重复计算,比方说排列方式为 1 1 2 2,那么它在 \(dp_{2,3},dp_{2,4}\) 中都会被计算,不过这个问题很容易解决,直接容斥原理一下就行了,故最终答案为 \(\sum\limits_{i=k}^ndp_{k,B}\times(-1)^{n-i}\)。

#include <bits/stdc++.h>
using namespace std;
#define fi first
#define se second
#define fill0(a) memset(a,0,sizeof(a))
#define fill1(a) memset(a,-1,sizeof(a))
#define fillbig(a) memset(a,63,sizeof(a))
#define pb push_back
#define ppb pop_back
#define mp make_pair
template<typename T1,typename T2> void chkmin(T1 &x,T2 y){if(x>y) x=y;}
template<typename T1,typename T2> void chkmax(T1 &x,T2 y){if(x<y) x=y;}
typedef pair<int,int> pii;
typedef long long ll;
typedef unsigned int u32;
typedef unsigned long long u64;
namespace fastio{
#define FILE_SIZE 1<<23
char rbuf[FILE_SIZE],*p1=rbuf,*p2=rbuf,wbuf[FILE_SIZE],*p3=wbuf;
inline char getc(){return p1==p2&&(p2=(p1=rbuf)+fread(rbuf,1,FILE_SIZE,stdin),p1==p2)?-1:*p1++;}
inline void putc(char x){(*p3++=x);}
template<typename T> void read(T &x){
x=0;char c=getchar();T neg=0;
while(!isdigit(c)) neg|=!(c^'-'),c=getchar();
while(isdigit(c)) x=(x<<3)+(x<<1)+(c^48),c=getchar();
if(neg) x=(~x)+1;
}
template<typename T> void recursive_print(T x){if(!x) return;recursive_print(x/10);putc(x%10^48);}
template<typename T> void print(T x){if(!x) putc('0');if(x<0) putc('-'),x=~x+1;recursive_print(x);}
void print_final(){fwrite(wbuf,1,p3-wbuf,stdout);}
}
const int MAXN=300;
const int MOD=1e9+7;
int n,a[MAXN+5],f[MAXN+5];
bool issqr(ll x){int _sqrt=sqrt(x);return (1ll*_sqrt*_sqrt==x);}
int find(int x){return (!f[x])?x:f[x]=find(f[x]);}
void merge(int x,int y){x=find(x);y=find(y);if(x^y) f[x]=y;}
int siz[MAXN+5],b[MAXN+5],bn=0;
int dp[MAXN+5][MAXN+5],fac[MAXN+5],ifac[MAXN+5];
void init_fac(int n){
fac[0]=ifac[0]=ifac[1]=1;
for(int i=2;i<=n;i++) ifac[i]=1ll*ifac[MOD%i]*(MOD-MOD/i)%MOD;
for(int i=1;i<=n;i++) fac[i]=1ll*fac[i-1]*i%MOD;
for(int i=1;i<=n;i++) ifac[i]=1ll*ifac[i]*ifac[i-1]%MOD;
}
int binom(int x,int y){
if(x<0||y<0||x<y) return 0;
return 1ll*fac[x]*ifac[x-y]%MOD*ifac[y]%MOD;
}
int main(){
scanf("%d",&n);init_fac(n);
for(int i=1;i<=n;i++) scanf("%d",&a[i]);
for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(issqr(1ll*a[i]*a[j]))
merge(i,j);
for(int i=1;i<=n;i++) siz[find(i)]++;
for(int i=1;i<=n;i++) if(find(i)==i) b[++bn]=siz[i];
dp[0][0]=1;
for(int i=1;i<=bn;i++) for(int j=1;j<=n;j++) for(int k=1;k<=min(j,b[i]);k++){
dp[i][j]=(dp[i][j]+1ll*dp[i-1][j-k]*binom(b[i]-1,k-1)%MOD*ifac[k]%MOD)%MOD;
} int ans=0,mul=1;for(int i=1;i<=bn;i++) mul=1ll*mul*fac[b[i]]%MOD;
for(int i=bn;i<=n;i++){
if((n-i)&1) ans=(ans-1ll*dp[bn][i]*fac[i]%MOD*mul%MOD+MOD)%MOD;
else ans=(ans+1ll*dp[bn][i]*fac[i]%MOD*mul%MOD)%MOD;
} printf("%d\n",ans);
return 0;
}

Codeforces 840C - On the Bench(dp/容斥原理)的更多相关文章

  1. Codeforces 840C On the Bench dp

    On the Bench 两个数如果所有质因子的奇偶性相同则是同一个数,问题就变成了给你n个数, 相同数字不能相邻的方案数. dp[ i ][ j ]表示前 i 种数字已经处理完, 还有 j 个位置需 ...

  2. CodeForces 840C - On the Bench | Codeforces Round #429 (Div. 1)

    思路来自FXXL中的某个链接 /* CodeForces 840C - On the Bench [ DP ] | Codeforces Round #429 (Div. 1) 题意: 给出一个数组, ...

  3. codeforces 429 On the Bench dp+排列组合 限制相邻元素,求合法序列数。

    限制相邻元素,求合法序列数. /** 题目:On the Bench 链接:http://codeforces.com/problemset/problem/840/C 题意:求相邻的元素相乘不为平方 ...

  4. Codeforces 840C. On the Bench 动态规划 排列组合

    原文链接https://www.cnblogs.com/zhouzhendong/p/CF840C.html 题解 首先,我们可以发现,如果把每一个数的平方因子都除掉,那么剩下的数,不相等的数都可以相 ...

  5. 【uoj#37/bzoj3812】[清华集训2014]主旋律 状压dp+容斥原理

    题目描述 求一张有向图的强连通生成子图的数目对 $10^9+7$ 取模的结果. 题解 状压dp+容斥原理 设 $f[i]$ 表示点集 $i$ 强连通生成子图的数目,容易想到使用总方案数 $2^{sum ...

  6. 【bzoj2560】串珠子 状压dp+容斥原理

    题目描述 有 $n$ 个点,点 $i$ 和点 $j$ 之间可以连 $0\sim c_{i,j}$ 条无向边.求连成一张无向连通图的方案数模 $10^9+7$ .两个方案不同,当且仅当:存在点对 $(i ...

  7. 【bzoj2339】[HNOI2011]卡农 dp+容斥原理

    题目描述 题解 dp+容斥原理 先考虑有序数列的个数,然后除以$m!$即为集合的个数. 设$f[i]$表示选出$i$个集合作为满足条件的有序数列的方案数. 直接求$f[i]$较为困难,考虑容斥,满足条 ...

  8. 洛谷 P2986 [USACO10MAR]Great Cow Gat…(树形dp+容斥原理)

    P2986 [USACO10MAR]伟大的奶牛聚集Great Cow Gat… 题目描述 Bessie is planning the annual Great Cow Gathering for c ...

  9. [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆)

    [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆) 题面 一棵二叉树的所有点的点权都是给定的集合中的一个数. 让你求出1到m中所有权 ...

随机推荐

  1. 软件工程个人博客作业-软件案例分析:VS与VS Code

    项目 内容 本作业属于北航 2020 年春软件工程 博客园班级连接 本作业是本课程个人项目作业 作业要求 我在这个课程的目标是 提高软件开发能力.团队协作能力 这个作业在哪个具体方面帮助我实现目标 提 ...

  2. Noip模拟66 2021.10.2

    T1 接力比赛 思路就是直接做背包$dp$,然后看看容量相同的相加的最大值. 考虑如何在$dp$过程中进行优化 注意到转移方程的第二维枚举容量没有必要从容量总和开始枚举 那么我们便转移边统计前缀和,从 ...

  3. CF375D Tree and Queries 题解

    感觉CF的题目名都好朴素的样子 你谷链接 首先这题显然是个dsu on tree 但是我不会. 其次这题显然是个莫队.这我会啊! 然后会发现好像不是很对劲.因为每次询问都有一个k,貌似和传统的莫队数颜 ...

  4. 零基础学习C语言入门必备知识

    今天跟大家一起从零学C语言: 1. C语言简介 1.1 C语言发展史 C语言是一种广泛使用的面向过程的计算机程序设计语言,既适合于系统程序设计,又适合于应用程序设计.C语言的发展历程大致如图1-1所示 ...

  5. STM32定时器学习---基本定时器

    STM32F1系列的产品,除了互联网产品外,工作8个,3种定时器,其中一种就是基本定时器.那么STM32单片机的基本定时器如何操作以及编程呢? 下面我们就来详细的了解一下 STM32F1系列的产品,除 ...

  6. Pod 健康检查和服务可用性检查

    Kubernetes 对 Pod 的健康状态可以通过两类探针来检查:LivenessProbe 和 ReadinessProbe,kubelet 定期执行这两类探针来针对容器的健康状况. Livene ...

  7. PHP查看内存占用

    function test(){ echo memory_get_usage(), '<br>'; $start = memory_get_usage(); $a = []; for ($ ...

  8. Code Runner for VS Code,下载量突破 3000 万!

    还记得五年前的夏天,我在巨硬写着世界上最好的语言,有时也需要带着游标卡尺写着另一门语言.然而,我对这两门语言都不熟悉,如果能在 VS Code 中方便快捷地运行各种语言,那岂不是很方便?于是,我就开发 ...

  9. Redis 专栏(使用介绍、源码分析、常见问题...)

    一.介绍相关 说Redis : 介绍Redis特性,使用场景,使用Jedis操作Redis等. 二.源码分析 1. 数据结构 Redis源码分析(sds):Redis自己封装的C语言字符串类型. Re ...

  10. 西邮Linux兴趣小组第一次技术分享会

    2016年10月30日晚,西邮Linux兴趣小组技术分享会在西安邮电大学长安校区东区逸夫教学楼FF305室成功举办.200多名来自全校不同专业的15,16级同学参加了此次分享会. 分享会于20:00正 ...