随意刷题

Two Round Dances

题面翻译

题目描述

有一天,\(n\) 人(\(n\) 是偶数)在广场上相遇,跳了两支圆舞曲,每支圆舞曲正好由\(\frac{n}{2}\)人组成。你的任务是找出 \(n\) 人可以跳两支圆舞的方案数量。每个人应该正好属于这两种圆舞中的一种。

圆舞是由 \(1\) 人或更多的人组成的舞蹈圈。如果两个圆舞可以通过选择第一个参与者转化为另一个圆舞,则两个圆舞是无法区分(相等)的。例如,圆舞 \([1,3,4,2]\) ,$[4,2,1,3] $和 $[2,1,3,4] $是不可区分的。

例如,如果 \(n=2\),那么方式的数量是 \(1\):一个圆舞曲由第一个人组成,第二个人的圆舞曲由第二个人组成。

例如,如果 \(n=4\),那么路数是 \(3\) 。可能的方案:

  • 一个圆舞曲 \([1,2]\) , 另一个 \([3,4]\)
  • 一支圆舞 \([2,4]\) ,另一支 $ [3,1] $。
  • 一个圆舞$ [4,1]$,另一个 $ [3,2]$ 。

你的任务是:如果每个圆舞曲正好由\(\frac{n}{2}\)人组成,找出 \(n\) 人可以跳两支圆舞曲的方案数量。

输入格式

包含一个整数 \(n\,(2\le n\le20)\)

输出格式

一个整数,表示方案数量,保证结果不超过\(64\)位整数的限制。

题目描述

One day, $ n $ people ( $ n $ is an even number) met on a plaza and made two round dances, each round dance consists of exactly $ $ people. Your task is to find the number of ways $ n $ people can make two round dances if each round dance consists of exactly $ $ people. Each person should belong to exactly one of these two round dances.

Round dance is a dance circle consisting of $ 1 $ or more people. Two round dances are indistinguishable (equal) if one can be transformed to another by choosing the first participant. For example, round dances $ [1, 3, 4, 2] $ , $ [4, 2, 1, 3] $ and $ [2, 1, 3, 4] $ are indistinguishable.

For example, if $ n=2 $ then the number of ways is $ 1 $ : one round dance consists of the first person and the second one of the second person.

For example, if $ n=4 $ then the number of ways is $ 3 $ . Possible options:

  • one round dance — $ [1,2] $ , another — $ [3,4] $ ;
  • one round dance — $ [2,4] $ , another — $ [3,1] $ ;
  • one round dance — $ [4,1] $ , another — $ [3,2] $ .

Your task is to find the number of ways $ n $ people can make two round dances if each round dance consists of exactly $ $ people.

输入格式

The input contains one integer $ n $ ( $ 2 n $ ), $ n $ is an even number.

输出格式

Print one integer — the number of ways to make two round dances. It is guaranteed that the answer fits in the $ 64 $ -bit integer data type.

样例 #1

样例输入 #1

1
2

样例输出 #1

1
1

样例 #2

样例输入 #2

1
4

样例输出 #2

1
3

样例 #3

样例输入 #3

1
8

样例输出 #3

1
1260

样例 #4

样例输入 #4

1
20

样例输出 #4

1
12164510040883200

因为 \(n\) 的圆排列为 \((n-1)!\) 。然后这里又有 \(2\) 组。

所以答案为 \(\dfrac{1}{2} \times C_n^ \frac{n}{2} \times ((\dfrac{n}{2}-1)!)^2\)

开头阶乘预处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define int long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define frep(i,a,n) for(int i=a;i>=n;i--)
#define PII pair<int,int>
#define lowbit(x) (x&(-x))
const int mod=1e9+7;
const double pai=acos(-1.0);
#define ios ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#define LF(x) fixed<<setprecision(x)
const ll Maxn=20+5;
ll n,mul[Maxn];
signed main()
{ cin>>n;
mul[0]=1;
for(ll i=1;i<=n;i++)mul[i]=1ll*mul[i-1]*i;
cout<<mul[n]/mul[n/2]/mul[n/2]/2*mul[n/2-1]*mul[n/2-1];
return 0;
}

Districts Connection

题面翻译

给出 \(n\) 个点的特征值 \(a_i\),要求构造一棵树使特征值相同的点之间不直接连边

题目描述

There are $ n $ districts in the town, the $ i $ -th district belongs to the $ a_i $ -th bandit gang. Initially, no districts are connected to each other.

