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中所有权 ...
随机推荐
- 浏览器有别_HTTP报文的回车换行
本来以为浏览器HTTP报文的生成应该是完全一致的.但最近在做一个项目的时候,发现Safari和Chrome提交同一份表单,后端的处理结果不一致.看提交结果呢,是因为Safari多了个回车.由于原项目的 ...
- SyntaxError: Non-UTF-8 code starting with '\xbb' in file D:\流畅学python\ex32.py on line 1, but no encoding declared; see http://python.org/dev/peps/pep-0263/ for details
1. 报错如下: SyntaxError: Non-UTF-8 code starting with '\xd3' in file D:\流畅学python\ex34.py on line 4, bu ...
- javascript-jquery介绍
jquery优势 1.轻量级 2.强大的选择器 3.出色的DOM封装 4.可靠的事件处理机制 5.完善的Ajax 6.不污染顶级变量 7.出色的浏览器兼容 8.链式操作方式 9.隐式迭代 10.行为层 ...
- python的虚拟环境Anaconda使用
Anaconda 使用conda常用命令 1.首先在所在系统中安装Anaconda.可以打开命令行输入conda -V检验是否安装以及当前conda的版本. 2.conda常用的命令. 1)con ...
- BUAA软件工程个人作业-软件案例分析
BUAA个人博客作业-软件案例分析 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 软件案例分析 我在这个课程的目标是 学习开发软件的能力 这个作 ...
- BUAA2020软工作业(一)——谈谈我和计算机的缘分
项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 第一次作业-热身! 我在这个课程的目标是 进一步提高自己的编码能力,工程能力 这个作业在哪个具体方 ...
- (数据科学学习手札129)geopandas 0.10版本重要新特性一览
本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 就在前不久,我们非常熟悉的Python地理 ...
- Noip模拟12 2021.7.12
T1 interval 亏得昨天晚上改掉了T3并且理解了单调栈,今天一扫这题目就知道要用啥了. 先预处理出以a[i]为最大值的最大左右区间.然后再将a[i]取%!!!是的,要不然会影响单调栈的使用.. ...
- 【做题记录】DP 杂题
P2577 [ZJOI2004]午餐 $\texttt{solution}$ 想到贪心: 吃饭慢的先打饭节约时间, 所以先将人按吃饭时间从大到小排序. 状态: \(f[i][j]\) 表示前 \(i\ ...
- web性能检测工具lighthouse
About Automated auditing, performance metrics, and best practices for the web. Lighthouse 可以自动检查Web页 ...