一些思维题(二)

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
" << x1 << " " << y1 << " " << x2 << " " << y2 << "
";
						return 0;
					}
				}
			}
			AB[su].insert(PA(a[i].id, a[j].id));
		}
	}
	cout << "NO
";
	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;
}

  

原文地址:https://www.cnblogs.com/ruanbaitql/p/14641833.html