You are the mayor of the city and want to build $ n-1 $ two-way roads to connect all districts (two districts can be connected directly or through other connected districts).

If two districts belonging to the same gang are connected directly with a road, this gang will revolt.

You don't want this so your task is to build $ n-1 $ two-way roads in such a way that all districts are reachable from each other (possibly, using intermediate districts) and each pair of directly connected districts belong to different gangs, or determine that it is impossible to build $ n-1 $ roads to satisfy all the conditions.

You have to answer $ t $ independent test cases.

输入格式

The first line of the input contains one integer $ t $ ( $ 1 t $ ) — the number of test cases. Then $ t $ test cases follow.

The first line of the test case contains one integer $ n $ ( $ 2 n $ ) — the number of districts. The second line of the test case contains $ n $ integers $ a_1, a_2, , a_n $ ( $ 1 a_i ^9 $ ), where $ a_i $ is the gang the $ i $ -th district belongs to.

It is guaranteed that the sum of $ n $ does not exceed $ 5000 $ ( $ n $ ).

输出格式

For each test case, print:

  • NO on the only line if it is impossible to connect all districts satisfying the conditions from the problem statement.
  • YES on the first line and $ n-1 $ roads on the next $ n-1 $ lines. Each road should be presented as a pair of integers $ x_i $ and $ y_i $ ( $ 1 x_i, y_i n; x_i y_i $ ), where $ x_i $ and $ y_i $ are two districts the $ i $ -th road connects.

For each road $ i $ , the condition $ a[x_i] a[y_i] $ should be satisfied. Also, all districts should be reachable from each other (possibly, using intermediate districts).

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
9
4
5
1 2 2 1 3
3
1 1 1
4
1 1000 101 1000
4
1 2 3 4

样例输出 #1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
YES
1 3
3 5
5 4
1 2
NO
YES
1 2
2 3
3 4
YES
1 2
1 3
1 4

全相同一定不行,有一个不同就可以

因为可以不同连1,相同连子节点

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
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define int long long
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define frep(i,a,n) for(int i=a;i>=n;i--)
#define PII pair<int,int>
#define lowbit(x) (x&(-x))
const int mod=1e9+7;
const double pai=acos(-1.0);
#define ios ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
#define LF(x) fixed<<setprecision(x)
const int MAXN=5000+5;
int T,n,a[MAXN];

signed main()
{
cin>>T;
while(T--)
{
cin>>n;
for(int i=1;i<=n;i++) cin>>a[i];
bool flag=1;
for(int i=2;i<=n;i++)
{
if(a[i]!=a[1])
{
flag=0;
break;
}
}
if(flag)
{
cout<<"NO\n";
continue;
}
cout<<"YES\n";
int id;
for(int i=2;i<=n;i++)
{
if(a[i]!=a[1])
{
cout<<"1 "<<i<<endl;
id=i;
}
}
for(int i=2;i<=n;i++)
{
if(a[i]==a[1]) cout<<i<<" "<<id<<endl;
}
}
return 0;
}

Dominant Piranha

题面翻译

\(n\) 条食人鱼,从左到右依次编号为 \(a_1,a_2,…,a_n\)(水族馆狭长,只能从左到右依次分布,\(a_i\) 表示第 \(i\) 条鱼的体积) 。如果一条鱼能吃掉其它的所有鱼,我们称之为优势鱼。

每条鱼可以吃很多次,但只能吃掉邻近的鱼

  • 存在 \(a_{i-1}\)\(a_{i-1} < a_i\) 则可以 \(a_i\) 可以吃掉 \(a_{i-1}\)

  • 存在 \(a_{i+1}\)\(a_{i+1} < a_i\) 则可以 \(a_i\) 可以吃掉 \(a_{i+1}\)

每吃一条鱼自己的体积就会加一

