「AGC036F」Square Constraints 题解

本文网址:https://www.cnblogs.com/zsc985246/p/16621307.html ,转载请注明出处。

2023/5/11update:修 LATEX

传送门

「AGC036F」Square Constraints

题目大意

给定一个整数 n,求有多少种 0  2n1 的排列 P,使得对于每个 i,都有 n2i2+Pi24n2输出答案对给定的 m 取余的结果

思路

通过 n2i2+Pi24n2 这个式子,我们很容易想到用 i2+Pi24n2 的方案数减去 i2+Pi2<n2 的方案数。但是我们发现实际上不可做。不过这种想法提示我们可以考虑容斥

首先,我们提炼一下问题模型:

  • 对于一个 0  2n1 组成的序列 P,限制 aiPibi,求方案数。

我们考虑这个问题的弱化版:

  • 对于一个 0  2n1 组成的序列 P,限制 Piai,求方案数。

我们考虑将 a 数组从小到大排序,那么这个弱化版的答案为:

2n1i=0aii

证明:

首先将 a 数组从小到大排序。

对于这个排列 P,我们肯定先从 a 小的开始放。所以第一步的方案数为 a0

再考虑下一个数 1。此时剩下 2n1 个数。因为 a 从小到大,所以上一轮选走的数一定小于等于 a1。此时的方案数为 a11

因为这些步骤是递进的,所以方案数是每一步的方案数的乘积。最终方案数即为:

a0×(a11)×(a22)××(a2n12n+1)=2n1i=0aii

证毕。

而原问题模型多了一个限制条件,不难想到将满足两个条件的最大值都设出来,综合在一起,套用公式得到答案


回到本题,我们将满足条件的最大值设出来。

我们尝试设 F(i) 为满足 i2+Pi24n2Pi 的最大值, G(i) 为满足 i2+Pi2<n2Pi 的最大值。其中 i[0,2n1]

通过观察我们不难发现:

  1. FG 两个函数值随着 i 的增大而减小。

    n2i2 为定值,i 增大,则 i2 增大,那么 Pi2 就会减小。

  2. G(i) 满足 i[0,n1]

    in 时,i2n2,要使 i2+Pi2<n2,则 Pi2<0,无解。

  3. maxi=0n1(Gi)<mini=0n1(Fi)

    由于第一条规律:maxi=0n1(Gi)G0 , mini=0n1(Fi)Fn1

    02+G02<n2,n2+Fn124n2

    G02<n2,Fn123n2

    n23n2

    G02<Fn123n2

    G0N,Fn1N

    G0<Fn1

    maxi=0n1(Gi)<mini=0n1(Fi)

有了这些东西,我们就可以将限制拆分为:

所有的数都满足 i2+Pi24n2,且至少有 k 个数满足 i2+Pi2<n2,也就是说在前面 n 个位置选出 kG(x)

然后就可以进行容斥了。

什么?你不会容斥?你可以翻到最下面的补充知识

在弱化版中,我们将 a 数组从小到大进行了排序。

所以我们类比一下,考虑将 FG 两个函数打包成二元组排序。由于第三条规律,我们通过如下方式打包成二元组:

  • i[0,n1]:(G(i),F(i))

  • i[n,2n1]:(F(i),0)

接下来按第一关键字进行排序。

接下来就是求解方案数。我们可以使用 DP 求解。

fi,j 表示在前 i 个位置上选出了 jG(x) 的方案数。

在状态转移时需要用到当前数在选出的位置上的排名,所以用两个变量 t1t2,分别统计前面 (F(x),0)(G(x),F(x)) 的个数。

首先枚举 k,从 0n,表示在前面 n 个位置选出 kG(x)

然后枚举 i,从 02n1,表示已经确定了前面 i 个位置;

最后枚举 j,从 0k,表示前面已经选出了 jG(x)

状态转移分情况讨论:

  • 当前二元组是 (F(i),0)

    • 选择 F(i)

      • 排名:

      前面选出的 jG(x) 一定比 F(i) 小;

      又因为排了序,前面的 t1F(x) 肯定也比 F(i) 小。

      所以总共有 j+t1 个数比 F(i) 小。

      • 转移:

      fi+1,j=fi+1,j+fi,j×(F(i)jt1)

  • 当前二元组是 (G(i),F(i))

    • 选择 F(i)

      • 排名:

      由于函数单调递减,所以 n 个二元组 (F(x),0) (x[n,2n1]) 相对 F(i) 一定排在前面;

      我们选出的所有 kG(x) 都比 F(i) 小;

      之前 (G(x),F(x)) 类型的二元组选出的 t2jF(x) 也都比 F(i) 小。

      所以总共就有 n+k+t2j 个数比 F(i) 小。

      • 转移:

      fi+1,j=fi+1,j+fi,j×(F(i)nkt2+j)

    • 选择 G(i):(注意只能选 k 个)

      • 排名:

      前面选出的 t1F(x) 一定比 G(i) 小;

      前面选出的 jG(x) 也比 G(i) 小;

      总共就有 t1+j 个数比 G(i) 小。

      • 转移:

      fi+1,j+1=fi+1,j+1+fi,j×(G(i)t1j)

