2025.10.14题解合集

A - 补给站

题目大意:

给两个补给站坐标和 n n n 个点的坐标。 q q q 次询问,每次询问回答在以两个补给站坐标为圆心,半径为 r 1 , r 2 r_1,r_2 r1,r2 的两个圆形范围内,共多少个点。

思路:

首先明确如何知道点与补给站的距离。(不是欧几里得距离!!

这里要用到勾股定理, x , y x,y x,y 坐标的差就是两点之间一个直角三角形的两条直角边。剩下不用我多说。

赛时,第一时间是想求两个前缀和,分别对应两个补给站,结果发现重复部分太难处理。

换一种思路。

考虑先以与一个补给站的距离为标准,先找到与这个补给站符合条件的点,再找另一个,避免重复。

观察到,补给站的位置不会变化,所以可以同样可以将询问以一个补给站的半径排序。

这样,某一个补给站的半径单调递增,设某一个当前询问半径为 a a a ,前一个询问半径为 b b b ,那么,以 a a a 为半径时合法的点,在半径为 b b b 时,必定合法。

接下来,就是如何去重,找另一个点。

我们先钦定按第一个点的标准排序。

将每一个点与两点的距离以与第一个点的距离为第一标准排序。

可以很容易想到,一个个去找是不可行的。

尝试树状数组,以前缀和优化。

每次将已经与第一个补给站合法配对的点的贡献 1 1 1 从树状数组中删除。

设当前与第一个补给站匹配的指针为 k k k

那么,每次询问答案就是 k+query(a[k].y)

到这就做完了。

C o d e : \mathcal Code: Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){int x = 0; bool f = 1; char ch = getchar();while (ch < '0' || ch > '9') { if (ch == '-') f = 0; ch = getchar(); }while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();return (f ? x : (-x));}
const int N=1e6+5;
int n,m;
int ax,ay,bx,by;
struct node{
	int num1,num2,id;
}a[N];
struct node1{
	int x,y,id;
}ask[N];
bool cmp(node a,node b){
	return a.num1<b.num1;
}
bool cmp1(node1 a,node1 b){
	if(a.x==b.x)return a.y<b.y;
	return a.x<b.x;
}
int lowbit(int x){return (x&(-x));}
int tree[N];
void add(int x,int y){
	for(int i=x;i<=300005;i+=lowbit(i))tree[i]+=y;
}
int query(int x){
	int ans=0;
	for(int i=x;i>0;i-=lowbit(i))ans+=tree[i];
	return ans;
}
int ans[N];
signed main(){
//	freopen("in.in","r",stdin);
//	freopen("myans.out","w",stdout);
	scanf("%lld%lld",&n,&m);
	scanf("%lld%lld%lld%lld",&ax,&ay,&bx,&by);
	for(int i=1;i<=n;i++){
		int q,p;
		scanf("%lld%lld",&q,&p);
		a[i]={(long long)ceil(sqrt(abs(ax-q)*abs(ax-q)+abs(ay-p)*abs(ay-p))),(long long)ceil(sqrt(abs(bx-q)*abs(bx-q)+abs(by-p)*abs(by-p))),i};
		//cout<<a[i].num2<<endl;
		add(a[i].num2+1,1);
	}
	sort(a+1,a+1+n,cmp);
	for(int i=1;i<=m;i++)scanf("%lld%lld",&ask[i].x,&ask[i].y),ask[i].id=i;
	sort(ask+1,ask+1+m,cmp1);
	//for(int i=1;i<=m;i++)cout<<ask[i].x<<" "<<ask[i].y<<endl;
	int k=1;
	for(int i=1;i<=m;i++){
		while(k<=n&&a[k].num1<=ask[i].x){
			//cout<<k<<endl;
			add(a[k].num2+1,-1);
			k++;
		}
		ans[ask[i].id]=k-1+query(ask[i].y+1);
	}
	for(int i=1;i<=m;i++)printf("%lld\n",ans[i]);
	//fclose(stdout);
	return 0;
}

B - WYF的盒子

洛谷-题目链接

(数据是自己随机生成的,有点水) 可以私信我,提供你的hack数据。

题目大意:

∑ i = m n i k   m o d   p \Large\sum^{n}_{i=m}i^k\bmod p i=mnikmodp

没了,就这么简单!

思路:

关键在于,这道题,你暴力绝对过不去!

就是恶心你。

然后,就必须要用第二类斯特林数这种更恶心的东西。

首先钦定 m = 1 m=1 m=1