找到任意一个的占优势的鱼,有则输出是第几条,没有则输出 \(”-1"\) (不带引号)

有多组样例输入

题目描述

There are $ n $ piranhas with sizes $ a_1, a_2, , a_n $ in the aquarium. Piranhas are numbered from left to right in order they live in the aquarium.

Scientists of the Berland State University want to find if there is dominant piranha in the aquarium. The piranha is called dominant if it can eat all the other piranhas in the aquarium (except itself, of course). Other piranhas will do nothing while the dominant piranha will eat them.

Because the aquarium is pretty narrow and long, the piranha can eat only one of the adjacent piranhas during one move. Piranha can do as many moves as it needs (or as it can). More precisely:

  • The piranha $ i $ can eat the piranha $ i-1 $ if the piranha $ i-1 $ exists and $ a_{i - 1} < a_i $ .
  • The piranha $ i $ can eat the piranha $ i+1 $ if the piranha $ i+1 $ exists and $ a_{i + 1} < a_i $ .

When the piranha $ i $ eats some piranha, its size increases by one ( $ a_i $ becomes $ a_i + 1 $ ).

Your task is to find any dominant piranha in the aquarium or determine if there are no such piranhas.

Note that you have to find any (exactly one) dominant piranha, you don't have to find all of them.

For example, if $ a = [5, 3, 4, 4, 5] $ , then the third piranha can be dominant. Consider the sequence of its moves:

  • The piranha eats the second piranha and $ a $ becomes $ [5, , 4, 5] $ (the underlined piranha is our candidate).
  • The piranha eats the third piranha and $ a $ becomes $ [5, , 5] $ .
  • The piranha eats the first piranha and $ a $ becomes $ [, 5] $ .
  • The piranha eats the second piranha and $ a $ becomes $ [] $ .

You have to answer $ t $ independent test cases.

输入格式

The first line of the input contains one integer $ t $ ( $ 1 t ^4 $ ) — the number of test cases. Then $ t $ test cases follow.

The first line of the test case contains one integer $ n $ ( $ 2 n ^5 $ ) — the number of piranhas in the aquarium. The second line of the test case contains $ n $ integers $ a_1, a_2, , a_n $ ( $ 1 a_i ^9 $ ), where $ a_i $ is the size of the $ i $ -th piranha.

It is guaranteed that the sum of $ n $ does not exceed $ 3 ^5 $ ( $ n ^5 $ ).

输出格式

For each test case, print the answer: -1 if there are no dominant piranhas in the aquarium or index of any dominant piranha otherwise. If there are several answers, you can print any.

样例 #1

样例输入 #1

1
2
3
4
5
6
7
8
9
10
11
12
13
6
5
5 3 4 4 5
3
1 1 1
5
4 4 3 4 4
5
5 5 4 3 2
3
1 1 2
5
5 4 3 5 5

样例输出 #1

1
2
3
4
5
6
3
-1
4
3
3
1

提示

The first test case of the example is described in the problem statement.

In the second test case of the example, there are no dominant piranhas in the aquarium.

In the third test case of the example, the fourth piranha can firstly eat the piranha to the left and the aquarium becomes $ [4, 4, 5, 4] $ , then it can eat any other piranha in the aquarium.

如果全为一个值,那么一定无解。否则,一定有一个最大值左右有一个值小于它,找到这个最大值即可。

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
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
int mx = 0;
for (auto &x : a) cin >> x, mx = max(mx, x);
if (all_of(a.begin(), a.end(), [&](int x) { return x == mx; })) {
cout << -1 << "\n";
} else {
int ans = -1;
for (int i = 0; i < n; i++) {
if (a[i] == mx) {
if (i - 1 >= 0 and a[i - 1] < a[i]) {
ans = i;
break;
}
if (i + 1 < n and a[i + 1] < a[i]) {
ans = i;
break;
}
}
}
cout << ans + 1 << "\n";
}
}
return 0;
}

上面还有一道水题B

统计一下两个1之间的0的个数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <bits/stdc++.h>

using namespace std;

int main() {

int t;
cin >> t;
while (t--) {
int n;
cin >> n;
vector<int> a(n);
for (auto &it : a) cin >> it;
while (a.back() == 0) a.pop_back();
reverse(a.begin(), a.end());
while (a.back() == 0) a.pop_back();
cout << count(a.begin(), a.end(), 0) << endl;
}

return 0;
}

GSS4 - Can you answer these queries IV

题面翻译

「题意」: \(n\) 个数,和在\(10^{18}\) 范围内。

也就是\(\sum~a_i~\leq~10^{18}\)

现在有「两种」操作