最后综合起来求出 f2n1,k,容斥即可。

代码实现

#include<bits/stdc++.h>
#define ll long long
const ll N=501;
using namespace std;

ll n,p;
ll f[N][N];
vector<pair<ll,ll>>t;//记录二元组 

ll F(ll i){//F函数 
	ll ans=2*n-1;
	while(ans>=0&&i*i+ans*ans>4*n*n)ans--;//注意是大于 
	return ans+1;
}

ll G(ll i){//G函数 
	ll ans=2*n-1;
	while(ans>=0&&i*i+ans*ans>=n*n)ans--;//注意是大于等于 
	return ans+1;
}

int main(){
	
	scanf("%lld%lld",&n,&p);
	for(ll i=0;i<2*n;i++){//预处理二元组 
		if(i<n)t.push_back({G(i),F(i)});
		else t.push_back({F(i),0});
	}
	sort(t.begin(),t.end());//对二元组按第一关键字排序 
	ll ans=0;
	for(ll k=0;k<=n;k++){
		//初始化 
		memset(f,0,sizeof(f));
		f[0][0]=1;
		
		ll t1=0,t2=0;//辅助统计变量 
		for(ll i=0;i<t.size();i++){
			for(ll j=0;j<=k;j++){
				if(!t[i].second){//(F(i),0) 
					f[i+1][j]=(f[i+1][j]+f[i][j]*(t[i].first-j-t1)%p)%p;//选F(i) 
				}else{//(G(i),F(i)) 
					if(j<k){//选了之后不能超过k个 
						f[i+1][j+1]=(f[i+1][j+1]+f[i][j]*(t[i].first-j-t1)%p)%p;//选G(i) 
					}
					f[i+1][j]=(f[i+1][j]+f[i][j]*(t[i].second-n-k-t2+j)%p)%p;//选F(i) 
				}
			}
			if(!t[i].second)t1++;
			else t2++;
		}
		//容斥 
		if(k%2)ans=(ans-f[t.size()][k]+p)%p;
		else ans=(ans+f[t.size()][k])%p;
	}
	printf("%lld",ans);
	
	return 0;
}

补充知识

容斥原理

容斥原理是一种组合数学常用的方法。

比如说,你要计算几个集合并集的大小,我们可以先将所有单个集合的大小加起来,然后减去所有两个集合相交的部分,再加回所有三个集合相交的部分,再减去所有四个集合相交的部分……依此类推,最终你就可以得出答案。

用数学公式可以表示为:

|i=1nai|=T{1,2,...,n}(1)|T|1|iTai|

广义容斥原理

容斥原理求的是不满足任何性质的方案数,我们通过计算所有至少满足 k 个性质的方案数之和来计算。

同样的,我们可以通过计算所有至少满足 k 个性质的方案数之和来计算恰好满足 k 个性质的方案数。这样的容斥方法我们称之为广义容斥原理。

一般我们会用二项式反演进行计算。

二项式反演

对于函数 f(k)g(k),满足:

g(k)=ki=0Ckif(i)

则:

f(k)=ki=0(1)kiCkig(i)

它一般用于"恰好"和"至少""至多"的转换式中


举个例子:

「bzoj2839」集合计数

一个有 n 个元素的集合有 2n 个不同子集(包含空集),现在要在这 2n 个集合中取出至少一个集合,使得它们的交集的元素个数为 k ,求取法的方案数模 109+7

1n106,0kn

由题列出式子:Cnk(22nk1)。即钦定 k 个交集元素,则包含这 k 个的集合有 2nk 个;每个集合可选可不选,但不能都不选。

f(i) 表示钦定交集元素为至少 i 个的方案数,g(i) 表示钦定交集元素恰好i 个的方案数,则有 Cnk(22nk1)=f(k)=ni=kCikg(i)

这个时候我们就可以利用二项式反演,求得 g(k)=ni=k(1)ikCikf(i)=ni=k(1)ikCikCni(22ni1)

这样就可以 O(n) 求出答案。

尾声

如果你发现了问题,你可以直接回复这篇题解

如果你有更好的想法,也可以直接回复!

posted @   zsc985246  阅读(215)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
点击右上角即可分享
微信分享提示