专栏文章

题解:CF403D Beautiful Pairs of Numbers

CF403D题解参与者 2已保存评论 1

文章操作

快速查看文章及其快照的属性,并进行相关操作。

当前评论
1 条
当前快照
1 份
快照标识符
@minky6c2
此快照首次捕获于
2025/12/02 04:06
3 个月前
此快照最后确认于
2025/12/02 04:06
3 个月前
查看原文
CF Duel 到的,极限拿下了。

题目大意

tt 个询问,每个询问给定 nnkk,求满足以下条件的长度为 kk 的二元组序列个数:
  • 1a1b1<a2b2<<akbkn1 \leq a_{1} \leq b_{1} < a_{2} \leq b_{2} < \ldots < a_{k} \leq b_{k} \leq n
  • 所有数 b1a1b_{1}-a_{1}b2a2b_{2}-a_{2}\ldotsbkakb_{k}-a_{k} 两两不同。

分析

首先 bbaa 的差两两不同,且 aabb 都不降,说明 aabb 增长很快,kk 太大时答案都是 00,构造一下,最坏情况下是 (1,1),(2,4),(5,8),(9,13)...(1,1),(2,4),(5,8),(9,13)... 算一下 kk 大于 5050 答案就肯定是 00
此时可以 O(n2k)O(n^2k) 了,尽量往上靠。
然后可以把 (ai,bi)(a_i,b_i) 看成一个区间 [ai,bi][a_i,b_i],此时我们只需要知道区间总长度 ss,那么这个总长度贡献的答案就是 (k+ns)!(ns)!\frac{(k+n-s)!}{(n-s)!}每个区间的左端点不在任何区间里的点 的任意一种排列方法都对应一种方案,除掉的是 不在任何区间里的点 的内部顺序)。
此时就很像 DP 了,先确定两个状态:目前加入了几个区间、此时区间总长度。
现在考虑第二个限制,转化后就是区间长度两两不同,这个两两不同十分不好处理,所以我们可以考虑按长度从小到大加入区间,并保证每种长度之加入一次。
此时状态就完全出来了:
fi,j,kf_{i,j,k} 表示目前加入了 ii 个区间,目前只加入了了长度 j\le j 的区间,总长度为 kk 的方案数。
转移:
fi,j,k=fi,j1,k+fi1,j1,kjf_{i,j,k}=f_{i,j-1,k}+f_{i-1,j-1,k-j}:分别对应加入/不加入长度为 jj 的区间。
最后统计答案时枚举最终区间总长度并用刚刚那个式子贡献就行了。

注意

开不下 O(50n2)O(50n^2) 的数组,开太小又会 RE,所以要把询问离线下来,对 kk 作扫描线(也就是把 kk 相等的统一处理)。

code

CPP
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;

ll read()
{
	ll ret = 0 ,f = 1; char ch = getchar();
	while (ch < '0' || ch > '9') f = (ch == '-' ? -f : f) ,ch = getchar();
	while (ch >= '0' && ch <= '9') ret = ret * 10 + ch - '0' ,ch = getchar();
	return ret * f;
}

const int mod = 1e9 + 7;
const int N = 1010 ,K = 50 ,T = 2e5 + 10;
ll f[2][N][N]; //滚动优化空间 
ll fac[N] ,inv[N] ,ans[T];

struct Q{
	int n ,id;
};
vector<Q> que[K];

ll qpow(ll a ,ll b)
{
	ll ret = 1;
	while (b)
	{
		if (b & 1) ret = ret * a % mod;
		a = a * a % mod;
		b >>= 1;
	}
	return ret;
}

int main()
{
	fac[0] = inv[0] = 1;
	for (int i = 1;i < N;i++) fac[i] = fac[i - 1] * i % mod;
	inv[N - 1] = qpow(fac[N - 1] ,mod - 2);
	for (int i = N - 2;i >= 1;i--) inv[i] = inv[i + 1] * (i + 1) % mod; //预处理阶乘及其逆元 
	
	int t = read();
	
	for (int i = 1;i <= t;i++)
	{
		int n = read() ,k = read();
		
		ll now = 0;
		bool f = false;
		for (int j = 0;j < k;j++)
		{
			now++ ,now += j;
			if (now > n) { ans[i] = 0; f = true; break; } //判答案为 0 
		}
		if (!f) que[k].push_back({n ,i}); //把答案不为0的询问离线下来 
	}
	
	for (int i = 0;i < N;i++) f[0][i][0] = 1;
	for (int i = 1 ,nw = 1;i < K;i++ ,nw ^= 1)
	{
		memset(f[nw] ,0 ,sizeof(f[nw])); 
		for (int j = 1;j < N;j++)
			for (int k = 0;k < N;k++)
			{
				f[nw][j][k] = f[nw][j - 1][k];
				if (k >= j) f[nw][j][k] = (f[nw][j][k] + f[nw ^ 1][j - 1][k - j]) % mod; //转移 
			}
		
		for (auto q : que[i])
		{
			int id = q.id ,n = q.n;
			for (int j = 1;j <= n;j++)
				ans[id] = (ans[id] + f[nw][n][j] * fac[i + n - j] % mod * inv[n - j] % mod) % mod; //贡献答案 
		}
	}
	
	for (int i = 1;i <= t;i++) printf("%lld\n",ans[i]);
	
	return 0;
}

评论

1 条评论,欢迎与作者交流。

正在加载评论...