ACM - ICPC Regionals 2014 Bangkok 亚洲区域赛 国外某数学题

ACM-ICPC Live Archive Regionals 2014 >> Asia - Bangkok
6844 - Combination
=====

原题页面
PDF题面
Vjudge提交地址

艰辛坎坷的探索历程

设 $ {f(n)=C_{n}^{r}(n∈N^{ * },r∈[0,n])} $中奇数的个数。
题意是说,求出 $ \sum f(k),k∈[low,high] $
庞大的数据, $ n≤16\times{10^{11}}. $

step1

判断给定 $ n∈N^{ * } $时 $ C_n^r(r∈[0,n]) $的奇偶性。 不难往n,r的二进制表示方向考虑。
查阅他人博客获得结论: $ n{ & }r==r $时 $ C_n^r $为奇数。 然而本题中无需直接应用这一“定理”,所要做的是统计。
拿出纸笔推算发现,若n的二进制表示中有d个1,则 $ C_n^r(r∈[0,n]) $中存在 $ 2^d $个奇数。

step2

研究 $ f(n) $ 的递推关系及通项。 编写暴力程序 $ O(n^2) $ ,打表观察 $ n∈[0,64) $ 时 $ f(n) $ 的取值:
1
2
2 4
2 4 4 8
2 4 4 8 4 8 8 16
2 4 4 8 4 8 8 16 4 8 8 16 8 16 16 32
2 4 4 8 4 8 8 16 4 8 8 16 8 16 16 32 4 8 8 16 8 16 16 32 8 16 16 32 16 32 32 64
第一行 $ f(0)=1 $ 作为边界条件, 第二行 $ f(1)=2 $ ,随后各行的第k行的 $ 2^{k-2} $ 个数分别表示 $ f(2^{k-2}),f(2^{k-2}+1),…,f(2^{k-1}-1) $ 。
通过不懈的努力,观察发现从第3行开始,每行的前半部分与前一行完全一致,每行的后半部分的各个f值恰为前半部分对应位置的f值的2倍。

step3

出于数列直觉,我们尝试求解各行的和构成的数列的递推以及通项。
$ a_1=1 $
$ a_2=2 $
$ a_3=a_2+2\times{a_2}=3\times{a_2}=6 $

$ a_n=a_{n-1}+2\times{a_{n-1}}=3\times{a_{n-1}}(n\geq 3) $(递推公式)
通项公式:
$$
\begin{equation} a_n= \begin{cases} 1 &\mbox{$n=1$}\newline 2 &\mbox{$n=2$}\newline 2\times3^{n-2} &\mbox{$n\geq 3$} \end{cases} \end{equation}
$$

$$
\begin{equation} S_n=\sum\limits_{i=1}^{n} {a_i}= \begin{cases} 1 &\mbox{$n=1$}\newline 3 &\mbox{$n=2$}\newline 3+6\times\frac{1-3^{n-2}}{1-3}=3+3\times{(3^{n-2}-1)}=3^{n-1} &\mbox{$n\geq 3$} \end{cases} \end{equation}
$$

$$
S_n=3^{n-1}(a_n的前n项和的公式)
$$
化简后上面这个公式样子够好看了吧?

step4

再看一下第2步发现的规律,应用“二分”手段高效地求解,而且真的可以二分求解。
具体地说,就是先置求解区间 $ [l,r) $ (左闭右开)的端点于每行的头尾( $ l=2^{i-2},r=2^{i-1} $ ),利用上一步所得公式计算该区间的 $ 左端点前缀和U=\sum\limits_{k=1}^{2^{i-2}} {f(k)} $ 与 $ 右端点前缀和V=\sum\limits_{k=1}^{2^{i-1}-1} {f(k)} $ 后,不断地对区间折半,总可以确定新区间的左端点前缀和与右端点前缀和。
当区间缩至一点时, $ U==V==f(k) $,即能得到我们所求,而无需计算出[1..n]的每个函数值。

step5

以上所解决的并不是最终答案,而是前缀和 $ \sum\limits_{k=1}^{n} {f(k)} $ ;
不过走到这一步已经很好办了, $ ans=\sum\limits_{k=low}^{high} {f(k)}=\sum\limits_{k=1}^{high} {f(k)}-\sum\limits_{k=1}^{low-1} {f(k)} $

PS:写完代码后,用极大的n值测试一下,事实上 $ \sum\limits_{k=1}^{n} {f(k)} $已经超出了int64的范围。其他的细节不必多说了。