x y ```把区间$[x,y]$ 内的每个数开方,下取整
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

```1 x y```询问区间$[x,y]$ 的每个数的和

「格式」:
有多组数据,数据以EOF结束,对于每组数据,输出数据的序号,每组数据之后输出一个空行。

「注意」:
不保证给出的区间$[x, y]$ 有$x <= y$ ,如果$x>y$ 请交换$x$ ,$y$ 。

感谢@Edgration 提供的翻译

## 题目描述

You are given a sequence A of N(N <= 100,000) positive integers. There sum will be less than 10 $ ^{18} $ . On this sequence you have to apply M (M <= 100,000) operations:

(A) For given x,y, for each elements between the x-th and the y-th ones (inclusively, counting from 1), modify it to its positive square root (rounded down to the nearest integer).

(B) For given x,y, query the sum of all the elements between the x-th and the y-th ones (inclusively, counting from 1) in the sequence.

## 输入格式

Multiple test cases, please proceed them one by one. Input terminates by EOF.

For each test case:

The first line contains an integer N. The following line contains N integers, representing the sequence A $ _{1} $ ..A $ _{N} $ .
The third line contains an integer M. The next M lines contain the operations in the form "i x y".i=0 denotes the modify operation, i=1 denotes the query operation.

## 输出格式

For each test case:

Output the case number (counting from 1) in the first line of output. Then for each query, print an integer as the problem required.

Print an blank line after each test case.

See the sample output for more details.

## 样例 #1

### 样例输入 #1

5 1 2 3 4 5 5 1 2 4 0 2 4 1 2 4 0 4 5 1 1 5 4 10 10 10 10 3 1 1 4 0 2 3 1 1 4

1
2
3

### 样例输出 #1

Case #1: 9 4 6

Case #2: 40 26

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

显然这是一道线段树板子题

主要是根号修改比较麻烦,但是根号可以优化,哪怕是很大的数字,开六次根号也会变成1的,所以说对这个区间的修改就是没有意义的,因此就可以直接跳过

核心就是判断是不是和为区间长度,也就是都是1

```cpp
#include<bits/stdc++.h>
#define lson (node<<1)//左儿子
#define rson (node<<1|1)//右儿子
#define ll long long
#define int long long //记得开long long
using namespace std;
const int MAXN = 100005;
struct st{
int l,r;//左右端点
ll sum;
}tree[MAXN<<2];

ll a[MAXN];
int n,m;
void pushup(int node){
tree[node].sum = tree[lson].sum + tree[rson].sum;//合并操作
}
void build(int node,int l,int r){//建树
tree[node].l = l;
tree[node].r = r;
if(l==r){
tree[node].sum = a[l];
return;
}
int mid = (l+r)>>1;
build(lson,l,mid);
build(rson,mid+1,r);
pushup(node);
}
void change(int node,int l,int r){
int L = tree[node].l,R = tree[node].r;
if(tree[node].sum==R-L+1) return;//如果总和为区间长度,也就是所有值均为1时,直接剪枝掉
if(L==R){
tree[node].sum = sqrt(tree[node].sum);
return;
}
int mid = (L+R)>>1;
if(l<=mid){
change(lson,l,r);
}
if(r>mid){
change(rson,l,r);
}
pushup(node);
}
ll query(int node,int l,int r)
{//查询
int L = tree[node].l,R = tree[node].r;
if(l<=L&&r>=R){//包含在查询区间内,直接返回sum值
return tree[node].sum;
}
int mid = (L+R)>>1;
ll ans = 0;
if(l<=mid){
ans+=query(lson,l,r);
}
if(r>mid){
ans+=query(rson,l,r);
}
return ans;
}
signed main(){
ios::sync_with_stdio(false);//不加貌似会TLE?
cin.tie(0);
cout.tie(0);
int Case=0;
while(cin>>n){
printf("Case #%d:\n",++Case);
memset(a,0,sizeof(a));//记得要先memset
memset(tree,0,sizeof(tree));
for(int i=1;i<=n;i++){
cin>>a[i];
}
build(1,1,n);
cin>>m;
while(m--){
int mode,left,right;
cin>>mode>>left>>right;
if(left > right){
swap(left,right);
}
if(mode==0){
change(1,left,right);
}
else{
cout<<query(1,left,right)<<'\n';
}
}
puts("");//记得换行
}

}

并查集优化贪心:

超市里有N个商品. 第i个商品必须在保质期(第di天)之前卖掉, 若卖掉可让超市获得pi的利润. 每天只能卖一个商品. 现在你要让超市获得最大的利润. 多组数据. INPUT 每组数据第一行为N, 即超市的商品数目 之后N行数字. 第i行为 pi, di N , pi, di <= 10000 OUTPUT 对于每一组数据, 输出当前条件下超市的最大利润

参考牛客题解:

按照正常的贪心想法,因为每天只能卖一件物品。所以我们对于某一天来说,应该优先卖贵的。 为了使得更多天数可以被利用,减少冲突,每一件商品应该等到快过期时卖掉(时间尽量靠后)。这样可以腾出更多天数给其他商品

