首页 > 其他分享 >「AGC036F」Square Constraints 题解

「AGC036F」Square Constraints 题解

时间:2022-08-24 19:34:07浏览次数:159  
标签:方案 Square 题解 ll 容斥 le ans 2n Constraints

「AGC036F」Square Constraints 题解

题目大意

给定一个整数 $ n $,求有多少种 $ 0\ -\ 2n!-!1 $ 的排列 $ P $,使得对于每个 $ i $,都有 $ n^2 \le i^2 + P_i^2 \le 4n^2 $。输出答案对给定的 $ m $ 取余的结果

输入

两个整数,$ n \(,\) m $。

输出

一个整数,表示答案。


思路

初始想法

通过 $ n^2 \le i^2 + P_i^2 \le 4n^2 $ 这个式子,我们很容易想到用 $ i^2 + P_i^2 \le 4n^2 $ 的方案数减去 $ i^2 + P_i^2 < n^2 $ 的方案数。但是我们发现实际上不可做。不过这种想法提示我们可以考虑容斥

深入思考

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

  • 对于一个 $ 0\ -\ 2n-1 $ 组成的序列 $ P $,限制 $ a_i \le P_i \le b_i $,求方案数。

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

  • 对于一个 $ 0\ -\ 2n-1 $ 组成的序列 $ P $,限制 $ P_i \le a_i $,求方案数。

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

\[\underset{i=0}{\overset{2n-1}{\prod}}a_i-i \]

证明:

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

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

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

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

\[a_0 \times (a_1-1) \times (a_2-2) \times \dots \times (a_{2n-1}-2n+1)\\ =\underset{i=0}{\overset{2n-1}{\prod}}a_i-i \]

证毕。

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


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

我们尝试设 $ F(i) $ 为满足 $ i^2 + P_i^2 \le 4n^2 $ 的 $ P_i $ 的最大值, $ G(i) $ 为满足 $ i^2 + P_i^2 < n^2 $ 的 $ P_i $ 的最大值。其中 $ i \in [0,2n-1] $。

通过观察我们不难发现:

  1. $ F $ 和 $ G $ 两个函数值随着 $ i $ 的增大而减小。

    $ n^2 $ 和 $ i^2 $ 为定值,$ i $ 增大,则 $ i^2 $ 增大,那么 $ P_i^2 $ 就会减小。

  2. $ G(i) $ 满足 $ i \in [0,n-1] $。

    当 $ i \ge n $ 时,$ i^2 \ge n^2 $,要使 $ i^2 + P_i^2 < n^2 $,则 $ P_i^2 < 0 $,无解。

  3. $ \max_{i=0}^{n-1}(G_i) < \min_{i=0}^{n-1}(F_i) $。

    由于第一条规律:$ \max_{i=0}^{n-1}(G_i) $ 即 $ G_0 \(,\) \min_{i=0}^{n-1}(F_i) $ 即 $ F_{n-1} $,

    $ \because 0^2 + G_0^2 < n^2 , n^2 + F_{n-1}^2 \le 4n^2 $

    $ \therefore G_0^2 < n^2 , F_{n-1}^2 \le 3n^2 $

    $ \because n^2 \le 3n^2 $

    $ \therefore G_0^2 < F_{n-1}^2 \le 3n^2 $

    $ \because G_0 \in \mathbb{N} ,F_{n-1} \in \mathbb{N} $

    $ \therefore G_0 < F_{n-1} $

    $ \therefore \max_{i=0}^{n-1}(G_i) < \min_{i=0}^{n-1}(F_i) $

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

所有的数都满足 $ i^2 + P_i^2 \le 4n^2 $,且至少有 $ k $ 个数满足 $ i^2 + P_i^2 < n^2 $,也就是说在前面 $ n $ 个位置选出 $ k $ 个 $ G(x) $ 。

然后就可以进行容斥了。

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

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

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

  • $ i \in [0,n-1] : (G(i),F(i)) $

  • $ i \in [n,2n-1] : (F(i),0) $

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

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

设 $ f_{i,j} $ 表示在前 $ i $ 个位置上选出了 $ j $ 个 $ G(x) $ 的方案数。

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

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

然后枚举 $ i $,从 $ 0 $ 到 $ 2n-1 $,表示已经确定了前面 $ i $ 个位置;

最后枚举 $ j $,从 $ 0 $ 到 $ k $,表示前面已经选出了 $ j $ 个 $ G(x) $;

状态转移分情况讨论:

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

    • 选择 $ F(i) $:

      • 排名:

      前面选出的 $ j $ 个 $ G(x) $ 一定比 $ F(i) $ 小;

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

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

      • 转移:

      $ f_{i+1,j}=f_{i+1,j}+f_{i,j} \times (F(i)-j-t1) $

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

    • 选择 $ F(i) $:

      • 排名:

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

      我们选出的所有 $ k $ 个 $ G(x) $ 都比 $ F(i) $ 小;

      之前 $ (G(x),F(x)) $ 类型的二元组选出的 $ t2-j $ 个 $ F(x) $ 也都比 $ F(i) $ 小。

      所以总共就有 $ n+k+t2-j $ 个数比 $ F(i) $ 小。

      • 转移:

      $ f_{i+1,j}=f_{i+1,j}+f_{i,j} \times (F(i)-n-k-t2+j) $

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

      • 排名:

      前面选出的 $ t1 $ 个 $ F(x) $ 一定比 $ G(i) $ 小;

      前面选出的 $ j $ 个 $ G(x) $ 也比 $ G(i) $ 小;

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

      • 转移:

      $ f_{i+1,j+1}=f_{i+1,j+1}+f_{i,j} \times (G(i)-t1-j) $