1.319s AC java代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/**
* @date 2015-05-31
* @author Semprathlon
*/
import java.math.*;
import java.io.*;

public class Main {
static int maxn=42;
static BigInteger[] sum=new BigInteger[maxn];
static long[] Pow2=new long[maxn];
final static BigInteger TWO=BigInteger.valueOf(2);
final static BigInteger THREE=BigInteger.valueOf(3);

static int found_pow2(long n){
return (int)(Math.log((double)n)/Math.log(2.0));
}

static void init(){
for(int i=0;i<maxn;i++)
{
sum[i]=THREE.pow(i);
Pow2[i]=1L<<i;
}

}
static BigInteger Bisearch(long l,long r,BigInteger ls,BigInteger rs,long key){
while(l+1L<r){
long mid=(l+r)>>1;
if(key<mid){
r=mid;
rs=ls.add(rs.subtract(ls).divide(THREE));
}
else{
l=mid;
ls=ls.add(rs.subtract(ls).divide(THREE));
}
}
return ls;
}
static BigInteger solve(long n){
if (n<0L) return BigInteger.ZERO;
if (n==0L) return BigInteger.ONE;
int k=found_pow2(n+1);
BigInteger ls=sum[k];
BigInteger rs=sum[k+1];
return Bisearch(Pow2[k],Pow2[k+1],ls,rs,n+1);
}
public static void main(String[] args) throws IOException{
init();
StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));
PrintWriter out = new PrintWriter(new OutputStreamWriter(System.out));
long n=0,m=0;
while(in.nextToken() != StreamTokenizer.TT_EOF){
n=(long)in.nval;
in.nextToken();
m=(long)in.nval;
if (n==0L&&m==0L) break;
out.println(solve(m).subtract(solve(n-1)));
}
out.flush();
out.close();
}
}

0.279s AC cpp代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
#include<cmath>
#include<cstdio>
#include<cstring>
#include<iostream>

using namespace std;

typedef long long LL;
typedef unsigned long long ULL;
const int maxn = 42;
const LL mod = 1e8;
const int digit = 8;
struct LongInt
{
LL data[3];
LongInt()
{
data[0] = data[1] = data[2] = 0;
}
LongInt(LL n)
{
data[0] = n % mod;
n /= mod;
data[1] = n % mod;
n /= mod;
data[2] = n;
}
LongInt operator= (const LL num)
{
LL n = num;
data[0] = n % mod;
n /= mod;
data[1] = n % mod;
n /= mod;
data[2] = n;
return *this;
}
LongInt operator+ (const LL& num)
{
LL n = num;
LongInt tmp = *this;
tmp.data[0] += n;
tmp.data[1] += tmp.data[0] / mod;
tmp.data[0] %= mod;
tmp.data[2] += tmp.data[1] / mod;
tmp.data[1] %= mod;
return tmp;
}
LongInt operator+ (const LongInt& b)
{
LongInt tmp = *this;
tmp.data[0] += b.data[0];
tmp.data[1] += b.data[1];
tmp.data[2] += b.data[2];
tmp.data[1] += tmp.data[0] / mod;
tmp.data[0] %= mod;
tmp.data[2] += tmp.data[1] / mod;
tmp.data[1] %= mod;
return tmp;
}
LongInt operator- (const LongInt& b)
{
LongInt tmp = *this;
tmp.data[0] -= b.data[0];
if (tmp.data[0] < 0)
{
tmp.data[1]--;
tmp.data[0] += mod;
}
tmp.data[1] -= b.data[1];
if (tmp.data[1] < 0)
{
tmp.data[2]--;
tmp.data[1] += mod;
}
tmp.data[2] -= b.data[2];
return tmp;
}
LongInt operator* (const LL num)
{
LL n = num;
LongInt tmp = *this;
tmp.data[0] *= num;
tmp.data[1] *= num;
tmp.data[2] *= num;
tmp.data[1] += tmp.data[0] / mod;
tmp.data[0] %= mod;
tmp.data[2] += tmp.data[1] / mod;
tmp.data[1] %= mod;
return tmp;
}
LongInt operator*= (const LL num)
{
*this = *this * num;
return *this;
}
LongInt operator/ (const LL num)
{
LongInt tmp = *this;
tmp.data[1] += tmp.data[2] % num * mod;
tmp.data[2] /= num;
tmp.data[0] += tmp.data[1] % num * mod;
tmp.data[1] /= num;
tmp.data[0] /= num;
return tmp;
}
void fillzero(int n)
{
char str[digit * 2];
sprintf(str, "%d", n);
for (int i = 1; i <= digit - strlen(str); i++)
{
printf("%d", 0);
}
printf("%s", str);
}
void print()
{
if (data[2] > 0)
{
printf("%d", data[2]);
fillzero(data[1]);
fillzero(data[0]);
}
else if (data[1] > 0)
{
printf("%d", data[1]);
fillzero(data[0]);
}
else
{
printf("%d", data[0]);
}
}
};