因此,将商品价格从大到小排序,然后对于每一个商品枚举天数。找到可以卖出的最靠后的某一天,标记之。 但是这样是暴力的贪心,暴力枚举天数会导致超时

可以采用并查集优化这个贪心,这个并查集的写法和普通的并不相同,姑且称之为类并查集

我们观察从后往前观察天数可以发现,如果某天被占用,则会向前找,直到找到一个没有被利用的为止 暴力解法的暴力在于,会重复枚举到被占用的天数。如果能像并查集一样,直接找到根节点/代表元素/最后的没有被占用的天数就好了 初始时给并查集数组]p[i] 赋值为-1代表其未被占用。之后如果要选择这一天,且未被占用,令p[i]=i−1,下一次选这一天的时候就直接找到第i−1天。这样合并并查集,对于所有不能选择的天数,最终都会指向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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<sstream>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#define pll pair<long long,long long>
#define pii pair<int,int>
#define pdd pair<double,double>
#define ll long long
#define F first
#define S second
#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
#define abs(a) ((a)<0))?(-1*(a)):(a)
#define INF 0x3f3f3f3f
#define INPUTi1(a) scanf("%d",&(a))
#define INPUTi2(a,b) scanf("%d %d",&(a),&(b))
#define INPUTi3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
#define INPUTd1(a) scanf("%lf",&(a))
#define INPUTd2(a,b) scanf("%lf %lf",&(a),&(b))
#define INPUTd3(a,b,c) scanf("%lf %lf %lf",&(a),&(b),&(c))
using namespace std;
const int N=10010;
struct node
{
int p,d;
};
bool cmp(node n1,node n2){
return n1.p>n2.p;
}
node e[N];
int p[N];
int find(int u)
{
if(p[u]==-1)
return u;
p[u]=find(p[u]);
return p[u];
}
int main(){
int n;
while(~INPUTi1(n))
{
for(int i=0;i<n;i++){
INPUTi2(e[i].p,e[i].d);
}
sort(e,e+n,cmp);
for(int i=0;i<N;i++)
p[i]=-1;
long long res=0;
for(int i=0;i<n;i++)
{
int pi=find(e[i].d);
//指向他的前一天
//这样就可以实现如果是0的话那么就无法访问了
if(pi)
{
res+=e[i].p;
p[pi]=pi-1;
}

}
cout<<res<<"\n";

}
}

还有反悔贪心的做法:

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
#include <iostream>
#include<cstring>
#include<cstdio>
#include<algorithm>
#include<cmath>
#include<cctype>
#include<sstream>
#include<string>
#include<vector>
#include<stack>
#include<queue>
#include<map>
#include<set>
#define pll pair<long long,long long>
#define pii pair<int,int>
#define pdd pair<double,double>
#define ll long long
#define F first
#define S second
#define max(a,b) (a)>(b)?(a):(b)
#define min(a,b) (a)<(b)?(a):(b)
#define abs(a) ((a)<0))?(-1*(a)):(a)
#define INF 0x3f3f3f3f
#define INPUTi1(a) scanf("%d",&(a))
#define INPUTi2(a,b) scanf("%d %d",&(a),&(b))
#define INPUTi3(a,b,c) scanf("%d %d %d",&(a),&(b),&(c))
#define INPUTd1(a) scanf("%lf",&(a))
#define INPUTd2(a,b) scanf("%lf %lf",&(a),&(b))
#define INPUTd3(a,b,c) scanf("%lf %lf %lf",&(a),&(b),&(c))
using namespace std;
const int N=10010;
struct node{
int p,d;
};
bool cmp(node n1,node n2){
if(n1.d!=n2.d)
return n1.d<n2.d;
return n1.p>n2.p;
}
node e[N];
int main(){
int n;
while(~INPUTi1(n)){
for(int i=0;i<n;i++){
INPUTi2(e[i].p,e[i].d);
}
sort(e,e+n,cmp);
priority_queue<int,vector<int>,greater<int> >pq;
long long res=0;
int cnt=0;
for(int i=0;i<n;i++){
if(cnt<e[i].d){
cnt++;
res+=e[i].p;
pq.push(e[i].p);
}else{
if(e[i].p>pq.top()){
res+=(e[i].p-pq.top());
pq.pop();
pq.push(e[i].p);
}
}
}
cout<<res<<"\n";
}
}

银河英雄传说

fa[i]表示飞船i的祖先节点,即其所在列的队头。

