Brainman

Time Limit: 1000MS   Memory Limit: 30000K
Total Submissions: 10575   Accepted: 5489

Description

Background
Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted 246 toothpicks spilled all over the floor in an instant just by glancing at them. And he can even count Poker cards. Charlie would love to be able to do cool things like that, too. He wants to beat his brother in a similar task.

Problem
Here's what Charlie thinks of. Imagine you get a sequence of N numbers. The goal is to move the numbers around so that at the end the sequence is ordered. The only operation allowed is to swap two adjacent numbers. Let us try an example:Start with: 2 8 0 3
swap  (2 8) 8 2 0 3
swap  (2 0) 8 0 2 3
swap  (2 3) 8 0 3 2
swap  (8 0) 0 8 3 2
swap  (8 3) 0 3 8 2
swap  (8 2) 0 3 2 8
swap  (3 2) 0 2 3 8
swap  (3 8) 0 2 8 3
swap  (8 3) 0 2 3 8So the sequence (2 8 0 3) can be sorted with nine swaps of adjacent numbers. However, it is even possible to sort it with three such swaps:Start with: 2 8 0 3
swap  (8 0) 2 0 8 3
swap  (2 0) 0 2 8 3
swap  (8 3) 0 2 3 8The question is: What is the minimum number of swaps of adjacent numbers to sort a given sequence?Since Charlie does not have Raymond's mental capabilities, he decides to cheat. Here is where you come into play. He asks you to write a computer program for him that answers the question. Rest assured he will pay a very good prize for it.

Input

The first line contains the number of scenarios.
For every scenario, you are given a line containing first the length N (1 <= N <= 1000) of the sequence,followed by the N elements of the sequence (each element is an integer in [-1000000, 1000000]). All numbers in this line are separated by single blanks.

Output

Start the output for every scenario with a line containing "Scenario #i:", where i is the number of the scenario starting at 1. Then print a single line containing the minimal number of swaps of adjacent numbers that are necessary to sort the given sequence. Terminate the output for the scenario with a blank line.

Sample Input

4
4 2 8 0 3
10 0 1 2 3 4 5 6 7 8 9
6 -42 23 6 28 -100 65537
5 0 0 0 0 0

Sample Output

Scenario #1:
3 Scenario #2:
0 Scenario #3:
5 Scenario #4:
0

Source

TUD Programming Contest 2003, Darmstadt, Germany
题目链接:http://poj.org/problem?id=1804
分析:求逆序数的大水题,虽然这题数据给的范围很小,暴力都可以过,写不来的时候直接暴力就可以过,如果数据改大了就必然TL,想想看还有啥方法!
<1>.暴力大法,时间复杂度为O(n^2)
<2>.归并排序法,时间复杂度为O(n*log n)
<3>.线段树单点更新,时间复杂度为O(log n)
第一种暴力大法,请看AC代码:
 #include <iostream>
#include <stdio.h>
using namespace std;
const int N=;
int a[N],b[N];
int main()
{
int n;
scanf("%d",&n);
for(int k=;k<=n;k++)
{
int m;
scanf("%d",&m);
for(int i=;i<=m;i++)
scanf("%d",&a[i]);
int ans=;
for(int i=;i<=m;i++)
for(int j=i+;j<=m;j++)
if(a[i]>a[j])
ans++;
printf("Scenario #%d:\n%d\n\n",k,ans);
}
return ;
}

第二种归并排序, 对2个已经排好序的数列,进行再排序,只需要把2个数列,从头到尾,按顺序,谁小,谁就先进入tmp数组, 最后tmp数组一定排好序了,然后把TMP数组的元素复制回原数组中即可。

同理,如果我们知道2个子序列的逆序对数量,是否可以通过归并排序一样,求出整体的数量呢?显然是可以的。

这里有一个地方,当左边的数列的a[k]要进tmp数组了, 这个时候,如果右边的指针指向a+mid+p,就说明a[k]比a[mid+1]...a[mid + 2]..a[mid+3].....a[mid+p]都要大!【重要】

也就是说,对于a[k]而言,整个数列中, mid+ mid+2...mid+p都在k后面,同时a[k]比a[mid+1],a[mid+2]...a[mid+p]都要大。 那么显然是增加逆序对数量的。 通过整个方法,计算出整个逆序对的数量即可。

下面给出AC代码:

 #include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int max_n = + ; int n, a[max_n];