const LL pow2[maxn] = {1, 2, 4, 8, 16, 32, 64, 128,
256, 512, 1024, 2048, 4096, 8192, 16384, 32768,
65536, 131072, 262144, 524288, 1048576, 2097152, 4194304, 8388608,
16777216, 33554432, 67108864, 134217728, 268435456, 536870912, 1073741824, 2147483648,
4294967296, 8589934592, 17179869184, 34359738368, 68719476736, 137438953472, 274877906944, 549755813888,
1099511627776, 2199023255552
};
/*const ULL sum[maxn] = {3, 5, 11, 29, 83, 245, 731, 2189,
6563, 19685, 59051, 177149, 531443, 1594325, 4782971, 14348909,
43046723, 129140165, 387420491, 1162261469, 3486784403, 10460353205, 31381059611, 94143178829,
282429536483, 847288609445, 2541865828331, 7625597484989, 22876792454963, 68630377364885, 205891132094651, 617673396283949,
1853020188851843, 5559060566555525, 16677181699666571, 50031545098999709, 150094635296999123, 450283905890997365, 1350851717672992091, 4052555153018976269,
12157665459056928803
};*/
LongInt sum[maxn];

int found_pow2(ULL n)
{
return int(log(n) / log(2));
}

LongInt Bisearch(LL l, LL r, LongInt ls, LongInt rs, LL key) // [ l , r )
{
while (l + 1 < r)
{
ULL mid = (l + r) >> 1;
if (key < mid)
{
r = mid;
rs = (rs - ls) / 3 + ls;
}
else
{
l = mid;
ls = (rs - ls) / 3 + ls;
}
}
return ls;
}

LongInt solve(LL n)
{
if (n < 0)
{
return 0;
}
if (!n)
{
return 1;
}
int k = found_pow2(n + 1);
LongInt ls = sum[k];
LongInt rs = sum[k + 1];
return Bisearch(pow2[k], pow2[k + 1], ls, rs, n + 1);
}

void init()
{
LongInt tmp = 1;
for (int i = 0; i < maxn; i++)
{
sum[i] = tmp;
tmp *= 3;
}
}

int main()
{
init();
LL n, m;
while (cin >> n >> m)
{
if (!n && !m)
{
break;
}
LongInt ans = solve(m) - solve(n - 1);
ans.print();
puts("");
}
return 0;
}

看起来比较舒服的矩阵乘法模板

转载源页面
poj 3070

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <cstdio>

#define SIZE 4
#define mod 10000

using namespace std;

struct MATRIX
{
int mt[SIZE][SIZE];
int x,y;
}ans,def;

int n;

inline MATRIX operator *(MATRIX a,MATRIX b)
{
MATRIX c;
memset(c.mt,0,sizeof c.mt);
c.x=a.x; c.y=b.y;
for(int i=1;i<=a.x;i++)
for(int j=1;j<=b.y;j++)
for(int k=1;k<=a.y;k++)
c.mt[i][j]=(c.mt[i][j]+(a.mt[i][k]%mod)*(b.mt[k][j]%mod))%mod;
return c;
}

inline MATRIX operator +(MATRIX a,MATRIX b)
{
MATRIX c;
memset(c.mt,0,sizeof c.mt);
c.x=a.x; c.y=a.y;
for(int i=1;i<=c.x;i++)
for(int j=1;j<=c.y;j++)
c.mt[i][j]=(a.mt[i][j]+b.mt[i][j])%mod;
return c;
}

inline bool prt(MATRIX &c)
{
for(int i=1;i<=c.x;i++)
{
for(int j=1;j<=c.y;j++) printf("%d ",c.mt[i][j]);
puts("");
}
}

void go()
{
n-=2;
def.mt[1][1]=def.mt[1][2]=def.mt[2][1]=1;
def.mt[2][2]=0; def.x=def.y=2;
ans.mt[1][1]=ans.mt[1][2]=ans.mt[2][1]=1; ans.mt[2][2]=0;
ans.x=ans.y=2;

while(n)
{
if(n&1) ans=ans*def;
def=def*def;
n>>=1;
}
printf("%d\n",ans.mt[1][1]);
}