看见多次求两个点的距离的问题,便想到用前缀和来实现:开一个g数组,g[i]表示飞船i到其所在队列队头的距离,然后飞船i和飞船j之间的飞船数量即为它们到队头的距离之差减一,就是abs(g[i]-g[j])-1

我们来分析一下:对于原来的队头,它到队头的距离为0,当将它所在的队列移到另一个队列后面时,它到队头的距离就是排在它前面的飞船数,也就是合并前另一个队列的飞船数量。因此,就知道该怎样实现了,我们再建一个数组numnum[i]表示以i为队头的队列的飞船数量,初始时都是1,在每次合并的时候,fx为合并前飞船i的队头,fy为合并前飞船j的队头,每次合并时,先更新g[fx],即给它加上num[fy],然后开始合并,即fa[fx]=fy,最后更新numnum[fy]+= num[fx];num[fx]=0

现在就差最后一步了:如何计算每个飞船到队头的距离。再来分析一下:对于任意一个飞船,我们都知道它的祖先(不一定是队头,但一定间接或直接指向队头),还知道距离它祖先的距离。对于每一个飞船,它到队头的距离,就等于它到它祖先的距离加上它祖先到队头的距离,而它的祖先到队头的距离,也可以变成类似的。可以递归实现,由于每一次更新都要用到已经更新完成的祖先到队头的距离,所以要先递归找到队头,然后在回溯的时候更新(g[i]+=g[fa[i]]),可以把这个过程和find的函数放在一起。

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
#include<bits/stdc++.h>
using namespace std;
const int MAXN=30000+10;
int T;
int fa[MAXN],num[MAXN],g[MAXN];
inline int read()
{
int tot=0;
char c=getchar();
while(c<'0'||c>'9')
c=getchar();
while(c>='0'&&c<='9')
{
tot=tot*10+c-'0';
c=getchar();
}
return tot;
}
inline int find(int k)
{
if(fa[k]==k)return k;
int grandfa=find(fa[k]);
g[k]+=g[fa[k]];
return fa[k]=grandfa;
}
int main()
{
T=read();
for(int i=1;i<=MAXN-10;i++)fa[i]=i,num[i]=1;
char c;
int x,y;
while(T--)
{
cin>>c;
x=read();y=read();
int fx=find(x),fy=find(y);
//cout<<c<<" "<<x<<" "<<y<<endl;
if(c=='M')
{
g[fx]+=num[fy];
fa[fx]=fy;
num[fy]+=num[fx];
num[fx]=0;
}
else
{
if(fx!=fy)cout<<-1<<endl;
else
cout<<abs(g[x]-g[y])-1<<endl;
}
}
return 0;
}

树状数组部分知识

中间看到了树状数组求解逆序对的方式,终于在现在彻底明白了

1
2
3
4
5
6
7
for(int i=n;i;;i--)
{
ans+=ask(a[i]-1);
//查询比a[i]小的个数,因为我们添加的是1,就是默认每一个数出现过一次
add(a[i],1);
//同上
}

因此就可以暴切楼兰图腾一题了,因为只需要统计两次即可。

求比他大的数字就用正序

比他小的就用逆序。

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
#include <iostream>
#include <cstdio>
#include <cstring>

using namespace std;

const int N = 2000010;

typedef long long LL;

int n;
//t[i]表示树状数组i结点覆盖的范围和
int a[N], t[N];
//Lower[i]表示左边比第i个位置小的数的个数
//Greater[i]表示左边比第i个位置大的数的个数
int Lower[N], Greater[N];

//返回非负整数x在二进制表示下最低位1及其后面的0构成的数值
int lowbit(int x)
{
return x & -x;
}

//将序列中第x个数加上k。
void add(int x, int k)
{
for(int i = x; i <= n; i += lowbit(i)) t[i] += k;
}
//查询序列前x个数的和
int ask(int x)
{
int sum = 0;
for(int i = x; i; i -= lowbit(i)) sum += t[i];
return sum;
}

int main()
{

scanf("%d", &n);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);

//从左向右,依次统计每个位置左边比第i个数y小的数的个数、以及大的数的个数
for(int i = 1; i <= n; i++)
{
int y = a[i]; //第i个数

//在前面已加入树状数组的所有数中统计在区间[1, y - 1]的数字的出现次数
Lower[i] = ask(y - 1);

//在前面已加入树状数组的所有数中统计在区间[y + 1, n]的数字的出现次数
Greater[i] = ask(n) - ask(y);

//将y加入树状数组,即数字y出现1次
add(y, 1);
}

