一些思维题(二)
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; }