一些思维题(二)
Problem
CF1486 B. Eastern Exhibition
题意:给n个点,要选一个地方建一个最优点(最优点不必和n个点的位置都不同,所有点的位置用两个整数坐标x和y表示)
定义最优点为n个点到这个点的距离之和最小,距离的定义为|x1 - x2| + |y1 - y2|,输出一共有多少个最优点
1<=n<=1000; 0<=xi,yi<=1e9
CF1486 D. Max Median
题意:给一个数列a[n],要找到一个长度大于等于 k 的区间[L, R],称a[L],a[L+1].....a[R]为子数列, 子数列的长度len = R - L + 1
子数列的中位数 mid 为把子数列排序,第 len / 2(向下取整) 大的数,求子数列中位数最大是多少
1<=k<=n<=2e5;1<=ai<=n
CF1501 C. Going Home
给一个整数数列a[n],找出4个不同的数x,y,z,w,满足a[x] + a[y] == a[z] + a[w]
4<=n<=2e5; 1<=a[i]<=2.5e6
CF1501 D. Two chandeliers
给两个数列a[n],b[m]以及一个数 k
一个数列中所有数互不相同
现在把两个数列无限循环
比如把 a{1,4,3,2} ,b{2,3,1,5} 变成 a{1,4,3,2,1,4,3,2,1,4.....} b{2,3,1,5,2,3,1,5,2,3......}
题目保证a数组和b数组不同,那么无限循环后的两个数组肯定有无限个位置是a[ i ] != b[ i ]的
要输出第k个a[ i ] != b[ i ]的位置
1<=n,m<=5e5; 1<=k<=1e12;
2<=a[i],b[i]<= 2 * max(n,m);
Solution
CF1486 B. Eastern Exhibition
此题为二维平面上的题目,我们可以先考虑一个简化成一维的题目:一个数轴上有n个点,要找有多少个最优点
这里有个很显然的结论:如果n为奇数,那么最优点只有一个,就是坐标为n个点的中位数的那个点
如果n为偶数,那么最优点就是第n/2个点到第n/2+1个点之间的这一段上的任意一点
数学上我们学过|2 - x| + |6 - x| 的最小值就是x取[2,6]的时候,如果是多个点,思考一下,那么最小值就是x取最中间的那两个点之间那段闭区间(结论)
至于二维平面上的n个点,我们可以发现最优点可以满足:
一、n个点的x坐标全部变成最优点的x坐标的花费是最优的
二、n个点的y坐标全部变成最优点的y坐标的花费是最优的
把满足条件一的点记为点集1,把满足条件二的点记为点集2,最优点就是这两个点的并集
做法就是把n个点映射到x轴上,求出最优点的x的范围,同理求初最优点的y的范围
代码:
sort(x+1,x+n+1);
sort(y+1,y+n+1);
long long a,b,ans;
if(n % 2) a = b = 1;
else{
a = x[n/2+1] - x[n/2] + 1;
b = y[n/2+1] - y[n/2] + 1;
}
ans = a * b;
cout << ans << endl;
CF1486 D. Max Median
这题可以比较快察觉到用二分答案,如果直接求的话实在难想,可以有非常多长度>=k的区间,怎么直接求啊
用二分答案的话,那就是每次取一个mid,判断ans是否>=mid
怎么判断?
先考虑怎么不排序来判断一个区间的中位数是否>=mid,做法是看区间内<=mid的数有多少个,小于等于mid的数的个数 如果>= 区间长度/2,那么中位数就>=mid
再来考虑如何快速算一个区间内有多少个数小于等于mid
可以先预处理前缀和,sum[i]表示[1, i ]区间内小于等于mid的数的个数
但是这样还是不能做...
更高明的做法,把原数组中小于等于k的置为1,大于k的置为-1,那么如果一段区间中1的个数>= -1 的个数,这个区间的中位数就>=k,且这段区间的数之和就>=0
然后我们用求出最大连续和(注意区间长度 >= k),并看它是否>=0
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 2e5+7;
int a[MAXN];
int b[MAXN];
int su[MAXN];
int st[MAXN];
int n,k;
bool check(int med){
for(int i = 1;i <= n;i++){
if(a[i] < med) b[i] = -1;
else b[i] = 1;
}
for(int i = 1;i <= n;i++){
su[i] = su[i-1] + b[i];
}
int l,r;
l = r = 0;
int mi = 0;
for(int i = 1;i <= n; i++){
if(i < k){
continue;
}
mi = min(mi,su[i-k]);
if(su[i] > mi)return true;
}
return false;
}
int main()
{
cin>>n>>k;
for(int i = 1;i <= n;i++){
scanf("%d",&a[i]);
}
int l = 1,r = n + 1,mid;
while(l < r){
mid = l + r >> 1;
if(check(mid)) l = mid + 1;
else r = mid;
}
l--;
cout<<l<<endl;
return 0;
}
CF1501 C. Going Home
枚举每一对a[i],a[j],求出a[i] + a[j],很显然n^2会炸,
但是a[ i ]<=2.5e6,a[i] + a[j]的值域就只有5e6了
由于值域不大,我们可以用set在权值上存数对
#include<iostream>
#include<algorithm>
#include<cstdio>
#include<set>
#include<vector>
using namespace std;
const int MAXN = 5e6 + 7;
struct PA {
int x, y;
PA(int X, int Y): x(X), y(Y) {}
bool operator < (const PA& o) const {
return x < o.x;
}
};
set<PA> AB[MAXN];
struct NUM {
int v, id;
}a[MAXN];
bool cmp(NUM a, NUM b) {
if(b.v != a.v) return b.v > a.v;
return b.id > a.id;
}
bool check(int x1, int y1, int x2, int y2) {
if (x1 == x2 || y1 == y2|| x1 == y2 || y1 == x2)return false;
return true;
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
scanf("%d", &a[i].v);
a[i].id = i;
}
sort(a + 1, a + n + 1, cmp);
for (int i = 1; i <= n; i++) {
for (int j = i + 1; j <= n; j++) {
int su = a[i].v + a[j].v;
int x1 = a[i].id, y1 = a[j].id;
if (AB[su].size()) {
for (set<PA>::iterator it = AB[su].begin(); it != AB[su].end(); it++) {
int x2 = it->x, y2 = it->y;
if (check(x1, y1, x2, y2)) {
cout << "YES\n" << x1 << " " << y1 << " " << x2 << " " << y2 << "\n";
return 0;
}
}
}
AB[su].insert(PA(a[i].id, a[j].id));
}
}
cout << "NO\n";
return 0;
}
CF1501 D. Two chandeliers
也是很容易想到二分答案的一题
二分答案pos,然后check [1,pos] 里有多少个位置是a[ i ] != b[ i ]
怎么算?
可以想到计算贡献的方法,对于原a数组里的每一位a[i],都计算出这一位的贡献,然后把全部贡献加起来
对于a[i],我们怎么计算这位的贡献?
我们可以算这一位在[1,pos]里出现的次数, 比如a{2,3,1} ,无限延长成a{2,3,1,2,3,1,2,3,1...}, 假设pos为8,那么 a[1] 就在a{2,3,1,2,3,1,2,3}里出现了3次,a[2]在[1,pos]里出现了3次,a[3]在[1,pos]里出现了2次
a[1]的贡献就是a[1]出现的次数 减去 a[1]出现时且a[ i ] == b[ i ]的次数
要算a[1]出现时且a[ i ] == b[ i ]的次数,我们就要在 b 数列里找到和a[1]相同的数,然后进行推算
举例:
a{2,3,1,4}, b{1,2,3}
a[1] == b[2],a[1] == a[5] == a[9] == .... , b[2] == b[5] == b[8] == ...
这就变成了一个数论问题,一个人在 i 点起跳,另一个人在 j 点起跳,第一个人是n格n格地跳,第二个人是m格m格地跳,问在[1, pos]里有多少格是他们共同跳到过的
而两个人从同一位置开始跳,1号是n格n格跳,2号是m格m格地跳,有如下性质:
性质一:两人都能跳到的格子的位置为k * lcm(n,m)
性质二:一号从一个两人都能跳到的位置,跳到下一个两人都能跳到的位置,需要跳m / gcd(n, m)次,而二号需要n / gcd(n,m)次
性质三:如果一个人在长度为m格的环里n格n格地跳,把所有能跳到的格子染上颜色,相邻两个涂上颜色的点相差gcd(n,m)格,
这个人从一个位置再次跳到这个位置需要跳m / gcd(n,m)次,也就是说:记A[i]为n * i % m,A[i]这个数组是有长度为m / gcd(n,m)的循环节的
也就是这两个人从同一位置起跳,一号能跳到的任意一个位置x,和二号能跳到的任意一个位置y,满足|x - y| 是gcd(n,m)的倍数,也就是这两个人相差的距离只能是gcd(n,m)的倍数
我们把这个过程模拟一遍,就能知道一号相差二号x个距离,一号至少需要跳多少次
用代码写就是:
int x = 0;(初始两人相差为0)need[0] = 0(要使两人距离为0,一号需要跳0次)
for( int i = 1;i < m / gcd(n,m); i++){//一号跳m / gcd(n,m)次就又跳到两人都能跳到的位置
x += n; x %= m;
need[x] = i ; (要使两人距离为x,一号需要跳 i 次)
}
代码:
#include<iostream>
#include<algorithm>
#include<cstdio>
using namespace std;
const int MAXN = 1e6 + 7;
int a[MAXN], b[MAXN];
int pos1[MAXN],pos2[MAXN];
long long cha[MAXN];
const long long INF = 1e18 + 7;
long long n, m;
long long k;
long long gb;
bool ok(long long x) {
long long res = 0;
if (x == 0)return false;
long long cs = x / n;
long long re = x % n;
for (int i = 1; i <= n; i++) {
long long ics = cs;
if(i <= re) ics++;//ics是a[i]出现的次数
res += ics;
if (!ics) break;
if(pos2[a[i]]) {
long long p1 = i;
long long p2 = pos2[a[i]];
long long ca = p2 - p1;
ca = (ca % m + m) % m;
long long de = cha[ca];//第一个人要跳de次跳到第一个共同跳到的格子
long long ccs = ics - 1;//第一个人能跳的次数
if (ccs >= de) {
ccs -= de;
res--;
res -= ccs / gb;
}
}
if (res >= k)return true;
}
if (res >= k)return true;
return false;
}
int main()
{
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) scanf("%d", &a[i]);
for (int i = 1; i <= m; i++) scanf("%d", &b[i]);
if (n > m) {
swap(n, m);
swap(a, b);
}
for (int i = 1; i <= n; i++) pos1[a[i]] = i;
for (int i = 1; i <= m; i++) pos2[b[i]] = i;
for (int i = 0; i < m; i++) cha[i] = INF;
cha[0] = 0;
int x = 0;
for (int i = 1; i <= max(n,m); i++) {
x = (x+n) % m;
gb++;
if (cha[x] != INF) break;
cha[x] = (long long)i;//跳i次能跳到对m取膜为x的地方
}
long long l = 0, r = INF;
while (l < r) {
long long mid = l + r >> 1;
if (ok(mid)) r = mid;
else l = mid + (long long)1;
}
cout << l << endl;
return 0;
}

浙公网安备 33010602011771号