solve 4 (D H A E)
rank 29/72
1.赛时ac的题在下面补充思路解法(思路较复杂可以贴代码);
2.赛后补上的题在下面补充详细的思路,以及代码,要清晰;
3.补到银牌题。
A - Banks (通过)
题意:通过多少步操作能把序列所有值都变成非负。
思路:
伪证明一下。对于一个负数a[pos],要把它变成正数,左右可能产生2个负数,也可能产生1个负数,也可能不产生负数。
对于新产生的负数,我们发现,如果取之变成正数,那么a[pos]的值依旧是正数。(此处易证。)
因此,要消除一个负数,同时消除掉他带来的新的负数,只需要经过较少次操作便可完成。
可以暴力实现。
D - Frame (通过)
<dzc>
题意:给出n,m,k,问你能不能用长度为k的木条拼出n*m的矩形框来?
思路:模拟即可,从某一个角开始一条边一条边的拼即可,每次用当前需要拼的边的长度对k取余,
如果余数>1,那么一定不能拼成
如果余数=1,那么可以继续拼下一条边,且下一条边是完整的
如果余数=0,那么可以继续拼下一条边,且下一条边的长度是下一条边边长-1
注意:需要分别以两个相邻顶点模拟,因为只有从对角的两个顶点开始铺才是等价的,相邻顶点并不等价,还有如果当前拼的是最后一条边,那么长度还要额外-1
E - Points (通过)
题意:给n个点,构造出一个凸包,使得n个点都严格在凸包里面,并且凸包的边只能是平行或垂直x轴,或与x轴夹角呈45°。
思路:
给定的每个点都要在凸包内部,那么我们可以把每个点的上下左右四个点记录下来,用它们构造一个凸包
(注意点集要去重。)
得到凸包后,有考虑边是否是平行或垂直x轴的边。
(1) 平行或垂直x轴:这条边不需要重新构造。
(2) 否则:这条线段可以转换成一条折线(夹角为135°)。
一、题意
给一个数字$N$,一个是数字$K$,一个初始序列,$a_0, a_1, a_2, \cdots, a_{N-1}$;接下来,有$K$次修改操作,每次给一个区间$[L, R]$,表示将区间$[L, R]$之间的元素加$1$。然后输出整个序列的中位数。
二、思路
分块的思想
把区间$[0, N)$分成$\sqrt{N}$个子区间,每个区间内$\sqrt{N}$个元素。当然,如果$N$不是完全平方数,要多用一个块,俗称桶子(下同)。对每个桶子排序,同时记录每个桶子内的整体加的值$sum[i]$,第一个大于中位数(一开始可以计算出来)的数字相对整个序列的位置(因为桶子内元素是有序的)$le[i]$。注意,序列的数据结构不能是int,因为对于每个数字,除了值之外,还需要记录它的初始下标。
每次区间修改时,枚举所有与$[L, R+1)$相交的区间$[l, r)$,有两种情况:
(1)如果$[l, r) \subset [L, R+1)$,直接将$[l, r)$整体$+1$;
(2)如果$[l, r) \not\subset [L, R+1)$且$[l, r) \cap [L, R+1) \ne \varnothing$,暴力修改。
对于情况(1),区间整体$+1$后该桶子内的序列仍然有序;对于情况(2),区间$[l, r)$内满足$L \le a_i \le R(l \le i < r)$的所有元素的值都增加了$1$,这将导致区间$[l, r)$无序,但是,这样的无序序列可以用$O(\sqrt{N})$的时间复杂度解决。
修改完成之后,枚举每一个桶子,通过数组$le$统计每个桶子内不大于中位数的个数$cnt$(注意,统计的时候,$le[i]$也要随着改变。改变的时候,只需朴素的查找即可),如果$cnt \le \frac{N}{2}$,说明整个序列中最多只有$\frac{N}{2}$个数不大于中位数,而中位数必定是第$\frac{N+1}{2}$个小的数(从1开始记),所以,不大于中位数的元素个数至少为$\frac{N+1}{2}$。所以,说明,整个序列的中位数已经$+1$了,那么,把中位数++即可,然后输出。
要注意的是,
(1)题目中给定的修改区间的下标从$1$开始,而分块法中,下标要从$0$开始(当然也可以从$1$开始,但是,编码麻烦不止一点点),所以,要把修改区间左端点$L-1$;
(2)暴力修改右边界所在的区间时要注意,最右端点是$min(N, r)$;
(3)通过数组$le$统计每个桶子内不大于中位数的个数时,实际上统计的是$median - sum[i]$,因为第$i$个区间的元素$e$的真实值是$e + sum[i]$,而不是$e$。但是在区间整体修改的时候,区间内所有的元素并没有被修改。
三、总结
1、每个桶子内的容量$c = \lfloor\sqrt{N}\rfloor$,桶子的个数$n = \lceil\frac{N}{c}\rceil$;
2、分块处理中,待修改区间的右边界所在区间的右端点是$min(N, r)$;
3、void nth_element (RandomAccessIterator first, RandomAccessIterator nth, RandomAccessIterator last)函数的用法:
参数一:序列初始位置;
参数二:第(nth-first)小(从0开始记)的元素会被放置的位置;
参数三:序列末位置(不包括);
比如:把序列(不要求有序)$a_0, a_1, a_2, \cdots, a_{N-1}$的中位数(第$\frac{N}{2}$小)放在$\frac{N}{2}$位置:
nth_element(a, a + N / 2, a + N);
那么,$a[N/2]$就是整个序列中第$\frac{N}{2}$小的元素。
四、源代码
#include<bits/stdc++.h> using namespace std; #define mk(x, y) make_pair(x, y) #define MAXN 60010 #define SQRTN 300 #define INF 0x3f3f3f3f typedef pair<int, int> PII; int N, K, sum[SQRTN], le[SQRTN]; PII a[MAXN], aux[MAXN]; void update(int ul, int ur, int l, int r){ for(int i = l;i < r;++i){ if(a[i].second >= ul && a[i].second < ur)a[i].first++; } int pre = -1, idx; for(int i = l;i < r;++i){ if(a[i].first < pre)swap(a[idx++], a[i]); else if(a[i].first > pre)idx = i; pre = a[i].first; } } int main() { // freopen("input.txt", "r", stdin); // freopen("output2.txt", "w", stdout); int cap, num, ul, ur, med; while(~scanf("%d%d", &N, &K)) { if(N == 0 || K == 0)continue; memset(sum, 0, sizeof(sum)); cap = floor(sqrt(N)); num = ceil(double(N) / cap); for(int i = 0; i < N; ++i)scanf("%d", &a[i].first), a[i].second = i; for(int i = 0; i < num; ++i)sort(a + i * cap, a + min(N, (i + 1) * cap)); copy(a, a + N, aux); nth_element(aux, aux + N / 2, aux + N); med = aux[N / 2].first; for(int i = 0; i < num; ++i)le[i] = upper_bound(a + i * cap, a + min(N, (i + 1) * cap), mk(med, INF)) - a; while(K--) { scanf("%d%d", &ul, &ur); ul--; int b1 = ul / cap, b2 = ur / cap; for(int i = b1 + 1; i <= b2 - 1; ++i)sum[i]++; update(ul, ur, b1 * cap, min(N, (b1 + 1) * cap)); if(b1 != b2)update(ul, ur, b2 * cap, min(N, (b2 + 1) * cap)); int cnt = 0; for(int i = 0; i < num; ++i) { int x = med - sum[i]; int& p = le[i]; while(p > i * cap && a[p - 1].first > x)--p; while(p < min(N, (i + 1) * cap) && a[p].first <= x)++p; cnt += p - i * cap; } //median has increased by one. if(cnt <= N / 2)++med; cout << med << endl; } } return 0; }
H - Triples (通过)
<qj>
题意:求区间内有多少组(x,y,z)满足公式
思路:
预处理所有数字的高次方,注意对1e9+7取余。
由于数据范围很小,暴力枚举所有的n,x,y,z,如果满足条件,答案+1 。
I - Multi-Machine Scheduling of Two Applications
题意:在n*n的区域里有a个雷达站,雷达站覆盖区域是三角形,重叠覆盖信号会冲突,求多少点有信号。
思路:
由于n较大,a较小,不妨把三角形转换成若干条扫描线来考虑,同时,可以把a条线段离散化成a组 pair<l,r> 。
这样我们就把问题的时间复杂度降低为O(n*a)了。
这里还有一个小结论不难证明:
当前扫描线上,我们用a组<l,r>可以把线段划分成若干个小块,可证:第2k+1块被奇数条线段覆盖,而第2k块则被偶数条线段覆盖。(k=0,1,2,3,...)
(以上证明需要随便构造一组数据然后作图,很直观的。)
所以直接把所有奇数段的长度求出来,求和。
#include <bits/stdc++.h> using namespace std; struct T { int x,y,l,k; }ts[101],tt; int main(){ int n,a,arr[222],ans; while(scanf("%d%d",&n,&a)!=EOF){ for(int i=0;i<a;i++){ scanf("%d%d%d%d",&ts[i].x,&ts[i].y,&ts[i].l,&ts[i].k); } ans=0; int cnt; for(int i=0;i<n;i++){ cnt=0; for(int j=0;j<a;j++){ tt = ts[j]; if((tt.k&2)?(i>=tt.x||i<tt.x-tt.l):(i<tt.x||i>=tt.x+tt.l))continue; arr[cnt++]=tt.y; if(tt.k==0){ arr[cnt++]=max(tt.y-tt.l-tt.x+i,0); } else if(tt.k==1){ arr[cnt++]=min(tt.y+tt.l+tt.x-i,n); } else if(tt.k==2){ arr[cnt++]=min(tt.y+tt.l-tt.x+i+1,n); } else { arr[cnt++]=max(tt.y-tt.l+tt.x-i-1,0); } } arr[cnt++]=n; sort(arr,arr+cnt); for(int j=1;j<cnt;j+=2){//取奇数段 ans+=arr[j]-arr[j-1]; } } printf("%d\n",ans); } return 0; }