Codeforces 840C - On the Bench(dp/容斥原理)
这是一道 *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/容斥原理)的更多相关文章
- Codeforces 840C On the Bench dp
On the Bench 两个数如果所有质因子的奇偶性相同则是同一个数,问题就变成了给你n个数, 相同数字不能相邻的方案数. dp[ i ][ j ]表示前 i 种数字已经处理完, 还有 j 个位置需 ...
- CodeForces 840C - On the Bench | Codeforces Round #429 (Div. 1)
思路来自FXXL中的某个链接 /* CodeForces 840C - On the Bench [ DP ] | Codeforces Round #429 (Div. 1) 题意: 给出一个数组, ...
- codeforces 429 On the Bench dp+排列组合 限制相邻元素,求合法序列数。
限制相邻元素,求合法序列数. /** 题目:On the Bench 链接:http://codeforces.com/problemset/problem/840/C 题意:求相邻的元素相乘不为平方 ...
- Codeforces 840C. On the Bench 动态规划 排列组合
原文链接https://www.cnblogs.com/zhouzhendong/p/CF840C.html 题解 首先,我们可以发现,如果把每一个数的平方因子都除掉,那么剩下的数,不相等的数都可以相 ...
- 【uoj#37/bzoj3812】[清华集训2014]主旋律 状压dp+容斥原理
题目描述 求一张有向图的强连通生成子图的数目对 $10^9+7$ 取模的结果. 题解 状压dp+容斥原理 设 $f[i]$ 表示点集 $i$ 强连通生成子图的数目,容易想到使用总方案数 $2^{sum ...
- 【bzoj2560】串珠子 状压dp+容斥原理
题目描述 有 $n$ 个点,点 $i$ 和点 $j$ 之间可以连 $0\sim c_{i,j}$ 条无向边.求连成一张无向连通图的方案数模 $10^9+7$ .两个方案不同,当且仅当:存在点对 $(i ...
- 【bzoj2339】[HNOI2011]卡农 dp+容斥原理
题目描述 题解 dp+容斥原理 先考虑有序数列的个数,然后除以$m!$即为集合的个数. 设$f[i]$表示选出$i$个集合作为满足条件的有序数列的方案数. 直接求$f[i]$较为困难,考虑容斥,满足条 ...
- 洛谷 P2986 [USACO10MAR]Great Cow Gat…(树形dp+容斥原理)
P2986 [USACO10MAR]伟大的奶牛聚集Great Cow Gat… 题目描述 Bessie is planning the annual Great Cow Gathering for c ...
- [BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆)
[BZOJ 3625] [Codeforces 438E] 小朋友的二叉树 (DP+生成函数+多项式开根+多项式求逆) 题面 一棵二叉树的所有点的点权都是给定的集合中的一个数. 让你求出1到m中所有权 ...
随机推荐
- JVM详解(六)——对象的实例化、内存布局与访问定位
一.对象的实例化 1.创建对象的方式 2.创建对象的步骤 脑图:https://www.processon.com/view/link/61701a927d9c087040525226 3.对象属性赋 ...
- 轻量级 Java 基础开发框架,Solon & Solon Cloud 1.5.52 发布
Solon 已有120个生态扩展插件,此次更新主要为细节打磨: 插件 mybatis-solon-plugin 增加 mappers 单行配置支持 之前的多行模式: mybatis.db1: type ...
- (二)、Docker 快速入门
文档:https://docs.docker.com/install/linux/docker-ce/centos/ 中文文档:https://docs.docker-cn.com/engine/in ...
- 它说你的代码有 Bug「GitHub 热点速览 v.21.44」
作者:HelloGitHub-小鱼干 本周热点上的榜单大多数提升工作效率的实用工具,像是一个 API 管理所有通知消息(包括推送.邮件-)的 notifire,再是高速解析 JSON 文件的 simd ...
- Linux调整时区和同步时间
1.调整时区 tzselect 选择Asia -> China -> Beijing Time 2.设置为默认时区 cp -f /usr/share/zoneinfo/Asia/Shang ...
- 这一篇 K8S(Kubernetes)集群部署 我觉得还可以!!!
点赞再看,养成习惯,微信搜索[牧小农]关注我获取更多资讯,风里雨里,小农等你,很高兴能够成为你的朋友. 国内安装K8S的四种途径 Kubernetes 的安装其实并不复杂,因为Kubernetes 属 ...
- Linux ns 4. UTS Namespace 详解
目录 1. 使用简介 1.1 hostname 1.2 domainname 1.3 uname 2. 代码分析 2.1 copy_utsname() 2.2 sethostname() 2.3 ge ...
- 持续集成、持续交付(CI/CD)开篇,先来唠唠嗑
前言 现在稍微有点规模的系统,很多都是采用分布式/微服务架构,将一个大系统拆分为很多个功能模块进行开发.测试.发布.管理等,如果全部流程都采用人工的形式进行的话,效率肯定是超级不高效滴.而且现在很多项 ...
- Django笔记&教程 2-2 URL详细匹配规则
Django 自学笔记兼学习教程第2章第2节--URL详细匹配规则 点击查看教程总目录 本章第一节中我们简单介绍了URL与View关系 简单概括来说,网页请求的url会通过urls.py里面的urlp ...
- psutil模块详解
import psutil#1.系统性能信息模块psutilmem = psutil.virtual_memory()print(mem)#svmem(total=8442675200, availa ...