POJ 3067 Japan (树状数组 && 控制变量)
题意: 西海岸和东海岸有分别有n (1~n)个和m (1~m)个城市, 两个海岸的城市之间有k条公路连通, 公路会相交, 现在给出城市和公路的信息问你由这些公路组成的复杂交通有多少个交点 (如果两个条公路的起点或者终点相同那这两点不算做相交)
分析: 这里公路信息用(x, y)二元组来表示西海岸的x城市与东海岸的y城市相连, 首先自然想到给k个公路的信息按x或y排个序, 否则变量太乱不助于思考!先设想按x升序排序且先不管x相等的情况, 如果x是升序的, 那我在考虑第i条公路的时候是不是只要关心yi值是否比之前所有的点的y值大就行了, 如果比所有之前的点的y值都大, 则之前没有线yi相交, 否则有之前有j个比它大的则就将多产生j个交点。这样去控制变量的计算方法可以自然与求逆序对联系到一起, 细想如果我先按x升序再按y升序, 那按照上面所述的方法, 是不是只要求出此时y数列的逆序对个数即可?那如果x相等怎么办呢?很显然, 如果x相等, 由于题目说 (如果两个条公路的起点或者终点相同那这两点不算做相交) , 那这两条线必定不能产生交点, 即不能在在线计算的过程中产生逆序对, 那只要先处理y比较小的即可, 也就是为什么x相等就按y从小到大排序的理由。既然是求逆序对, 便用到树状数组了!
瞎想: 可以发现在用树状数组解决问题时候, 应该多去想如何控制变量或者使变量单一(排序或者....其他), 或者将其变成求逆序数的经典模型, 或者将区间(一般是两端点)转画成直角坐标系里的x,y等方法来寻求规律!
瞎搞: 当时想偷偷懒, 用个set< pair<int, int> >来存储, 的确也能做到, 不过TLE的我眼泪掉下来!还有就是最后的ans是交点个数, 可以很大, 我一开始缺用了int, 最后才发现啊!
#include<stdio.h> #include<iostream> #include<algorithm> #include<string> #include<string.h> #include<set> #define LL long long #define lowbit(i) (i&(-i)) using namespace std; int c[1001], n, k, m; inline void add(int i, int val) { while(i<=m){//由于是求y序列所以这里上限是m c[i] += val; i += lowbit(i); } } LL sum(int i) { LL ans = 0; while(i>0){ ans += c[i]; i -= lowbit(i); } return ans; } typedef struct stru { int x, y; }A; A arr[10000000]; bool cmp(const A fir, const A sec) { if(fir.x == sec.x) return fir.y < sec.y; return fir.x < sec.x; } int main(void) { int nCase; scanf("%d", &nCase); for(int t=1; t<=nCase; t++){ memset(c, 0, sizeof(c)); scanf("%d%d%d", &n, &m, &k); for(int i=0; i<k; i++){ scanf("%d%d", &arr[i].x, &arr[i].y); } sort(arr, arr+k, cmp);//先按x升序,若x相等则按y升序 LL ans = 0; //注意是long long for(int i=0; i<k; i++){//树状数组求y序列的逆序对,经典求法 ans += i - sum(arr[i].y);//这里不必避免参数为0的坑,因为最小为1 add(arr[i].y, 1); } printf("Test case %d: %lld\n", t, ans); } return 0; }