//清空树状数组,从右往左统计每个位置右边比第i个数y小的数的个数、以及大的数的个数
memset(t, 0, sizeof t);

LL resA = 0, resV = 0;
//从右往左统计
for(int i = n; i >= 1; i--)
{
int y = a[i];
resA += (LL)Lower[i] * ask(y - 1);
resV += (LL)Greater[i] * (ask(n) - ask(y));

//将y加入树状数组,即数字y出现1次
add(y, 1);
}

printf("%lld %lld\n", resV, resA);

return 0;
}

还有一道求区间和和修改区间和的题,转换为两个树状数组进行求解

主要是公式的化简,不多赘述

线段树部分知识

最大子段和维护

维护子段和部分需要维护四个值一般,一般是左区间最大连续子段和,右区间最大连续子段和,区间最大连续子段和,总和

GCD问题

题目:Interval GCD

巧用\(gcd(a,b)=gcd(b,a-b)\),就可以进行推广:\(gcd(a,b,c)=gcd(a,b-a,c-b)\),一个转化为差分的思路,于是就可以用差分+树状数组维护修改值求出本题

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
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
const int SIZE = 500010;
struct SegmentTree {
int l, r;
long long dat;
} t[SIZE * 4];
long long a[SIZE], b[SIZE], c[SIZE];
int n, m;

long long gcd(long long a, long long b) {
return b ? gcd(b, a%b) : a;
}

// 维护区间gcd的线段树
void build(int p, int l, int r) {
t[p].l = l, t[p].r = r;
if (l==r) { t[p].dat = b[l]; return; }
int mid = (l + r) / 2;
build(p*2, l, mid);
build(p*2+1, mid+1, r);
t[p].dat = gcd(t[p*2].dat, t[p*2+1].dat);
}

void change(int p, int x, long long v) {
if (t[p].l == t[p].r) { t[p].dat += v; return; }
int mid = (t[p].l + t[p].r) / 2;
if (x <= mid) change(p*2, x, v);
else change(p*2+1, x, v);
t[p].dat = gcd(t[p*2].dat, t[p*2+1].dat);
}

long long ask(int p, int l, int r) {
if (l <= t[p].l && r >= t[p].r) return abs(t[p].dat);
int mid = (t[p].l + t[p].r) / 2;
long long val = 0;
if (l<=mid) val = gcd(val, ask(p*2, l, r));
if (r>mid) val = gcd(val, ask(p*2+1, l, r));
return abs(val);
}

long long sum(int x) {
long long y = 0;
for (; x; x -= x & -x) y += c[x];
return y;
}

void add(int x, long long y) {
for (; x <= n; x += x & -x) c[x] += y;
}

int main() {
cin >> n >> m;
for (int i = 1; i <= n; i++) {
scanf("%lld", &a[i]);
b[i] = a[i] - a[i-1];
}
build(1, 1, n);
for (int i = 1; i <= m; i++) {
char str[2]; scanf("%s", str);
int l, r; scanf("%d%d", &l, &r);
if (str[0] == 'Q') {
long long al = a[l] + sum(l);
long long val = l < r ? ask(1, l + 1, r) : 0;
printf("%lld\n", gcd(al, val));
} else {
long long delta; scanf("%lld", &delta);
change(1, l, delta);
if (r < n) change(1, r + 1, -delta);
add(l, delta);
add(r + 1, -delta);
}
}
}

poj2279

f[a1,a2,a3,a4,a5]表示每排从左起占了a1,a2,a3,a4,a5个人的方案数。

f[0,0,0,0,0]=1。

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
#include<bits/stdc++.h>
typedef long long ll;
#define MAXN 20//
using namespace std;
int f[MAXN][MAXN][MAXN][MAXN][MAXN];
int line;
int n[6];
void dp()
{
memset(f,0,sizeof(f));//清零是好习惯(多组数据)
memset(n,0,sizeof(n));
for(int i=1;i<=line;i++) cin>>n[i];
f[0][0][0][0][0]=1;
for(int a=0;a<=n[1];a++)
for(int b=0;b<=n[2];b++)
for(int c=0;c<=n[3];c++)
for(int d=0;d<=n[4];d++)
for(int e=0;e<=n[5];e++)
{
if(a<n[1]) f[a+1][b][c][d][e]+=f[a][b][c][d][e];
if(b<n[2]&&b<a) f[a][b+1][c][d][e]+=f[a][b][c][d][e];
if(c<n[3]&&c<b) f[a][b][c+1][d][e]+=f[a][b][c][d][e];
if(d<n[4]&&d<c) f[a][b][c][d+1][e]+=f[a][b][c][d][e];
if(e<n[5]&&e<d) f[a][b][c][d][e+1]+=f[a][b][c][d][e];
}
}
int main()
{
while(cin>>line&&line)
{

dp();
cout<<f[n[1]][n[2]][n[3]][n[4]][n[5]]<<endl;
}
return 0;
}

