APIO2015 八邻旁之桥
这道题看起来十分的不可做……可能是我数学太差智商太低了orz。
首先如果一个人的家和办公室在同一侧那就完全不用考虑,把结果记下来就行。
然后我们先考虑k=1的情况。因为只能建1座桥,那么我们发现(除去过桥一个单位长度不算)答案就是sigma(abs(ai-x) + abs(bi-x)),其中ai,bi表示区间的开始和末尾,这样我们发现区间的开始和末尾并没有本质上的区别……所以我们直接把所有端点都排个序然后直接找到中位数(r),答案就是sigma(r-ai) (i<=r) + sigma(ai - r) (i >= r) 然后实际中计算的时候非常的简洁,只要把所有小于中位数的答案加起来,把所有大于中位数的答案也加起来,两者差值+预处理结果就是答案。
那么k=2的时候怎么办呢?这个我又没想到……首先我们可以这么想,找到每个区间的中点,如果中点更靠近左边的桥那么就从左边过桥,否则从右边过桥(这个还是比较显然的)。于是我们先把所有区间按照l+r从小到大排序(中点坐标),再选择枚举一个分割线(分割线左边的全部从左边过桥,右边的从右边过桥),这样我们就把两边分成了两个k=1的情况。
之后的问题就是怎么快速计算。我们使用线段树维护中位数(r),维护区间内元素个数(k)和距离总和(s),这样的话一个区间的答案就是k*loc[r] - s + s1 - k1*loc[r],(前面是中位数左边的,后面是中位数右边的),loc记录这个点所对应的位置。然后分割线另一端的也一样,开两个线段树维护即可。
一开始首先把右边的树填满,之后分割线每移动一次,把对应的从右树中删除,加到左树里。
然后这题有俩坑,第一个是有可能区间的末尾比起点小,这时候要先swap一遍,第二个就是有可能不需要过河…………这样的时候直接特判输出就行…………
看一下代码。
#include<iostream> #include<cstdio> #include<cmath> #include<algorithm> #include<queue> #include<cstring> #include<utility> #include<map> #define pr pair<int,int> #define mp make_pair #define fi first #define sc second #define rep(i,a,n) for(int i = a;i <= n;i++) #define per(i,n,a) for(int i = n;i >= a;i--) #define enter putchar('\n') using namespace std; typedef long long ll; const int M = 100005; const int N = 10000005; const ll INF = 1000000000000000009; int read() { int ans = 0,op = 1; char ch = getchar(); while(ch < '0' || ch > '9') { if(ch == '-') op = -1; ch = getchar(); } while(ch >='0' && ch <= '9') { ans *= 10; ans += ch - '0'; ch = getchar(); } return ans * op; } int loc[M<<2],n,k,s,ter,tot,cnt,len; //tot records the number of loc,cnt records the number of segments(node) char s1[5],s2[5]; ll ans,minn = INF; struct node { int l,r; bool operator < (const node &g)const { return l + r < g.l + g.r; } }d[M<<2]; struct seg { ll v[M<<3],sz[M<<3]; void modify(int p,int l,int r,int pos,int x) { if(l == r) { v[p] += x * loc[pos]; sz[p] += x; return; } int mid = (l+r) >> 1; if(pos <= mid) modify(p<<1,l,mid,pos,x); else modify(p<<1|1,mid+1,r,pos,x); v[p] = v[p<<1] + v[p<<1|1]; sz[p] = sz[p<<1] + sz[p<<1|1]; } int find(int p,int l,int r,int x) { if(l == r) return l; int mid = (l+r) >> 1; if(x <= sz[p<<1]) return find(p<<1,l,mid,x); else return find(p<<1|1,mid+1,r,x - sz[p<<1]); } ll size(int p,int l,int r,int kl,int kr) { if(l == kl && r == kr) return sz[p]; int mid = (l+r) >> 1; if(kr <= mid) return size(p<<1,l,mid,kl,kr); else if(kl > mid) return size(p<<1|1,mid+1,r,kl,kr); else return size(p<<1,l,mid,kl,mid) + size(p<<1|1,mid+1,r,mid+1,kr); } ll sum(int p,int l,int r,int kl,int kr) { if(l == kl && r == kr) return v[p]; int mid = (l+r) >> 1; if(kr <= mid) return sum(p<<1,l,mid,kl,kr); else if(kl > mid) return sum(p<<1|1,mid+1,r,kl,kr); else return sum(p<<1,l,mid,kl,mid) + sum(p<<1|1,mid+1,r,mid+1,kr); } }t[2];//0 records left ,1 records right void work() { ll tot1 = 0,tot2 = 0; sort(loc+1,loc+1+tot); rep(i,1,tot>>1) tot1 += loc[i]; rep(i,(tot>>1)+1,tot) tot2 += loc[i]; printf("%lld\n",ans + tot2 - tot1); } void solve() { sort(d+1,d+1+cnt); sort(loc+1,loc+1+tot); len = unique(loc+1,loc+1+tot) - loc - 1; rep(i,1,cnt) { d[i].l = lower_bound(loc+1,loc+1+len,d[i].l) - loc; d[i].r = lower_bound(loc+1,loc+1+len,d[i].r) - loc; } rep(i,1,cnt) t[1].modify(1,1,len,d[i].l,1),t[1].modify(1,1,len,d[i].r,1); rep(i,1,cnt) { t[0].modify(1,1,len,d[i].l,1),t[0].modify(1,1,len,d[i].r,1); t[1].modify(1,1,len,d[i].l,-1),t[1].modify(1,1,len,d[i].r,-1); int r1 = t[0].find(1,1,len,i),r2 = t[1].find(1,1,len,cnt-i); ll d1 = t[0].size(1,1,len,1,r1) * loc[r1] - t[0].sum(1,1,len,1,r1) + t[0].sum(1,1,len,r1,len) - t[0].size(1,1,len,r1,len) * loc[r1]; ll d2 = t[1].size(1,1,len,1,r2) * loc[r2] - t[1].sum(1,1,len,1,r2) + t[1].sum(1,1,len,r2,len) - t[1].size(1,1,len,r2,len) * loc[r2]; minn = min(minn,d1+d2); } printf("%lld\n",minn + ans); } int main() { k = read(),n = read(); rep(i,1,n) { scanf("%s",s1),s = read(); scanf("%s",s2),ter = read(); if(s > ter) swap(s,ter); if(s1[0] == s2[0]) ans += ter-s; else ans++,loc[++tot] = s,loc[++tot] = ter,d[++cnt].l = s,d[cnt].r = ter; } if(!cnt) { printf("%lld\n",ans); exit(0); } if(k == 1) work(); else solve(); return 0; }