int main()
{
while(scanf("%d",&n))
{
if(n==-1) break;
else if(n==0) puts("0");
else if(n==1) puts("1");
else go();
}
system("pause");
return 0;
}

ACM-ICPC Live Archive Regionals 2014 >> Asia - Kuala Lumpur 6811 - Irrigation Lines

参见:转载前页面

怎么读题时就没发现“每行每列各有一个水阀”?这是把问题转化成二分图模型的关键啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <cstring>
#include <cstdio>
using namespace std;

const int MAXN = 110;
char graph[MAXN][MAXN];
bool visited[MAXN];
int nCase, cCase, use[MAXN], m, n;

void init() {
memset(use, -1, sizeof(use));
}

void input() {
scanf("%d%d", &m, &n);
for (int i = 0; i < m; i++) {
scanf("%s", graph[i]);
}
}

bool find(int x) {
for (int j = 0; j < n; j++) {
if (graph[x][j] == '1' && !visited[j]) {
visited[j] = true;

if (use[j] == -1 || find(use[j])) {
use[j] = x;
return true;
}
}
}
return false;
}

int match() {
int count = 0;
for (int i = 0; i < m; i++) {
memset(visited, false, sizeof(visited));
if (find(i)) count++;
}
return count;
}

void solve() {
printf("Case #%d: %d\n", ++cCase, match());
}

int main() {
scanf("%d", &nCase);
while (nCase--) {
init();
input();
solve();

}
return 0;
}

2015年浙江省大学生程序设计竞赛

像这样搞错映射关系,不是个很好的情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<queue>
#include<stack>
#include<set>
#include<map>
#define CLEAR(a) memset((a),0,sizeof((a)))


using namespace std;


typedef long long LL;
const double pi = acos(-1.0);
const int maxn=1e4;
const int inf=99999999;
const double eps=1e-3;


int month[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};


int ans(int x)
{


  //  cout<<x<<endl ;
    if (x==-1) return 6;
    if (x==0) return 5;
    if (x==1) return 6;
    if (x==2) return 9;
    if (x==3) return 6;
    if (x==4) return 5;
    if (x==5) return 5;
    if (x==6) return 5;
    if (x==7) return 5;
    if (x==8) return 6;
}


int isleap(int y)
{
    if ((y%4==0&&y%100!=0) || y%400==0) return 366;
    else return 365;
}


int fun(int y,int m)
{
    if (m==2)
    {
        if ((y%4==0&&y%100!=0) || y%400==0) return 29;
        else return 28;
    }
    else return month[m];
}


int main()
{
    int T;
    scanf("%d",&T);
    while(T--)
    {
        int y;
        scanf("%d",&y);
        int day=0;
        for(int i=1928;i<y;i++)
            day+=isleap(i);
        for(int i=1;i<=4;i++)
            day+=fun(y,i);
        day+=1;
        //cout<<day<<' ';
        cout<<ans(day%7)<<endl;
        /*switch (day%7)
        {
        case 0:


        }*/
    }
    return 0;
}

【转】核心网络—BestCoder需要注意的几个问题

核心网络—BestCoder需要注意的几个问题.

1.不能随意hack,hack成功有加分,但是hack失败也会扣分!
2.不要随意提交已经AC的题目,罚时是这样的:最后一次AC的之前所有提交(无论AC与否)均算作罚时!其中AC的标为Accepted(Past),被hack掉的AC也会标为Accepted(Past)。
3.AC不代表真正的AC,比赛最后阶段有hack数据,如果被hack掉就不算AC。hack数据一般都是非常变态的输入,或者是大数据!有时候有些人刻意利用题目bug构造变态的数据导致全场无人AC……
4.hack时只能读别人的代码,无法复制,这是锻炼读代码的能力。
5.如果有一场比赛注册了,但是忘做了,只要没有该场比赛的提交记录就不会使Rating降低。
6.只有本场比赛进入前300才能使Rating升高,否则就降低。
7.ABCD四道题的难度是依次升高的,分数也是依次升高的,根据错的次数和AC时间而使所得分数不同。
8.比赛题目是民间的ACMer出的,如果发现题目中有大量的英语语法错误,请不要见怪。
9.比赛日期并非固定在周五或者周六或者周日,根据杭电oj的具体日程安排而定。比如,如果有网络赛,可能就要提前或者延后了。
10.最后你的很多AC的题目可能都会被hack掉,请不要见怪。
11.