int tmp[max_n], ans; void merge(int *a, int *tmp, int l, int mid, int r)
{
if (l >= r) return;
int i = l, j = mid + , k = ;
int count = , flag = ;
while (i <= mid && j <= r)
{
if (a[i] <= a[j])
{
tmp[k ++] = a[i++];
ans += j - mid - ;
}else tmp[k ++ ] = a[j++];
}
while (i <= mid) tmp[k ++] = a[i++], ans += r- mid;
while (j <= r) tmp[k ++] = a[j++];
for (i = ; i != k; ++ i) a[l + i] = tmp[i];
} void mergesort(int *a, int *tmp, int l, int r)
{
if (l >= r) return;
int mid = (l + r) / ;
mergesort(a, tmp, l, mid);
mergesort(a, tmp , mid + , r);
merge(a, tmp, l, mid, r);
} int main()
{
int tt;
scanf("%d", &tt);
for (int i = ; i <= tt; ++ i)
{
cout<<"Scenario #"<<i<<":"<<endl;
scanf("%d", &n);
ans = ;
for (int i = ; i != n; ++ i) scanf("%d", &a[i]);
mergesort(a, tmp, , n - );
cout<<ans<<endl<<endl;
}
}

第三种线段树单点更新

n个数扫一边 遇到一个值i加到线段树上  查(i+1,n),再求和输出!
下面给出AC代码:
 #include <map>
#include <iostream>
#include <set>
#include <cstdio>
#include <cstdlib>
using namespace std; const int max_n = + ; int n;
int a[max_n], count;
map<int, int>G;
map<int, int>::iterator it; struct node
{
int cd, key;
int ls, rs;
int L, R;
node():L(),R(),ls(),rs(),cd(),key(){};
void clear()
{
cd = key = ;
}
}t[max_n * ];
int tail = ; void init()
{
for (int i = ; i != max_n * ; ++ i) t[i].clear();
G.clear();
scanf("%d", &n);
for (int i = ; i != n; ++ i)
{
scanf("%d", &a[i]);
G[a[i]] = ;
}
count = ;
for (it = G.begin(); it != G.end(); ++ it) it -> second = ++ count;
} void make_tree(int now, int LL, int RR)
{
t[now].L = LL;
t[now].R = RR;
if (LL == RR) return;
int mid = (LL + RR)/ ;
make_tree(t[now].ls = ++ tail, LL, mid);
make_tree(t[now].rs = ++ tail, mid + , RR);
} void tran(int now)
{
int left_son = t[now].ls, right_son = t[now].rs;
t[left_son].cd += t[now].cd;
t[right_son].cd += t[now].cd;
t[now].key += t[now].cd;
t[now].cd = ;
} void ins(int now, int LL, int RR)
{ tran(now);
if (t[now].L == LL && t[now].R == RR)
{
t[now].cd ++;
return;
}
t[now].key ++;
int mid = (t[now].L + t[now].R) / ;
if (RR <= mid) {ins(t[now].ls, LL, RR); return;}
if (mid < LL) {ins(t[now].rs, LL, RR); return;}
ins(t[now].ls, LL, mid);
ins(t[now].rs, mid + , RR);
} int find(int now, int LL, int RR)//因为题目的特殊性,只会找一个……
{
tran(now);
if (t[now].L == LL && t[now].R == RR) return t[now].key;
int mid = (t[now].L + t[now].R) / ;
if (RR <= mid) return find(t[now].ls, LL, RR);
if (mid < LL) return find(t[now].rs, LL, RR);
cout<<"wtf?"<<endl;
} void doit()
{
int ans=;
for (int i = ; i != n; ++ i)
{
int num = G[a[i]];
ans += find(, num + , num + );
ins(, , num);
}
cout<<ans<<endl;
} int main()
{
int tt;
scanf("%d",&tt);
make_tree(, , );
for (int i = ; i <= tt; ++ i)
{
cout<<"Scenario #"<<i<<":"<<endl;
init();
doit();
cout<<endl;
}
}

另外还有几种好办法,贴一下
第四种:树状数组

树状数组, 其实和线段树道理一样。 但是对于树状数组,我会单独开一张好好研究哒。 这里就贴一下速度,并没有比线段树快很多……也许我的写法不好?【如果对树状数组有疑惑,可以看我下一篇文章,我会带领你们好好学会树状数组这个神奇的东西~】

 #include <cstdio>
