传送门:CF
A题:A. Coins
一道数论题.发现这道题显然是一道解不定方程的题目.需要了解一下裴属定理.
裴属定理有一个重要的推论:
a,b互质的充分必要条件是存在整数x,y使ax+by=1.
具体证明网上有大量博客对此进行介绍,此处就不在赘述了
所以对于本题来说,也就是如果存在
2
∗
x
+
k
∗
y
=
1
2*x+k*y=1
2∗x+k∗y=1有解的话就等价于
2
∗
x
∗
n
+
k
∗
y
∗
n
=
n
2*x*n+k*y*n=n
2∗x∗n+k∗y∗n=n有解,将此时的
n
∗
x
=
X
,
n
∗
y
=
Y
n*x=X,n*y=Y
n∗x=X,n∗y=Y,也就是
2
∗
X
+
k
∗
Y
=
n
2*X+k*Y=n
2∗X+k∗Y=n有解
所以只需要判断2与k是否互质即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define int long long
#define maxn 1000000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int gcd(int a,int b) {
if(a%b==0) return b;
else return gcd(b,a%b);
}
signed main() {
int T=read();
while(T--) {
int n=read();int k=read();
if(n%2==0||n%k==0) {
printf("YES\n");
continue;
}
if(gcd(2,k)==1) printf("YES\n");
else printf("NO\n");
}
return 0;
}
B题:B. Long Legs
一道巨恶心的题目
首先我们需要知道的是,假设我们最终的腿长为
x
x
x,假设我们需要走的路长为
s
s
s,那么显然我们贪心的去想,因为我们已经花费到达这个腿长了,所以我们肯定是每一次尽量走最长的步数是最优秀的.但是我们最终肯定又会剩下来一部分(记为k),但是这个k显然是小于x的(因为是剩下来的),所以我们显然可以在腿长为
k
k
k的时候顺便走一步即可.所以此时的需要的总花费显然就是
c
e
i
l
(
s
/
x
)
+
x
−
1
ceil(s/x)+x-1
ceil(s/x)+x−1
所以我们将我们的题目进行抽象表示,也就是
F
(
x
)
=
c
e
i
l
(
n
/
x
)
+
x
−
1
+
c
e
i
l
(
m
/
x
)
F(x)=ceil(n/x)+x-1+ceil(m/x)
F(x)=ceil(n/x)+x−1+ceil(m/x),所以我们现在只要枚举
x
x
x即可.显然我们会发现取极值的点应该是
x
∗
(
x
−
1
)
=
(
n
+
m
)
x*(x-1)=(n+m)
x∗(x−1)=(n+m)左右的时候,此时这道题抽象的地方就来了,当时我也就这么想的,然后感觉就应该是
x
+
1
,
x
−
1
x+1,x-1
x+1,x−1这样的位置,但是就是一直wa,我们需要发现的是此时我们的函数是带取整函数的,所以会影响我们的极值的取点.所以我们此时需要枚举分界点左右的所有点(因为取整函数的原因导致附近点都会成为最优点).上边界我们应该取
2
∗
x
2*x
2∗x的地方,因为我们的余数的影响范围肯定是小于(x)的,因为x是分母会导致余数最大为x-1,所以最大的影响范围也只能是
2
∗
x
2*x
2∗x,然后下边界取0即可
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define int long long
#define maxn 1000000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
int get_num(int a) {
return ((ll)1+(ll)__builtin_sqrt((ll)1+(ll)4*a))/(ll)2;
}
signed main() {
int T=read();
while(T--) {
int x=read(),y=read();
int num=get_num(x+y)*2;
int ans=ll_INF;
for(int i=1;i<=num;i++) {
ans=min(ans,(ll)ceil((double)x/(double)i)+(ll)ceil((double)y/(double)i)+i-1);
}
cout<<ans<<endl;
}
return 0;
}
C题:C. Search in Parallel
题目很长,但是实际上应该并不是很难.
读懂题意之后应该不难想到一个贪心的想法:也就是尽量将需要次数较多的球放在需要时间最小的地方.
简单来证明一下这个贪心想法:假设此时我们有两个球,一个需要a次,一个需要b次.我们此时可以放两个位置c,d且
t
i
m
e
(
c
)
<
t
i
m
e
(
d
)
time(c)<time(d)
time(c)<time(d)那么假设我们的a放在c出,b放在d处.此时我们需要的相对时间为
a
∗
c
+
b
∗
d
a*c+b*d
a∗c+b∗d,反之则需要
a
∗
d
+
b
∗
c
a*d+b*c
a∗d+b∗c,显然需要次数多的放在时间少的花费更少.这是两个球的情况,不难将其推广到n个球.
所以我们只要排序一下,然后模拟放球过程即可,放球的时候尽量放在时间较短的地方
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x=0,w=1;char ch=getchar();
for(;ch>'9'||ch<'0';ch=getchar()) if(ch=='-') w=-1;
for(;ch>='0'&&ch<='9';ch=getchar()) x=x*10+ch-'0';
return x*w;
}
#define maxn 1000000
const double eps=1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Ball{
int times,id;
bool operator < (const Ball &rhs) const {
return times>rhs.times;
}
}ball[maxn];
int main() {
int T=read();
while(T--) {
int n=read(),s1=read(),s2=read();
for(int i=1;i<=n;i++) {
ball[i].times=read();
ball[i].id=i;
}
sort(ball+1,ball+n+1);
vector<int>a,b;
int t1=0,t2=0;
for(int i=1;i<=n;i++) {
if(t1+s1<=t2+s2) {
a.push_back(ball[i].id);
t1+=s1;
}
else {
b.push_back(ball[i].id);
t2+=s2;
}
}
printf("%d ",a.size());
for(int i=0;i<a.size();i++) {
printf("%d ",a[i]);
}
printf("\n");
printf("%d ",b.size());
for(int i=0;i<b.size();i++) {
printf("%d ",b[i]);
}
printf("\n");
}
return 0;
}
D题:D. Balancing Weapons
本题可以使用双指针套双指针,也可以使用二分+双指针,前者复杂度为nn,后者复杂度为nn*log(n),并且由于博主写的比较丑,两种方法皆不能使用map或者set增加一个log,如果没有被卡常的话使用stl将会更加舒服
先讲讲二分的思路(二分比较好理解):
首先先按照成绩排一个序,因为我们发现改的位置是随意的,和原本的位置没有关系,所以不妨排一个序
然后我们会发现改的位置越多,我们最终的序列显然是越容易满足最终结果的.因为假设我们改n个位置可以使其满足了条件,显然我们改n+1个位置也可以使其满足条件.这就有了一个二分的单调性,所以本题可以使用二分.我们二分改变的数字的个数.
我们观察一下我们排好序的序列,我们会发现存在这样的一个性质,我们最终保留不改的那几个数字显然应该是连在一块的才对.因为假设我们固定了序列的中的两个数字,因为我们的序列是单调的,所以显然我们两个数字之间的所有数字都是没有必要进行更改的.因为对于中间的数字来说,他们既不是最大值也不是最小值,没有做出贡献.所以我们最终的固定的数字肯定是连在一块的.
所以此时的check函数也就不难写了,对于每一次check我们都有了保留数字的个数,又因为他们是连在一起的,所以我们可以枚举这个区间的左右两端点记为(
l
,
r
l,r
l,r)然后我们尽量的更改区间以外的数字,让他们尽量的往中间去靠(因为此时我们固定的区间已经产生了一个差值)..对于一个数字,我们此时有两种情况:
1.假设我们通过更改能使这个数字变成区间范围内的数字,那么显然此时是最优的,因为没有对差值造成影响.
2.反之我们通过更改可以找到一个恰好大于
a
[
r
]
a[r]
a[r]的一个值,也可以找到一个恰好小于
a
[
l
]
a[l]
a[l]的一个值.那么此时对于这个数字我们可以选择其中的一个值.注意此时我们不能贪心的单个去考虑选哪一个值!!,因为可能此时较小值可能最优,但是后面我们出现了一个较大值包括了当前较大值的一个数,就会导致较大值更优了!!.所以我们此时需要存下来等会一起考虑
考虑记录每一个不能变换到区间中的数字的编号以及较大,小值,我们将其以<编号,值>存入数组中,然后按照值来从小到大排序.然后我们此时需要的就是找到一个区间,这个区间符合我们的差值条件,又包括所有数字的编号即可.这个我们可以使用双指针来从左到右进行枚举(具体实现看代码)
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x = 0, w = 1;
char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
#define int long long
#define maxn 1000000
const double eps = 1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Gun {
int f, d, mul;
bool operator < (const Gun &rhs) const {
return mul < rhs.mul;
}
} gun[maxn];
struct Node {
int num, id;
bool operator < (const Node &rhs) const {
return num < rhs.num;
}
}a[maxn];
int sum[maxn];
int n, k;
int mp[maxn];
inline int check(int mid) {
int len = n - mid;
if (len == 0) return true;
for (int r = len; r <= n; r++) {
int l = r - len + 1;
int num1 = gun[l].mul;
int num2 = gun[r].mul;
if (num2 - num1 > k) continue;
int CNT = 0;int cnt = 0;
for (int i = 1; i <= n; i++) mp[i] = 0;
for (int j = 1; j < l; j++) {
int beishu = num2 / gun[j].f;int beishu2 = num1 / gun[j].f;
if (num2 % gun[j].f == 0 || num1 % gun[j].f == 0) {
CNT++;continue;
}
if (beishu * gun[j].f >= num1 && beishu * gun[j].f <= num2) {
CNT++;continue;
}
if ((beishu2 + 1)*gun[j].f >= num1 && (beishu2 + 1)*gun[j].f <= num2) {
CNT++;continue;
}
a[++cnt] = {(beishu + 1)*gun[j].f, j};
if (beishu2 != 0) a[++cnt] = {beishu2*gun[j].f, j};
}
for (int j = r + 1; j <= n; j++) {
int beishu = num2 / gun[j].f;int beishu2 = num1 / gun[j].f;
if (num2 % gun[j].f == 0 || num1 % gun[j].f == 0) {
CNT++;continue;
}
if (beishu * gun[j].f >= num1 && beishu * gun[j].f <= num2 && beishu != 0) {
CNT++;continue;
}
if ((beishu2 + 1)*gun[j].f >= num1 && (beishu2 + 1)*gun[j].f <= num2) {
CNT++;continue;
}
a[++cnt] = {(beishu + 1)*gun[j].f, j};
if (beishu2 != 0) a[++cnt] = {beishu2*gun[j].f, j};
}
sort(a + 1, a + cnt + 1);
if (CNT == mid) return true;
int L = 1, R = 1;
while (L <= cnt && num2 - a[L].num > k) L++;
if (L == cnt + 1) continue;
if (a[L].num - num1 > k) continue;
R = L;mp[a[L].id]++;CNT++;
int flag = 1;
while (L <= cnt) {
if (CNT == mid) return true;
if (a[L].num - num1 > k) {
flag = 0;
break;
}
while (R <= cnt && a[R + 1].num - a[L].num <= k) {
R++;
if (a[R].num - num1 > k) {
flag = 0;
break;
}
mp[a[R].id]++;
if (mp[a[R].id] == 1) CNT++;
if (CNT == mid) return true;
}
if (flag == 0) break;
mp[a[L].id]--;
if (mp[a[L].id] == 0) CNT--;
L++;
}
}
return false;
}
signed main() {
int T = read();
while (T--) {
n = read(), k = read();
for (int i = 1; i <= n; i++) {
gun[i].f = read();
}
for (int i = 1; i <= n; i++) {
gun[i].d = read();
gun[i].mul = gun[i].f * gun[i].d;
}
sort(gun + 1, gun + n + 1);
if (gun[n].mul - gun[1].mul <= k) {
cout << 0 << endl;
continue;
}
int l = 1, r = n;
int ans = -1;
while (l <= r) {
int mid = (l + r) >> 1;
if (check(mid)) {
ans = mid;
r = mid - 1;
} else {
l = mid + 1;
}
}
cout << ans << endl;
}
return 0;
}
然后讲讲如何优化掉二分的一个log.在第一个方法中我们的check函数中其实构造了一个用来解决判断一个固定当前区间是否可行的函数,我们沿用这个函数.
考虑使用双指针来枚举我们最终可行的固定区间.显然当我们的
[
l
,
r
]
[l,r]
[l,r]区间固定时假设可以满足题意,那么此时我们有一个ans就是
r
−
l
+
1
r-l+1
r−l+1,并且此时我们可以考虑将我们的区间扩大,也就是将右指针右移
假设此时我们的区间没法满足题意,那么此时我们的区间如果扩大肯定更难满足题意.所以我们此时需要将区间缩小,所以我们将左指针右移.
上述情况就满足了双指针的做法的正确性.
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define root 1,n,1
#define ls rt<<1
#define rs rt<<1|1
#define lson l,mid,rt<<1
#define rson mid+1,r,rt<<1|1
inline ll read() {
ll x = 0, w = 1;
char ch = getchar();
for (; ch > '9' || ch < '0'; ch = getchar()) if (ch == '-') w = -1;
for (; ch >= '0' && ch <= '9'; ch = getchar()) x = x * 10 + ch - '0';
return x * w;
}
#define int long long
#define maxn 1000000
const double eps = 1e-8;
#define int_INF 0x3f3f3f3f
#define ll_INF 0x3f3f3f3f3f3f3f3f
struct Gun {
int f, d, mul;
bool operator < (const Gun &rhs) const {
return mul < rhs.mul;
}
} gun[maxn];
struct Node {
int num, id;
} a[maxn];
bool cmp(Node a, Node b) {
return a.num < b.num;
}
int sum[maxn];
int n, k;
int mp[maxn];
inline int check(int l, int r) {
int mid = n - (r - l + 1);int len = n - mid;
if (len == 0) return true;
int num1 = gun[l].mul;int num2 = gun[r].mul;
if (num2 - num1 > k) return false;
int CNT = 0;int cnt = 0;
for (int i = 1; i <= n; i++) mp[i] = 0;
for (int j = 1; j < l; j++) {
int beishu = num2 / gun[j].f;int beishu2 = num1 / gun[j].f;
if (num2 % gun[j].f == 0 || num1 % gun[j].f == 0) {
CNT++;continue;
}
if (beishu * gun[j].f >= num1 && beishu * gun[j].f <= num2) {
CNT++;continue;
}
if ((beishu2 + 1)*gun[j].f >= num1 && (beishu2 + 1)*gun[j].f <= num2) {
CNT++;continue;
}
a[++cnt] = {(beishu + 1)*gun[j].f, j};
if (beishu2 != 0) a[++cnt] = {beishu2*gun[j].f, j};
}
for (int j = r + 1; j <= n; j++) {
int beishu = num2 / gun[j].f;
int beishu2 = num1 / gun[j].f;
if (num2 % gun[j].f == 0 || num1 % gun[j].f == 0) {
CNT++;continue;
}
if (beishu * gun[j].f >= num1 && beishu * gun[j].f <= num2 && beishu != 0) {
CNT++;continue;
}
if ((beishu2 + 1)*gun[j].f >= num1 && (beishu2 + 1)*gun[j].f <= num2) {
CNT++;continue;
}
a[++cnt] = {(beishu + 1)*gun[j].f, j};
if (beishu2 != 0) a[++cnt] = {beishu2*gun[j].f, j};
}
sort(a + 1, a + cnt + 1, cmp);
if (CNT == mid) return true;
int L = 1, R = 1;
while (L <= cnt && num2 - a[L].num > k) L++;
if (L == cnt + 1) return false;
if (a[L].num - num1 > k) return false;
R = L;mp[a[L].id]++;CNT++;
int flag = 1;
while (L <= cnt) {
if (CNT == mid) return true;
if (a[L].num - num1 > k) {
flag = 0;
break;
}
while (R <= cnt && a[R + 1].num - a[L].num <= k) {
R++;
if (a[R].num - num1 > k) {
flag = 0;
break;
}
mp[a[R].id]++;
if (mp[a[R].id] == 1) CNT++;
if (CNT == mid) return true;
}
if (flag == 0) break;
mp[a[L].id]--;
if (mp[a[L].id] == 0) CNT--;
L++;
}
return false;
}
signed main() {
int T = read();
while (T--) {
n = read(), k = read();
for (int i = 1; i <= n; i++) {
gun[i].f = read();
}
for (int i = 1; i <= n; i++) {
gun[i].d = read();
gun[i].mul = gun[i].f * gun[i].d;
}
sort(gun + 1, gun + n + 1);
if (gun[n].mul - gun[1].mul <= k) {
cout << 0 << endl;
continue;
}
int l = 1, r = 1;
int ans = ll_INF;
while (l <= n) {
if (check(l, r)) {
ans = min(ans, n - (r - l + 1));
r++;
if (r > n) break;
} else {
l++;
if (r < l) r = l;
}
}
if (ans == ll_INF) cout << n << endl;
else cout << ans << endl;
}
return 0;
}