题解 luogu.P9489 ZHY 的表示法

题目

luogu.P9489 ZHY 的表示法

比较难想的一道组合数学题。

题意建模

题目理解

给定 \(n\) 个数 \(x_1, x_2, \dots, x_n\),问区间 \([l, r]\) 中有多少个正整数 \(x\) 满足:存在实数 \(y\),使得 \(\sum_{i=1}^n \lfloor \frac{y}{x_i} \rfloor = x\)。例如,\(n=2\)\(x_1=2, x_2=3\) 时,\(x=5\) 可取 \(y=6\) 满足条件。

算法分析

初步理解

在我拿到题目时,我并不知道如何入手。最关键的是,我连暴力都写不出来。本来想先混个 \(30-50\) 分的,忽然发现,\(x\) 是未知的,竟然 \(y\) 也是未知的。这个取整函数也不好处理。但是相对这两个位置量,我连暴力枚举都做不到。这是我的“卡点”。代表我思路的堵塞之处。带着这样的问题,我翻开了题解。

首先,需要知道的是,这两个变量怎么建立关系。其次,需要知道,怎么取理解这个下取整的意义(首先肯定要明白一点,就是本题肯定是有特殊性质的)。我们分别来分析。

  1. 两个变量之间的关系
    显然每个 \(y\) 都唯一对应一个 \(x\),因此只需让每一个 \(x\) 都对应唯一一个 \(y\) 即可。我们可以发现\(x\) 实际上只与 \(y\) 除以 \(x\) 的商有关,和余数无关。这是一个很关键的性质。

  2. 单调性的发现
    能想出这一步,基本是做出来了。反正,我没发现。当规定 \(x\) 的值是一定的时候,肯定会有一种现象:随着 \(y\) 的增加,\(x\) 的值也会增大。因此可以发现按此方法 \(x\) 关于 \(y\) 的函数是单调的,并且显然可以发现 \(y\) 关于 \(x\) 的函数也是单调的,因此 \(x\)\(y\) 一一对应

到现在,已经有了一个初步的想法:枚举 \(y\),看看能不能在 \(y\) 的变化中,存在 \(x\) 的整数变化。

  1. 计算。
    可以发现,在 \(y\) 增大的过程中,若某一时刻 \(y\) 刚好是某个 \(x_{i}\) 的倍数,那么 \(y\) 除以 \(x_{i}\) 的商就会改变,\(x\) 就会改变,答案就应加 \(1\)。这也就是上文中说的变化观点

整理思路如下:

关键思路

  1. 问题转换:对于给定的 \(x\),是否存在 \(y\) 使得 \(\sum \lfloor \frac{y}{x_i} \rfloor = x\)?直接枚举 \(y\) 不可行(范围太大)。
  2. 单调性观察\(\sum \lfloor \frac{y}{x_i} \rfloor\)\(y\) 增加非递减。当 \(y\) 增加时,和可能不变或增加(当 \(y\) 达到某些 \(x_i\) 的倍数时)。
  3. 容斥原理:统计 \(y\) 使得 \(\sum \lfloor \frac{y}{x_i} \rfloor\) 变化,即 \(y+1\) 是至少一个 \(x_i\) 的倍数。这样的 \(y\) 的个数等于 \(1\)\(y_{\text{max}}\) 中能被至少一个 \(x_i\) 整除的数的个数(用容斥计算)。

算法步骤

  1. 二分查找:对于给定的上界 \(val\)(如 \(r\)\(l-1\)),找到最大的 \(y\)(记为 \(res\))使得 \(\sum \lfloor \frac{y}{x_i} \rfloor \leq val\)
  2. 容斥计算:计算 \(1\)\(res\) 中能被至少一个 \(x_i\) 整除的数的个数(即 \(cal(res)\))。
  3. 答案求解:区间 \([l, r]\) 的答案即为 \(solve(r) - solve(l-1)\),其中 \(solve(val)\) 返回 \(cal(res)\)

