2022牛客寒假算法基础集训营4
比赛链接
2022牛客寒假算法基础集训营4
D.雪色光晕
题目描述
给出两个初始点,一个点动 \(n\) 次,另外一个点不动,给出动点的方向向量 \((dx,dy)\),求动点和静点的最短距离
输入描述:
第一行是一个正整数 \(n\)。
第二行是四个整数 \(x_0\)、 \(y_0\)、\(x\) 和 \(y\) ,用空格隔开。用来表示动点和静点的初始坐标。
接下来的\(n\)行,每行输入两个整数 \(x_i\) 和 \(y_i\),用来表示方向向量坐标。
数据范围:
\(1\leq n\leq 200000\)
\(10^9\leq x,y,x_i,y_i\leq10^9\)
输出描述:
最近的距离。如果你的答案和正确答案的相对误差不超过 \(10^{-7}\),则认为答案正确。
示例1
输入
1
1 1 1 2
2 0
输出
1.0
示例2
输入
2
0 0 1 1
1 0
1 1
输出
0.70710678
示例3
输入
1
-10 0 1 1
20 0
输出
1.0
解题思路
计算几何
点到直线的距离公式:
设直线 \(L\) 的方程为\(Ax+By+C=0\),点 \(P\) 的坐标为\((x_0,y_0)\),则点 \(P\) 到直线 \(L\) 的距离为:\(\frac{Ax_0+By_0+C}{A^2+B^2}\)
同理可知,当\(P(x0,y0)\),直线\(L\)的解析式为\(y=kx+b\)时,则点\(P\)到直线\(L\)的距离为:\(\frac{|kx_0-y_0+b|}{\sqrt{k^2+1}}\)
求出静点到 \(n\) 个线段的最小值即可
- 时间复杂度:\(O(n)\)
代码
// Problem: 雪色光晕
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/23479/D
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair PII;
template bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
double x,y,xx,yy;
int n;
double dis(double x,double y,double xx,double yy)
{
return sqrt((x-xx)*(x-xx)+(y-yy)*(y-yy));
}
double f(double dx,double dy)
{
double xe=x+dx,ye=y+dy;
double res=min(dis(x,y,xx,yy),dis(xe,ye,xx,yy));
double k=dy/dx,b=y-k*x,bb=yy+1/k*xx;
double xj=(bb-k*b)/(k*k+1);
if(xj>=min(x,xe)&&xj<=max(x,xe))res=min(res,fabs(k*xx-yy+b)/sqrt(k*k+1));
return res;
}
int main()
{
scanf("%d",&n);
scanf("%lf%lf%lf%lf",&x,&y,&xx,&yy);
double res=dis(x,y,xx,yy);
while(n--)
{
double dx,dy;
scanf("%lf%lf",&dx,&dy);
res=min(res,f(dx,dy));
x+=dx,y+=dy;
}
printf("%lf",res);
return 0;
}
G.子序列权值乘积
题目描述
小红定义一个数组的权值为该数组的最大值乘以最小值。例如数组 \([4,1,3]\) 的权值是 \(4*1=4\)。
小红拿到了一个数组。她想知道,这个数组的所有 非空子序列 的权值的乘积是多少?由于该数过大,请对\(1000000007\)取模。
子序列的定义:对于一个数组,删除其中某些数之后(也可以不删)得到的数组。子序列中的数的相对顺序必须和原数组中的顺序相同。
例如:数组 \([1,3,2]\) 的非空子序列有 \([1] [3] [2] [1,3] [1,2] [3,2] [1,3,2]\) 共\(7\)个。
输入描述:
第一行输入一个正整数 \(n\) ,代表数组的长度。
第二行输入 \(n\) 个正整数 \(a_i\),代表小红拿到的数组。
数据范围:
\(1\leq n \leq 200000\)
\(1\leq a_i \leq 10^9\)
输出描述:
数组所有子序列的权值的乘积。答案对\(1000000007\)取模。
示例1
输入
2
2 3
输出
216
说明
对于子序列 \([2,3]\) ,其权值为 \(3 * 2 = 6\)。
对于子序列 \([2]\) ,其权值为 \(2 * 2 = 4\)。
对于子序列 \([3]\) ,其权值为 \(3 * 3 = 9\)。
因此所有非空子序列的权值乘积为: $6 * 4 * 9 = 216¥
示例2
输入
4
4 1 4 3
输出
757784222
解题思路
欧拉降幂:若正整数 \(a,n\) 互质,则对于任何正整数 \(b\),有 \(a^b\equiv a^{b\bmod \varphi {(n)}}(\bmod n)\)
由于是子序列,我们考虑每个值对答案的贡献,将数组排序并不会影响答案,对某个值 \(a_i\),其作为最小值时,后面的数即更大的数不能取否则与最小值矛盾,前面的数可以任取,即有 \(i-1\) 个数可取可不取,共 \(2^{i-1}\) 种方案,同理,后面 \(n-i\) 个数有 \(2^{n-i}\) 种方案,共 \(2^{i-1}+2^{n-i}\) 种方案,其对答案的贡献为 \(a_i^{2^{i-1}+2^{n-i}}\),单单算的话由于指数过大会超时,可以欧拉降幂: \(a_i^{2^{i-1}+2^{n-i}}=a_i^{(2^{i-1}+2^{n-i})\bmod \varphi {(1e9+7)}}\)
- 时间复杂度:\(O(n(logn+log(1e9+7)))\)
代码
// Problem: 子序列权值乘积
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/23479/G
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair PII;
template bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=2e5+5,mod=1e9+7;
int n,a[N];
int ksm(int a,int b,int p)
{
int res=1%p;
for(;b;b>>=1)
{
if(b&1)res=1ll*res*a%p;
a=1ll*a*a%p;
}
return res;
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d",&a[i]);
sort(a+1,a+1+n);
int res=1;
for(int i=1;i<=n;i++)
res=(1ll*res*ksm(a[i],(ksm(2,i-1,mod-1)+ksm(2,n-i,mod-1))%(mod-1),mod))%mod;
printf("%d",res);
return 0;
}
I.爆炸的符卡洋洋洒洒
题目描述
给出 \(n\) 个物品体积 \(a_i\) 和价值 \(b_i\),各物品最多只能选择一次,要求选出的物品的体积为 \(k\) 的倍数,求最大价值
输入描述:
第一行输入两个正整数 \(n\) 和 \(k\) ,用空格隔开。
接下来的 \(n\) 行,每行输入两个正整数 \(a_i\) 和 \(b_i\),用空格隔开。
数据范围:
\(1\leq n,k \leq 1000\)
\(1\leq a_i,b_i \leq 10^9\)
输出描述:
体积无法形成倍数关系,则输出\(-1\)。
否则输出最大价值。
示例1
输入
2 3
1 2
2 1
输出
3
示例2
输入
2 2
1 2
2 1
输出
1
示例3
输入
3 4
1 2
5 3
1 4
输出
-1
解题思路
01背包变形
背包体积过大,但是 \(k\) 较小,可将体积对 \(k\) 取模,将体积转化为模,将要求的体积转化为模为 \(0\),即:
- 状态表示:\(f[i][j]\) 表示前 \(i\) 个物品体积对 \(k\) 取模为 \(j\) 时的最大价值
- 状态计算:\(f[i][j]=max(f[i-1][j],f[i-1][(j-v[i]\%k+k)\%k]+w[i])\)
注意:初始化要尽可能小,表示取不到,\(f[0][0]\) 表示 \(0\) 个物品价值为 \(0\)
- 时间复杂度:\(O(nk)\)
代码
// Problem: 爆炸的符卡洋洋洒洒
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/23479/I
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair PII;
template bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=1010;
LL f[N][N];
int n,k,v[N],w[N];
int main()
{
scanf("%d%d",&n,&k);
for(int i=1;i<=n;i++)scanf("%d%d",&v[i],&w[i]);
for(int i=0;i<=n;i++)for(int j=0;j<=k;j++)f[i][j]=-1e12;
f[0][0]=0;
for(int i=1;i<=n;i++)
for(int j=0;j
J.区间合数的最小公倍数
题目描述
给定一个区间 \([l,r]\),求其中所有合数的最小公倍数
输入描述:
两个正整数 \(l\) 和 \(r\) ,用空格隔开。
数据范围:\(1\leq l \leq r \leq 30000\)
输出描述:
若区间 \([l,r]\) 中没有任何合数,则输出\(-1\)。
否则输出区间 \([l,r]\) 所有合数的最小公倍数,答案对\(1000000007\)取模。
示例1
输入
2 3
输出
-1
说明
区间 \([2,3]\) 的两个数 \(2\) 和 \(3\) 都是素数,不是合数。
示例2
输入
4 6
输出
12
说明
区间\([4,6]\)中有三个数:\(4、5、6\),其中 \(5\) 是素数, \(4\) 和 \(6\) 是合数,它们的最小公倍数是\(12\)。
示例3
输入
25000 30000
输出
187554966
解题思路
数学:求取多个数的最小公倍数,即将所有数分解质因数,统计每个素数出现的最高次幂,其乘积即为所求
例:\(a=2^3*3^5*5^1,b=2^4*3^1*5^2\),那么它们的最小公倍数就是 \(2^4*3^5*5^2\)
直接欧拉筛求素数,剩余的合数求最小公倍数即可
- 时间复杂度:\(O(nlogn)\)
代码
// Problem: 区间合数的最小公倍数
// Contest: NowCoder
// URL: https://ac.nowcoder.com/acm/contest/23479/J
// Memory Limit: 524288 MB
// Time Limit: 2000 ms
//
// Powered by CP Editor (https://cpeditor.org)
// %%%Skyqwq
#include
#define pb push_back
#define fi first
#define se second
#define mkp make_pair
using namespace std;
typedef long long LL;
typedef pair PII;
template bool chkMax(T &x, T y) { return (y > x) ? x = y, 1 : 0; }
template bool chkMin(T &x, T y) { return (y < x) ? x = y, 1 : 0; }
template void inline read(T &x) {
int f = 1; x = 0; char s = getchar();
while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
x *= f;
}
const int N=3e4+5,mod=1e9+7;
bool st[N];
int prime[N],v[N];
int l,r,m;
void primes(int n)
{
memset(v,0,sizeof v);
for(int i=2;i<=n;i++)
{
if(v[i]==0)
{
prime[++m]=i;
v[i]=i;
st[i]=true;
}
for(int j=1;j<=m;j++)
{
if(i*prime[j]>n||v[i]>=1)
{
if(b&1)res=1ll*res*a%p;
a=1ll*a*a%p;
}
return res;
}
int main()
{
primes(N);
scanf("%d%d",&l,&r);
int res=1;
unordered_map mp;
for(int i=l;i<=r;i++)
{
if(!st[i])
{
int j=i;
for(int k=2;k*k<=j;k++)
{
if(j%k==0)
{
int t=0;
while(j%k==0)t++,j/=k;
mp[k]=max(mp[k],t);
}
}
if(j>1)mp[j]=max(mp[j],1);
}
}
for(auto [x,y]:mp)res=1ll*res*ksm(x,y,mod)%mod;
printf("%d",res==1?-1:res);
return 0;
}