#include <cstdlib>
#include <map>
#include <cstring>
using namespace std;
#define lowbit(k) ((k)&(-k)) const int max_n = + ;
int n, a[max_n], s[max_n];
map<int, int>G;
map<int, int>::iterator it;
int count;
void init()
{
scanf("%d", &n);
G.clear();
count = ;
memset(s, , sizeof(s));
for (int i = ; i != n; ++ i)
{
scanf("%d", &a[i]);
G[a[i]] = ;
}
for (it = G.begin(); it != G.end(); ++ it) it -> second = ++ count;
} void ins(int k)
{
s[k] += ;
while ((k += lowbit(k)) <= ) s[k] += ;
} int ask(int k)//1..k的和
{
int tot = s[k];
while (k -= lowbit(k)) tot += s[k];
return tot;
} void doit()
{
int ans = ;
for (int i = ; i != n; ++ i)
{
int num = G[a[i]];
ans += ask(count) - ask(num);
ins(num);
}
printf("%d\n",ans);
} int main()
{
int tt;
scanf("%d", &tt);
for (int i = ; i <= tt; ++ i)
{
printf("Scenario #%d:\n",i);
init();
doit();
printf("\n");
}
}

第五种:平衡树

只要查找,当前在树中,有多少个数字比a[k]要大, 因为是按顺序插入的,所以这个数字的数量就是逆序对的个数

这里有一个小技巧,如果平衡树每次要删的话很麻烦,直接用写成struct,然后新的树就new,最后delete掉即可~

 #include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
const int max_n = + ; int n;
const int maxint = 0x7fffffff; struct node
{
node *c[];
int key;
int size;
node():key(),size()
{
c[] = c[] = this;
}
node(int KEY_, node *a0, node *a1):
key(KEY_){c[] =a0, c[]=a1;}
node* rz(){return size = c[]->size + c[]->size + , this;}
}Tnull, *null=&Tnull; struct splay
{
node *root;
splay()
{
root = (new node(*null)) -> rz();
root -> key = maxint;
}
void zig(int d)
{
node *t = root -> c[d];
root -> c[d] = null -> c[d];
null -> c[d] = root;
root = t;
}
void zigzig(int d)
{
node *t = root -> c[d] -> c[d];
root -> c[d] -> c[d] = null -> c[d];
null -> c[d] = root -> c[d];
root -> c[d] = null -> c[d] -> c[!d];
null -> c[d] -> c[!d] = root -> rz();
root = t;
} void finish(int d)
{
node *t = null -> c[d], *p = root -> c[!d];
while (t != null)
{
t = null -> c[d] -> c[d];
null -> c[d] -> c[d] = p;
p = null -> c[d] -> rz();
null -> c[d] = t;
}
root -> c[!d] = p;
}
void select(int k)//谁有k个儿子
{
int t;
while ()
{
bool d = k > (t = root -> c[] -> size);
if (k == t || root -> c[d] == null) break;
if (d) k -= t + ;
bool dd = k > (t = root -> c[d] -> c[] -> size);
if (k == t || root -> c[d] -> c[dd] == null){zig(d); break;}
if (dd) k -= t + ;
d != dd ? zig(d), zig(dd) : zigzig(d);
}
finish(), finish();
root -> rz();
}
void search(int x)
{
while ()
{
bool d = x > root -> key;
if (root -> c[d] == null) break;
bool dd = x > root -> c[d] -> key;
if (root -> c[d] -> c[dd] == null){zig(d); break;}
d != dd ? zig(d), zig(dd) : zigzig(dd);
}
finish(), finish();
root -> rz();
if (x > root -> key) select(root -> c[] -> size + );
} void ins(int x)
{
search(x);
node *oldroot = root;
root = new node(x, oldroot -> c[],oldroot);
oldroot -> c[] = null;
oldroot -> rz();
root -> rz();
}
int sel(int k){return select(k - ), root -> key;}
int ran(int x){return search(x), root -> c[] -> size + ;}
}*sp; int main()
{
int tt;
scanf("%d", &tt);
for (int i = ; i <= tt; ++ i)
{
sp = new splay;
cout<<"Scenario #"<<i<<":"<<endl;
scanf("%d", &n);
int ans = ;
int tmp;
for (int i = ; i != n; ++ i)
{
scanf("%d", &tmp);
tmp = - tmp;
ans += sp -> ran(tmp) - ;
//cout<<sp.ran(tmp) - 1<<endl;
sp -> ins(tmp);
}
delete sp;
cout<<ans<<endl<<endl;
}
}