牛客挑战赛某场

A

由于关系是确定的,因此我们可以预处理出每个下标需要增加或者说减少的次数

然后让大的数乘上小的下标

小的数乘上大的下标就解决了

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
#include<bits/stdc++.h>
using namespace std;

const int N = 1e5 + 20;
typedef long long ll;
int a[N],b[N],n,m,x,y;
ll ans;

void solve()
{
cin>>n>>m;
for(int i = 1;i <= n; ++i)
{
cin>>a[i];
}
for(int i = 1;i <= m; ++i)
cin>>x>>y,b[x]++,b[y]--;
sort(a + 1,a + n + 1);
sort(b + 1,b + n + 1);
for(int i = 1;i <= n; ++i)
ans += 1LL * a[i] * b[n - i + 1];
cout<<ans;
}

int main()
{
int t = 1;
while(t--) solve();
return 0;
}

A

初始你在坐标轴的0处,你要走到n处

第x步你可以进行以下操作:

  1. 向右走x步
  2. 向左走一步

另一场的A

先看看暴力走要走几步(二分优化)

最后看看哪一步向左走

这样可以省出-1~l-1步数,因为向左走了一步

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
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
#define rep(i,a,n) for(int i=a;i<=n;i++)
#define frep(i,a,n) for(int i=a;i>=n;i--)
#define int long long
#define PII pair<int,int>
#define lowbit(x) (x&(-x))
const int mod=1e9+7;
const double pai=acos(-1.0);
#define ios ios::sync_with_stdio(0); cin.tie(0),cout.tie(0);
const int N=1e6+100;
#define LF(x) fixed<<setprecision(x)
int get(int x)
{
return x*(x+1)/2;
}

signed main()
{
int t;
cin>>t;
while(t--)
{
int l=0;
int r=1e8;
int n;
cin>>n;
while(l<r)
{
int mid=(l+r+1)>>1;
if(get(mid)>=n)
{
r=mid-1;
}
else
{
l=mid;
} }
if(n-get(l)==l)
{
cout<<l+2<<'\n';
}
else
{
cout<<l+1<<'\n';
}
}

return 0;
}

B

给你一个数x,让你将他分成k个质数的和,要求看最小

哥德巴赫猜想:当n为偶数时候一定能够分成两个质数相加,只需要判断一个,再判断另一个

如果是奇数分一个1出来,然后继续哥德巴赫猜想

大致就是欧拉函数判断,然后直接上就可以了。

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
#include <bits/stdc++.h>

using namespace std;
typedef long long int ll;
const ll maxn = 1e7 + 100 ;
int prime[maxn];
int visit[maxn];
map<int, int> maps;
int cnt = 0, s;

void Euler_prime(int n) {
for (int i = 2; i < n; i++) {
if (visit[i] == 0) {
visit[i] = 1;
prime[++cnt] = i;
maps[i] = 1;
}
for (int j = 1; j <= cnt && i * prime[j] < n; j++) {
visit[i * prime[j]] = 1;
if (i % prime[j] == 0)
break ;
}
}
}



int main() {
Euler_prime(maxn);
int test_case;
cin>>test_case;


while (test_case--) {
int n;
cin>>n;
// 特殊情况
if (n < 4 || maps[n] == 1) {
printf("1\n%d = %d\n", n, n);
continue;
}
//偶数
if (n % 2 == 0) {
for (int i = 1; i <= cnt; i++) {
if (maps[n - prime[i]] == 1) {
cout << 2 << endl << n - prime[i] << " + " << prime[i] << " = " << n << endl;
break;
}
}
} else {

if (maps[n - 2] == 1) {
printf("2\n2 + %d = %d\n", n - 2, n);
continue;
}
for (int i = 1; i <= cnt; i++) {
if (maps[n - 1 - prime[i]] == 1) {
printf("3\n1 + %d + %d = %d\n", n - prime[i] - 1, prime[i], n);
break;
}
}
}
}

return 0;
}