最后综合起来求出 $ f_{2n-1,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;
}

补充知识

容斥原理

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

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

用数学公式可以表示为:

\[\left | \bigcup_{i=1}^{n}a_i \right | = \underset{T \subseteq \{1,2,...,n\}}{\sum}(-1)^{|T|-1} \left | \bigcap_{i \in T} a_i \right | \]

广义容斥原理

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

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

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

二项式反演

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

\[g(k) = \underset{i=0}{\overset{k}{\sum}} C_k^i f(i) \]

则:

\[f(k) = \underset{i=0}{\overset{k}{\sum}} (-1)^{k-i} C_k^i g(i) \]

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


举个例子:

「bzoj2839」集合计数

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

$ 1 \le n \le 10^6 \(,\) 0 \le k \le n $。

由题列出式子:$ C_n^k (2{2{n-k}}-1) $。即钦定 $ k $ 个交集元素,则包含这 $ k $ 个的集合有 $ 2^{n−k} $ 个;每个集合可选可不选,但不能都不选。

设 $ f(i) $ 表示钦定交集元素为至少 $ i $ 个的方案数,$ g(i) $ 表示钦定交集元素恰好为 $ i $ 个的方案数,则有 $ C_n^k (2{2{n-k}}-1) = f(k) = \underset{i = k}{\overset{n}{\sum}} C_i^k g(i) $。

这个时候我们就可以利用二项式反演,求得 $ g(k) = \underset{i = k}{\overset{n}{\sum}} (-1)^{i-k} C_i^k f(i) = \underset{i=k}{\overset{n}{\sum}} (-1)^{i-k} C_i^k C_n^i (2{2{n-i}}-1) $。

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

尾声

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

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

标签:方案,Square,题解,ll,容斥,le,ans,2n,Constraints
From: https://www.cnblogs.com/zsc985246/p/16621307.html

相关文章

  • 「POJ1475」Pushing Boxes 题解
    「POJ1475」PushingBoxes题解题目大意一张N行M列的地图,字符“.”表示空地,字符“#”表示墙,字符“S”表示人的起始位置,字符“B”表示箱子的起始位置,字符“T”表示箱子的......
  • 「COCI2014-2015#2」Norma 题解
    「COCI2014-2015#2」Norma题解题目大意给定一个\(n\)个数的序列\(a\),求\[\underset{i=1}{\overset{n}{\sum}}\underset{j=i}{\overset{n}{\sum}}(j-i+1)\min(a_i,a_......
  • 题解:【TJOI2009】宝藏
    【TJOI2009】宝藏题目链接看到走地图问题,自然联想到广搜,这个题前两篇题解讲的很清楚了,要带着机关状态走。最多只有十个机关,考虑状压。但是大佬们的装压我都看不懂捏,特意......
  • 题解:【SWTR-8】15B03
    题解:【SWTR-8】15B03题目链接前言本篇题解大量配图!作为一道非常好的有思维深度的题,必须写篇题解记录一下。谨以此篇献给我的第一道构造题。第一问(80pts)求需要撤去......
  • 题解:「GLR-R3」雨水
    题解:「GLR-R3」雨水题目链接前言先吐槽一下,这个英文是真的坑。constintMAXN=712;//Setarightvalueaccordingtoyoursolution.为啥不能直接把数组下标设为......
  • 题解:【TJOI2012】防御
    【TJOI2012】防御题目链接小清新数据结构题,题解区为啥清一色两棵线段树。考虑分块,维护两个数组:$tag$和$minx$分别记录整块的累计伤害和当前护盾最小值。当发现有护盾......
  • 题解:【THUSCH2017】 大魔法师
    【THUSCH2017】大魔法师题目链接前言线段树和矩阵乘法的板子拼接题,这个题题目本身思维难度不大,但是可以给我们提供许多平时写代码的底层优化技巧。题目思路首先回到......
  • 题解:【POI2001】Goldmine
    【POI2001】Goldmine题目链接扫描线板子题,本质上这个题跟窗口的星星是一样的,只不过把权值$k$都换成$1$就行。但是需要注意的是这句话:(若矿石位于这块地的边缘,我们同......
  • 数的划分 题解
    \(0.\)写在前面1.3【例题1】数的划分-TuringEDUP2706数的划分-TopsCoding这题可以有两种写法:(至少两种)深搜计数\(\text{DP}\)接下来将会依次讲解\(1.\)......
  • 题解:【WC2005】双面棋盘
    【WC2005】双面棋盘题目链接这天做双面棋盘这道题,发现题解里面大多都是LCT,对于线段树套并查集的写法思路讲评很少而且不大清晰,因此有了这一篇题解。维护联通块的数量,......