时间复杂度

  • 二分查找:\(O(\log (2 \times 10^{18}) \times n) \approx 60n\)
  • 容斥计算:枚举所有 \(2^n\) 个子集,计算 LCM。最坏 \(O(2^n \times n)\),但实际因 LCM 增长快,常可提前终止。
  • 总复杂度:\(O(\log y_{max}) \times n + 2^n \times n)\)\(n=25\) 时可行。

参考代码

#include<iostream>
#define int __int128
#define rei register int 
const int N=30;
int a[N]; 
int n,l,r; 
inline int read()
{
	int f=1,x=0;
	char c=getchar();
	while(c<'0' || c>'9') { if(c=='-') f=-1; c=getchar(); }
	while(c>='0' && c<='9') { x=(x<<1)+(x<<3)+(c^48); c=getchar(); }
	return f*x;
}
void write(int x)
{
	if(x<0) putchar('-'),x=-x;
	if(x>9) write(x/10);
	putchar(x%10+'0');
}
int get1(int n) 
{
    int count=0;
    while(n)
	{
	    n&=(n-1);  // 消除最低位的1
    	count++;
    }
    return count;
}
int gcd(int a,int b) { return b?gcd(b,a%b):a; }
int lcm(int a,int b) { return a/gcd(a,b)*b; }
bool check(int x,int y)
{
	int sum=0;
	for(rei i=1;i<=n;++i) sum+=y/a[i];
	return sum<=x;
}
int cal(int x)
{
	int all=(1<<n)-1,ans=0;
	for(rei i=1;i<=all;++i)
	{
		int cnt=get1(i),sum=1;
		for(rei j=1;j<=n;++j)
		{
			if(!(i&1<<j-1)) continue;
			sum=lcm(sum,a[j]);
			if(sum>x) break;
		}
		if(cnt&1) ans+=x/sum;
		else ans-=x/sum;
	}
	return ans;
}
int solve(int val)
{
	int L=-1,R=2e18,res;
	while(L<=R)
	{
		int mid=L+R>>1;
		if(check(val,mid)) res=mid,L=mid+1;
		else R=mid-1;
	}
	return cal(res);
}
signed main()
{
	n=read(),l=read(),r=read();
	for(rei i=1;i<=n;++i) a[i]=read();
	write(solve(r)-solve(l-1)); putchar('\n');
	return 0;
}

细节实现

代码注释

int solve(int val) {
    int L = -1, R = 2e18, res;
    while (L <= R) {
        int mid = (L + R) >> 1;
        if (check(val, mid)) { // 检查 Σ floor(mid/x_i) ≤ val
            res = mid;
            L = mid + 1;
        } else R = mid - 1;
    }
    return cal(res); // 计算1到res中能被至少一个x_i整除的数的个数
}

int cal(int x) {
    int ans = 0;
    for (int i = 1; i < (1 << n); ++i) { // 枚举所有非空子集
        int cnt = __builtin_popcount(i); // 子集大小
        int sum = 1;
        for (int j = 0; j < n; ++j) {
            if (i & (1 << j)) {
                sum = lcm(sum, a[j]); // 计算子集LCM
                if (sum > x) break; // 提前终止
            }
        }
        if (cnt % 2) ans += x / sum; // 奇加偶减
        else ans -= x / sum;
    }
    return ans;
}

总结归纳

本题的切入点比较隐晦。属于是非常难以挖掘。切入点如下:

  • 发现下取整函数只跟倍数有关。由此可以推得 \(y\) 不论是整数还是实数,都不影响答案的统计;

  • 基于上面一点,用一种变化的视角理解,就是当 \(y\) 变化的时候,如果 \(x\) 也跟着变化,那么就是算进答案中,成为一点贡献;

  • 又由上,发现是一个函数的模型,所以研究函数的基本性质,自然会联想到单调性(因为也是与 OI 竞赛联系最紧的),所以到这里,基本上思路就通了。

问题是,我还是不理解这种分析过程是怎样在大脑产生的。还是不得不感叹:容斥原理好难啊。。。

posted @ 2025-08-09 11:23  枯骨崖烟  阅读(6)  评论(0)    收藏  举报