以上错误我几乎都犯过……
O__O”…

【Bestcoder

1001 - Rikka with string

Accepts: 395
Submissions: 2281
Time Limit: 2000/1000 MS (Java/Others)
Memory Limit: 65536/65536 K (Java/Others)
Problem Description

As we know, Rikka is poor at math. Yuta is worrying about this situation, so he gives Rikka some math tasks to practice. There is one of them:

One day, Yuta got a string which contains n letters but Rikka lost it in accident. Now they want to recover the string. Yuta remembers that the string only contains lowercase letters and it is not a palindrome string. Unfortunately he cannot remember some letters. Can you help him recover the string?

It is too difficult for Rikka. Can you help her?
Input

This problem has multi test cases (no more than 20). For each test case, The first line contains a number n(1≤n≤1000). The next line contains an n-length string which only contains lowercase letters and ‘?’ – the place which Yuta is not sure.
Output

For each test cases print a n-length string – the string you come up with. In the case where more than one string exists, print the lexicographically first one. In the case where no such string exists, output “QwQ”.
Sample Input

5
a?bb?
3
aaa

Sample Output

aabba
QwQ

WA得智商都被拉低了 QAQ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int dfs(int step,bool &state)//1:to check
{
int res;
if (step<m-1) res=dfs(step+1,state);
int pos=n-vec[step]-1;
cout<<step<<' '<<str[vec[step]]<<' '<<str[pos]<<endl;
for(char ch='a';ch<='z';ch++)
if (state&&str[pos]==ch)
{
if (ch<'z') continue;
else return -1;//error
}
else
{
str[vec[step]]=ch;
if (state&&str[pos]!='?'&&ch!=str[pos]) state=0;
if (ch!=str[pos]) return 1;
}
return -1;
}

AC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include<cctype>
#include<cmath>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<iostream>
#include<string>
#include<queue>
#include<stack>
#include<set>
#include<map>
#define CLEAR(a) memset((a),0,sizeof((a)))

using namespace std;

typedef long long LL;
const double pi = acos(-1.0);
const int maxn=2e3;
const int inf=99999999;
const float eps=1e-3;

vector<int> vec;
int n,m;
char s[maxn],str[maxn];

int dfs(int step,bool &state)//1:to check
{

int pos=n-vec[step]-1;
for(char ch='a';ch<='z';ch++)
{
str[vec[step]]=ch;
if (str[pos]!='?'&&ch!=str[pos]) {state=0;}
if (step<m-1) dfs(step+1,state);
if (state&&str[pos]!='?'&&ch!=str[pos]||!state) return 1;
}

return -1;
}

int main()
{

while(~scanf("%d",&n))
{
vec.clear();
scanf("%s",s);
memcpy(str,s,sizeof(s));
for(int i=0;i<n;i++)
if (s[i]=='?')
vec.push_back(i);
m=vec.size();
bool tmp=1;

if (m>1||m==1&&!(n%2&&vec[0]==n/2))
{
if (dfs(0,tmp)>=0) puts(str);
else puts("QwQ");
}
else
{
bool ans=1;
for(int i=1;i<=strlen(s)/2;i++)
if (s[i-1]!=s[strlen(s)-i])
{
ans=0;break;
}
if (m==1&&n%2&&vec[0]==n/2) str[vec[0]]='a';
puts(ans?"QwQ":str);
}

}
return 0;
}

[心痛]线段树或树状数组求逆序数

原文地址

线段树或树状数组求逆序数

求逆序数的方法有分治,归并,本文只介绍线段树或树状数组求逆序数的办法,众所周知,线段树和树状树可以用来解决区间操作问题,就是因为这两个算法区间操作的时间复杂度很低O(logN),才让这种方法具有可行性。

首先先来看一个序列   6 1 2 7 3 4 8 5,此序列的逆序数为5+3+1=9。冒泡法可以直接枚举出逆序数,但是时间复杂度太高O(n^2)。冒泡排序的原理是枚举每一个数组,然后找出这个数后面有多少个数是小于这个数的,小于它逆序数+1。仔细想一下,如果我们不用枚举这个数后面的所有数,而是直接得到小于这个数的个数,那么效率将会大大提高。