POJ 1804 Brainman(5种解法,好题,【暴力】,【归并排序】,【线段树单点更新】,【树状数组】,【平衡树】)的更多相关文章

  1. poj 2892---Tunnel Warfare(线段树单点更新、区间合并)

    题目链接 Description During the War of Resistance Against Japan, tunnel warfare was carried out extensiv ...

  2. HDU 1166 敌兵布阵(线段树单点更新,板子题)

    敌兵布阵 Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others) Total Submi ...

  3. POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和)

    POJ.3321 Apple Tree ( DFS序 线段树 单点更新 区间求和) 题意分析 卡卡屋前有一株苹果树,每年秋天,树上长了许多苹果.卡卡很喜欢苹果.树上有N个节点,卡卡给他们编号1到N,根 ...

  4. POJ.2299 Ultra-QuickSort (线段树 单点更新 区间求和 逆序对 离散化)

    POJ.2299 Ultra-QuickSort (线段树 单点更新 区间求和 逆序对 离散化) 题意分析 前置技能 线段树求逆序对 离散化 线段树求逆序对已经说过了,具体方法请看这里 离散化 有些数 ...

  5. CDOJ 1073 线段树 单点更新+区间查询 水题

    H - 秋实大哥与线段树 Time Limit:1000MS     Memory Limit:65535KB     64bit IO Format:%lld & %llu Submit S ...

  6. POJ 1804 Brainman

    Brainman Time Limit: 1000MS   Memory Limit: 30000K Total Submissions: 7787   Accepted: 4247 Descript ...

  7. POJ 1804 Brainman(归并排序)

    传送门 Description Background Raymond Babbitt drives his brother Charlie mad. Recently Raymond counted ...

  8. POJ 2892 Tunnel Warfare(线段树单点更新区间合并)

    Tunnel Warfare Time Limit: 1000MS   Memory Limit: 131072K Total Submissions: 7876   Accepted: 3259 D ...

  9. poj 2828(线段树单点更新)

    Buy Tickets Time Limit: 4000MS   Memory Limit: 65536K Total Submissions: 18561   Accepted: 9209 Desc ...

随机推荐

  1. ES6之遍历器(Iterator)

    什么是Iterator?他是一种接口,为各种不同的数据结构提供统一的访问机制,任何数据结构只要部署上Iterator接口就可以完成遍历操作(PS:个人认为他的这个遍历就是c语言里面的指针),他的作用有 ...

  2. 对Spring与SpringMVC的理解

    Spring 在我的Spring --简介及环境搭建跑通Hello提到关于Spring的基本结构与功能 SpringMVC 先上一张SpringMVC的流程图 Spring MVC 是一个模型 - 视 ...

  3. c#发送get请求

    c#发送get请求爬取网页 关键点:在控制台中发送一个get请求,将响应的内容写入文件流中保存html格式 static void Main(string[] args) { string url = ...

  4. bzoj 4196: [Noi2015]软件包管理器

    Description Linux用户和OSX用户一定对软件包管理器不会陌生.通过软件包管理器,你可以通过一行命令安装某一个软件包,然后软件包管理器会帮助你从软件源下载软件包,同时自动解决所有的依赖( ...

  5. js回到顶部------转载

    [1]锚点 使用锚点链接是一种简单的返回顶部的功能实现.该实现主要在页面顶部放置一个指定名称的锚点链接,然后在页面下方放置一个返回到该锚点的链接,用户点击该链接即可返回到该锚点所在的顶部位置 [注意] ...

  6. ConcurrentHashMap 从Java7 到 Java8的改变

    一.关于分段锁 集合框架很大程度减少了java程序员的重复劳动,然而,在Java多线程环境中,以线程安全的方式使用集合类是一个首先考虑的问题. 越来越多的程序员了解到了ConcurrentHashMa ...

  7. 操作Frame和IFrame中页面元素

    HTML <iframe> 标签 定义:iframe 元素会创建包含另外一个文档的内联框架(即行内框架). frame标签有frameset.frame.iframe三种,frameset ...

  8. Q:javax.comm 2.0 windows下Eclipse的配置

    @转自http://blog.csdn.net/zhuanghe_xing/article/details/7523744处 要在Windows下,对计算机的串口或并口等进行编程,可以选择使用Java ...

  9. Maven的下载、安装与环境配置

    在创建一个项目时,搭建环境往往是编写具体代码的先决条件,而获取到所有需要的jar包是其中的重中之重.起初,人们在需要jar包的时候总会在网上四处查找,而且如果不知道某jar包版本的更迭,写出的代码或许 ...

  10. 细说MyEclipse调试

     在程序出现问题时,我们需要找到并解决掉这些恼人的Bug,才能使程序顺利的运行下去.但是,当代码很多,程序很大的时候,找起来就很麻烦. 所以,我们需要借助工具——Eclipse/MyEclipse中的 ...