• 一些思维题(二)


    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;
    }
    

      

  • 相关阅读:
    程序猿之没事瞎吐槽
    iOS 打印日志的保存 (一)
    Xcode4.5 本地化,多语言设置
    css3渐变画斜线 demo
    关于JavaScript的一些记录
    Windows 10 自带输入法(微软拼音)繁体简体切换快捷键
    ng-class用法小记
    基于vue监听滚动事件,实现锚点链接平滑滚动
    总结继承的几种方式
    浅谈jQuery的内部框架结构,操作
  • 原文地址:https://www.cnblogs.com/ruanbaitql/p/14641833.html
Copyright © 2020-2023  润新知