显然:
a n s = ∑ i = 1 n i k                  ( 1 ) = ∑ i = 1 n ∑ j = 0 k { k j } i j ‾ ( 2 ) = ∑ i = 1 n ∑ j = 0 k { k j } ∏ d = i − j + 1 i d ( 3 ) = ∑ i = 1 n ∑ j = 0 k { k j } × j ! × ( i j ) ( 4 ) = ∑ j = 0 k { k j } j ! ∑ i = j n ( i j ) ( 5 ) = ∑ j = 0 k { k j } j ! ( n + 1 j + 1 ) ( 6 ) = ∑ j = 0 k { k j } j ! ( n + 1 ) ! ( j + 1 ) ! ( n − j ) ! ( 7 ) = ∑ j = 0 k { k j } ( j + 1 ) − 1 ∏ p = n + 1 − j n + 1 p ( 8 ) \begin{aligned} ans&=\sum^{n}_{i=1}i^k~~~~~~~~~~~~~~~~&(1)\\ &=\sum^{n}_{i=1}\sum^{k}_{j=0}{k\brace j}i^{\underline{j}}&(2)\\ &=\sum^{n}_{i=1}\sum^{k}_{j=0}{k\brace j}\prod^{i}_{d=i-j+1}d&(3)\\ &=\sum^{n}_{i=1}\sum^{k}_{j=0}{k\brace j}\times j!\times\binom{i}{j}&(4)\\ &=\sum^{k}_{j=0}{k\brace j}j!\sum^{n}_{i=j}\binom{i}{j}&(5)\\ &=\sum^{k}_{j=0}{k\brace j}j!\binom{n+1}{j+1}&(6)\\ &=\sum^{k}_{j=0}{k\brace j}j!\frac{(n+1)!}{(j+1)!(n-j)!}&(7)\\ &=\sum_{j=0}^{k}{k\brace j}(j+1)^{-1}\prod^{n+1}_{p=n+1-j}p&(8) \end{aligned} ans=i=1nik                =i=1nj=0k{jk}ij=i=1nj=0k{jk}d=ij+1id=i=1nj=0k{jk}×j!×(ji)=j=0k{jk}j!i=jn(ji)=j=0k{jk}j!(j+1n+1)=j=0k{jk}j!(j+1)!(nj)!(n+1)!=j=0k{jk}(j+1)1p=n+1jn+1p(1)(2)(3)(4)(5)(6)(7)(8)

( 2 ) (2) (2) 运用公式:
x n = ∑ k { n k } x k ‾ x^n=\sum_{k}{n\brace k}x^{\underline k} xn=k{kn}xk
( 3 ) (3) (3) 转化为连乘。

( 4 ) (4) (4) 将连乘转化为组合数。将 j ! × ( i j ) j!\times\binom{i}{j} j!×(ji) 拆开可知。

( 6 ) (6) (6) ( n m ) = ( n − 1 m − 1 ) + ( n − 1 m ) \binom{n}{m}=\binom{n-1}{m-1}+\binom{n-1}{m} (mn)=(m1n1)+(mn1) ,所以 ∑ i = j n ( i j ) \sum^{n}_{i=j}\binom{i}{j} i=jn(ji) 即可发现该结论。

( 8 ) (8) (8) 因为 ∏ p = n + 1 − j n + 1 p \prod^{n+1}_{p=n+1-j}p p=n+1jn+1p 共是连续 j + 1 j+1 j+1 个数的乘积,所以 ∏ p = n + 1 − j n + 1 p \prod^{n+1}_{p=n+1-j}p p=n+1jn+1p 必定能整除 j + 1 j+1 j+1

公式推理完毕。

现在考虑怎么求 m ≠ 0 m\ne 0 m=0 的情况。

很简单,上述在 m = 0 m=0 m=0 的情况下,相当于求得了 ∑ i = 1 n i k \sum^{n}_{i=1}i^k i=1nik ,那么我们把 n n n 换成 m m m 再做一遍,两次得到的答案相减,就是正确答案。

由于本题的特殊性质,我们可以用上述方法求得 k ≤ 2000 k \le 2000 k2000 的情况,若 k > 2000 k>2000 k>2000 就用快速幂解决,因为在 k > 2000 k>2000 k>2000 时,有特殊性质规定 n − m ≤ 5000 n-m\le 5000 nm5000

C o d e : \mathcal Code: Code:

#include<bits/stdc++.h>
#define int long long
using namespace std;
inline int read(){int x = 0; bool f = 1; char ch = getchar();while (ch < '0' || ch > '9') { if (ch == '-') f = 0; ch = getchar(); }while (ch >= '0' && ch <= '9') x = (x << 1) + (x << 3) + (ch ^ 48), ch = getchar();return (f ? x : (-x));}
const int N=1e6+5;
__int128 num[2001][2001];
__int128 Pow(__int128 x,__int128 y,__int128 p){
	__int128 res=1;
	while(y){
		if(y&1)res=res*x%p;
		x=x*x%p;
		y>>=1; 
	}
	return res;
}
void work1(int n,int m,int k,int p){
	__int128 ans=0;
	for(int i=m;i<=n;i++)ans=(ans+Pow(i,k,p)%p)%p;
	printf("%lld\n",(long long)ans);
}
void init(int k,int p){
	num[0][0]=1;
	for(int i=1;i<=k;i++)for(int j=1;j<=k;j++)num[i][j]=(num[i-1][j-1]+num[i-1][j]*j%p)%p;
}
__int128 work2(int n,int k,int p){
	__int128 res=0;
	for(int i=0;i<=k;i++){
		__int128 ksum1=1;
		for(int j=n+1-i;j<=n+1;j++)ksum1=(ksum1*(j%(i+1)==0?j/(i+1):j))%p;
		res=(res+num[k][i]*ksum1)%p;
	}
	return res;
}
signed main(){
	int k,n,m,p;
	scanf("%lld%lld%lld%lld",&k,&n,&m,&p);
	if(k>2000)work1(n,m,k,p);
	else{
		init(k,p);
		printf("%lld\n",(long long)((work2(n,k,p)-work2(m-1,k,p)+p)%p));
	}
	return 0;
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值