总共有N个数,如何判断第i+1个数到最后一个数之间有多少个数小于第i个数呢?不妨假设有一个区间 [1,N],只需要判断区间[i+1,N]之间有多少个数小于第i个数。如果我们把总区间初始化为0,然后把第i个数之前出现过的数都在相应的区间把它的值定为1,那么问题就转换成了[i+1,N]值的总和。再仔细想一下,区间[1,i]的值+区间[i+1,N]的值=区间[1,N]的值(i已经标记为1),所以区间[i+1,N]值的总和等于N-[1,i]的值!因为总共有N个数,不是比它小就是比它(大或等于)。

现在问题已经转化成了区间问题,枚举每个数,然后查询这个数前面的区间值的总和, i-[1,i] 既为逆序数。

线段树预处理时间复杂度O(NlogN),N次查询和N次插入的时间复杂度都为O(NlogN),总的时间复杂度O(3*NlogN)

树状数组不用预处理,N次查询和N次插入的时间复杂度都为O(NlogN),总的时间复杂度O(2*NlogN)

线段树:
&nbsp;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
// 线段树
#include <stdio .h>
#include <string .h>
#include <stdlib .h>
#define MAX 51000
#define MID(a,b) (a+b)>>1
#define R(a) (a< <1|1)
#define L(a) a<<1
typedef struct {
int num,left,right;
}Node;
int ans[MAX];
Node Tree[MAX<<2];
int n;

void Build(int t,int l,int r) //以1为根节点建立线段树
{
int mid;
Tree[t].left=l,Tree[t].right=r;
if(Tree[t].left==Tree[t].right)
{
Tree[t].num=0;
return ;
}
mid=MID(Tree[t].left,Tree[t].right);
Build(L(t),l,mid);
Build(R(t),mid+1,r);
}

void Insert(int t,int l,int r,int x) //向以1为根节点的区间[l,r]插入数字1
{
int mid;
if(Tree[t].left==l&&Tree[t].right==r)
{
Tree[t].num+=x;
return ;
}
mid=MID(Tree[t].left,Tree[t].right);
if(l>mid)
{
Insert(R(t),l,r,x);
}
else if(r< =mid)
{
Insert(L(t),l,r,x);
}
else
{
Insert(L(t),l,mid,x);
Insert(R(t),mid+1,r,x);
}
Tree[t].num=Tree[L(t)].num+Tree[R(t)].num;
}

int Query(int t,int l,int r) //查询以1为根节点,区间[l,r]的和
{
int mid;
if(Tree[t].left==l&&Tree[t].right==r)
return Tree[t].num;
mid=MID(Tree[t].left,Tree[t].right);
if(l>mid)
{
return Query(R(t),l,r);
}
else if(r<=mid)
{
return Query(L(t),l,r);
}
else
{
return Query(L(t),l,mid)+Query(R(t),mid+1,r);
}
}


int main()
{
int a,n,i,t;
scanf("%d",&t);
long long int k;
while(t--)
{
scanf("%d",&n);
memset(Tree,0,sizeof(Tree)); //初始化线段树
Build(1,1,n);
for(i=1;i<=n;i++) //输入n个数
{
scanf("%d",&ans[i]);
}
for(i=1,k=0;i<=n;i++)
{
a=ans[i];
Insert(1,a,a,1); //把线段树[ans[i],ans[i]]区间的值插入为1
k=k+(i-Query(1,1,a)); //查询区间[1,ans[i]]值的总和,逆序数等于i-[1,ans[i]]
}
printf("%lld\n",k);
}
return 0;
}

树状数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 树状数组
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <algorithm>
using namespace std;
#define MAX 100010
int c[MAX],a[MAX],ans[MAX],n;

int Lowbit(int x) //返回二进制最后一个1所表示的数
{
return x&(-x);
}

void Updata(int x) //向前更新
{
while(x<=n)
{
c[x]++;
x+=Lowbit(x);
}
}

int Sum(int x) //向后更新求和
{
int sum=0;
while(x>0)
{
sum+=c[x];
x-=Lowbit(x);
}
return sum;
}

int main()
{
int i,t,k;
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
for(i=1;i<=n;i++)
{
scanf("%d",&ans[i]);
}
memset(c,0,sizeof(c)); //初始化树状数组
for(i=1,k=0;i<=n;i++)
{
Updata(ans[i]); //向后更新节点ans[i].k
k=k+(i-Sum(ans[i])); //向前查询节点ans[i].k
}
printf("%d\n",k);
}
return 0;
}

&nbsp;

FFZU-ACM月赛不行了

线段树/树状数组的另一种用法不会,它的逆向问题更不会