『湖南省队集训』博士的计算器

题目

题目描述

一日,BY大神黄犇黄大组长打完羽毛球回来,发现博士正在用魔兽地图编辑器做一个计算器的软件。

因为最近总是一些取模取模的,所以想非常快速的算出来。简而言之,就是对以下$3$个问题进行讨论。

  1. 给定$y, z, P$,计算$y^z ({\rm mod}~ P)$的值;
  2. 给定$y, z, P$,计算满足$y^x \equiv z({\rm mod}~ P)$的最小非负整数$x$;
  3. 给定$y, z, P$,计算$C(z, y)({\rm mod}~ P)$的值(意义为$Z$中取$Y$的组合)。

输入

第一行一个正整数$N$,描述数据组数。

接下来的$N$行,每行$4$个正整数$Sum, y, z, P$。

$Sum$表述询问类型,如上所述。对于第$2$种要求,若$x$不存在,则输出“Math Error”。

输出

要求有$N$行输出,每行一个整数,为询问的答案。

样例输入1

4
1 2 10 1000
2 3 1 1000
2 2 3 4
3 2 7 9

样例输出1

24
0
Math Error
3

样例输入2

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

样例输出2

Math Error
6
6
1

数据范围

操作$1$操作$2$操作$3$
$1 \sim 4$操作个数$< 501$。保证$y, z, p < 10^9$。操作个数为$0$。操作个数为$0$。
$5 \sim 10$操作个数$< 51$。保证$y, z, P < 10^3$。$P$不一定为质数。操作个数$< 11$。保证$y, z < 10^3, P < 10^9$。$P$不一定为质数。
$11 \sim 16$操作个数$< 31$。保证$y, z, P < 10^9$。而且$P$为质数。操作个数$< 31$。保证$y, z < 10^7, P < 10^5$。而且$P$为质数。
$17 \sim 20$操作个数$< 51$。保证$y, z, P < 10^9$。$P$不一定为质数。操作个数$< 51$。保证$y, z < 10^6, P < 10^9$。$P$不为质数。

『湖南省队集训』期望

题目

题目描述

在米国有一所大学,名叫万国歌剧与信息大学(Universal Opera and Informaticas University)。简称UOI大学。UOI大学的建筑与道路分布很有趣,每对建筑之间有且仅有一条直接或者间接的路径相连,更加明确的说,就是成树形分布。 其实在设计时,对于大学的$N$个建筑,总共有$M$条道路可以修建,每条道路都有一个距离值$Dist_i$和一个美学值$Value_i$。一个设计方案的距离值和美学值定义为该设计方案内包含的道路的距离值与美学值之和。投资方的要求只有设计方案的距离值最小。

大神出于对树的喜爱所以决定设计方案必须是一棵树。 因为要参加UOI,所以当时大神就急急忙忙地随机选择了一个合法的方案。但其实存在很多合法的方案,假设每种设计方案取的概率是均等的,那么设计方案的美学值期望是多少?

输入

第一行两个整数,$N$和$M$,意义如上所述。

第二行到第$M+1$行,每行$4$个整数,$X_i, Y_i, Dist_i, Value_i$,分别表示这条道路连接的两个建筑的编号,距离值以及美学值。

输入保证至少有一种合法方案。

输出

一行一个整数,即满足总道路长度最小的情况下,设计方案的美学值期望。 要求保留$5$位小数。

样例输入

2 1
1 2 3 4

样例输出

4.00000

数据范围

$10\%$的数据保证$N \leq 10, M \leq 20$;

$30\%$的数据保证$N \leq 100, M \leq 1000$;

$100\%$的数据保证$N \leq 1000, M \leq 200000$;

$10\%$的数据保证$M=N-1$;

$20\%$的数据保证距离值相同的道路数小于$10$;

$100\%$的数据保证距离值相同的道路数小于$30$,同时不保证没有重边。

『湖南省队集训』嘿嘿嘿嘿的zy

题目

题目描述

HJWJSSB 成年了,父母觉得他是时候去找一个喜欢的人。 他遇到了室友 HAO,在和HAO 一起同居的两年, HJWJSSB 不知不觉中对 HAO 产生了一点好感。 后来 HJWJSSB 换了一个公司,遇到了同事 ciewai。 ciewai 对他很好,经常加班陪他,等他一起回家, HJWJSSB 接触 ciewai 这么久后,也感觉 ciewai 对自己有一些爱慕,可是他又有点放不下 HAO,于是他找到了他曾经的王者,问他怎么办,王者用他智慧的大脑说为了你的后代考虑,你应该给他们出一道题,若谁先做出这道题,谁的智商就更高一些,这样你就能更好的选择。。。

于是 HJWJSSB 拿着王者出的题给了 ciewai 和 HAO,然而 ciewai 很蠢, HAO 很聪明,但是 ciewai 却不想认输,于是 ciewai 找到聪明的你,求你来帮帮 ciewai,让 ciewai 获得真爱。。。

王者出的题目是这样的:

给一颗$n$个点的树,每一个点有一个颜色,然后维护几个操作。

  • 操作$1$:$t=1$时,将$x$点的颜色修改为$y$;
  • 操作$2$:$t=2$时,询问$x$到$y$路径上有多少个不同的颜色段;
  • 操作$3$:$t=3$时,询问$x$到$y$路径上的出现次数最多的颜色的出现次数。

输入描述

第一行两个数$n, Q$,分别表示树的点数和操作个数。

第二行$n$个数,分别表示每个点的颜色。

第$3$行到第$n + 1$行每行两个数$x, y$表示$x$到$y$有一条无向边。

第$n + 2$行到第$n + Q + 1$行每行$3$个数$t, x, y$。

输出描述

输出的行数为询问个数。

每一行输出当前询问的答案。

样例输入

5 4
2 3 1 3 1
1 2
1 3
2 4
2 5
2 4 3
3 4 5
1 3 2
2 3 4

样例输出

3
2
2

数据范围

对于$20\%$的数据,$1 \leq n \leq 100$,$1 \leq Q \leq 100$;

另有$15\%$的数据,树是一条链,只有操作$1$和操作$2$;

另有$15\%$的数据,只有操作$1$和操作$2$;

另有$15\%$的数据,树是一条链,只有操作$1$和操作$3$;

另有$15\%$的数据,只有操作$1$和操作$3$;

对于$100\%$的数据,$1 \leq n \leq 100000$,$1 \leq Q \leq 100000$,$0 \leq 颜色大小 \leq 100000$。

后记

最后在你的帮助下 ciewai 赢了 HAO,可是 HJWJSSB 还是十分犹豫,于是他去问了他曾经的王者,王者告诉他说既然你觉得很难选择,那就不如都选择!!!

于是 HJWJSSB 和 ciewai,还有 HAO 幸福快乐的生活在了一起。。。。。。

Orz 祯哥,祝祯哥早日得到妹子的认可, NOI 进队!!!

『湖南省队集训』疯狂BB的zy

题目

题目描述

zy 超强, 总是想些奇怪的问题。

一天,他脑洞出了这样一个问题。 有$n$个点, 每个点的度数不超过$A_i$, 问从中选出一些点形成一颗大小为$s(1 \leq s \leq n)$的树的方案数。 zy 自然$O(n)$秒掉了这题,于是他想用这个问题考考你,来显示自己的强大。

输入描述

第一行有一个整数$n$。

第二行$n$个数表示$A_i$。

输出描述

一行$n$个数,第$s$个数表示形成大小为$s$的树的方案数对$1004535809(497 \times 2^{21} + 1)$取模的结果。

样例输入

3
2 2 1

样例输出

3 3 2

数据范围

对于$20\%$的数据,$n \leq 6$;

对于$60\%$的数据,$n \leq 50$;

对于$100\%$的数据,$n \leq 100$。

题解

考虑无根树的prufer序列。一个结点在prufer序列中出现的次数就等于它的度数$-1$,因此问题就转化为在序列中每个数出现次数有限制的计数问题。

考虑如下的状态表示:$ans[i][j][k]$表示计算到前$i$个结点时,已经考虑过$j$个结点,并且已经填上序列中的$k$个位置的方案数。

那么转移就很显然了,首先不考虑就是$ans[i][j][k] = ans[i - 1][j][k]$,考虑的话,还需要枚举该结点编号出现次数$time(0 \leq time < A[i])$,这样带来的方案总数为

$$\sum_{time = 0}^{k}C_{k}^{time}ans[i - 1][j - 1][k - time]$$

两种情况相加即可。这个DP的时间复杂度显然是$O(n^4)$。

最后的输出嘛,首先输出$1$个结点的答案$n$,然后对每个$s$输出$ans[n][s][s - 2]$,其中$s - 2$就是prufer序列的长度。

代码

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
#include <cstdio>
#define MAXN 120
#define MOD 1004535809
int C[MAXN][MAXN];
int ans[MAXN][MAXN][MAXN];
int n;
int a[MAXN];
int main()
{
freopen("zy.in", "r", stdin);
freopen("zy.out", "w", stdout);
scanf("%d", &n);
C[0][0] = 1;
for (int i = 1; i <= n; i++)
{
C[i][0] = C[i][i] = 1;
for (int j = 1; j < i; j++)
C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;
}
for (int i = 1; i <= n; i++)
scanf("%d", &a[i]);
ans[0][0][0] = 1;
for (int i = 1; i <= n; i++)
{
for (int j = 0; j < i; j++)
for (int k = 0; k <= n; k++)
ans[i][j][k] = (ans[i][j][k] + ans[i - 1][j][k]) % MOD;
for (int time = 0; time < a[i]; time++)
for (int j = 1; j <= i; j++)
for (int k = time; k <= n; k++)
ans[i][j][k] = (ans[i][j][k] + (long long)C[k][time] * ans[i - 1][j - 1][k - time] % MOD) % MOD;
}
printf("%d", n);
for (int i = 2; i <= n; i++)
printf(" %d", ans[n][i][i - 2]);
printf("\n");
return 0;
}

『湖南省队集训』永不落幕的zy

题目

背景

人们的感情像洪水一样袭来,头晕目眩。

题目描述

zy 是一位拥有“共感”能力的少年,他总是能有意无意地感知别人的心情。 sm 是一位有着“心墙”的少女,她的感情无法传递出去。就像命运一般,他们相遇了,仿佛 zy(的能力)就是为了 sm 而存在一样(事实上也确实如此)。

sm 的心墙由许多迷茫组成,每个迷茫都希望从$X$轴上某一点去到另一点,而只有 zy 能消除 sm 的迷茫。面对数量如此庞大的迷茫, zy 不曾想要放弃,但有些迷失了方向。

假设现在时间是$0$, zy 在心墙的$1$位置, zy 每单位时间的移动速度是$1$。

若现在 zy 携带的迷茫想要去的地点和心墙上剩下的迷茫中在他右边的个数大于等于左边,则 zy 会向右移动,反之向左。若现在 sm 没有任何迷茫,则 zy 原地不动。现在 zy 想
知道,组成心墙的每个迷茫分别在什么时候消失。

为了尽快地让 zy 能拥抱 sm,请你帮帮他。

输入描述

第一行两个数$N, M$表示迷茫的数量和心墙的大小。接下来$N$行,每行$3$个整数,$t, a, b$,表示这个迷茫出现在时刻$t$,希望从$a$到$b$。

输出描述

$N$行$N$个整数,表示每个迷茫分别在什么时候消失。

样例输入

2 10
1 2 5
7 4 5

样例输出

5
9

样例解释

时刻$1$,有迷茫出现了!

时刻$2$, zy 移动到了位置$2$。

时刻$5$, zy 移动到了位置$5$,迷茫消失。

时刻$7$,有迷茫出现了!

时刻$8$, zy 移动到了位置$4$。

时刻$9$, zy 移动到了位置$5$,迷茫消失。

数据范围

对于$30\%$的数据,$N, M, t \leq 100$;

对于$60\%$的数据,$N, M, t \leq 10000$;

对于$100\%$的数据,$N, M, t \leq 100000$。

后记

我们的落幕,是新的开始。

附件



题解

题意非常的玄妙,形象地说,就是公交车在路上跑,一段时间后会出现一个指定出发地和目的地的乘客,如果公交车右边的未上车乘客的出发地个数和车上乘客的目的地个数之和大于等于左边的,那就往右跑,否则往左跑,注意这是实时更新的,每出现一位乘客或一位乘客下车都可能导致方向改变。然后求每位乘客下车时间。

用线段树记录所有迷茫的位置,支持插入,删除(注意如果是乘客上车的话还要再插入一个目的地,要区分上下车的情况),区间内点数询问,求前驱后继,注意实现细节,特别是对于事件点重合的情况要非常小心。这样按照题意模拟运动就好,当然一次不止运动一个单位,在事件点不变的情况下能走多远走多远。

代码

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
#include <cstdio>
#include <cctype>
#include <cmath>
#include <algorithm>
#include <vector>
using namespace std;
#define MAXN 100010
int readint()
{
int x = 0, c;
while (!isdigit(c = getchar()));
do
x = x * 10 + c - '0';
while (isdigit(c = getchar()));
return x;
}
int n, m;
struct mimang
{
long long t;
int a, b, id;
mimang() {}
mimang(long long t, int a, int b, int id) : t(t), a(a), b(b), id(id) {}
};
bool operator <(mimang x, mimang y)
{
if (x.t != y.t)
return (x.t < y.t);
if (x.a != y.a)
return (x.a < y.a);
if (x.b != y.b)
return (x.b < y.b);
return (x.id < y.id);
}
mimang q[MAXN];
struct node
{
int sum, minpos, maxpos, l, r;
vector<int> ms;
node () {}
node(int l, int r) : l(l), r(r), sum(0), minpos(200100), maxpos(0) {}
};
node segtree[MAXN << 2];
inline void update(int x)
{
segtree[x].sum = segtree[x << 1].sum + segtree[(x << 1) | 1].sum;
segtree[x].minpos = min(segtree[x << 1].minpos, segtree[(x << 1) | 1].minpos);
segtree[x].maxpos = max(segtree[x << 1].maxpos, segtree[(x << 1) | 1].maxpos);
}
void build(int now, int l, int r)
{
segtree[now] = node(l, r);
if (l < r)
{
int mid = l + (r - l) / 2;
build(now << 1, l, mid);
build((now << 1) | 1, mid + 1, r);
}
}
int query(int now, int l, int r)
{
int nl = segtree[now].l, nr = segtree[now].r;
if (l <= nl && r >= nr)
return segtree[now].sum;
if (l > nr || r < nl)
return 0;
return query(now << 1, l, r) + query((now << 1) | 1, l, r);
}
void insert(int now, int pos, int id)
{
int nl = segtree[now].l, nr = segtree[now].r;
if (nl == nr)
{
segtree[now].sum++;
segtree[now].minpos = segtree[now].maxpos = nl;
segtree[now].ms.push_back(id);
return;
}
int mid = nl + (nr - nl) / 2;
if (pos <= mid)
insert(now << 1, pos, id);
else
insert((now << 1) | 1, pos, id);
update(now);
}
int workl(int now, int pos)
{
int nl = segtree[now].l, nr = segtree[now].r;
if (nl == nr)
return segtree[now].maxpos;
int mid = nl + (nr - nl) / 2;
if (pos <= mid)
return workl(now << 1, pos);
else
return max(workl(now << 1 | 1, pos), segtree[now << 1].maxpos);
}
int workr(int now, int pos)
{
int nl = segtree[now].l, nr = segtree[now].r;
if (nl == nr)
return segtree[now].minpos;
int mid = nl + (nr - nl) / 2;
if (pos <= mid)
return min(workr(now << 1, pos), segtree[(now << 1) | 1].minpos);
else
return workr((now << 1) | 1, pos);
}
long long now, pos;
long long time, ans[MAXN];
int cnt;
void clear(int now, int pos)
{
int nl = segtree[now].l, nr = segtree[now].r;
if (nl == nr)
{
for (int i = 0; i < (int)segtree[now].ms.size(); i++)
{
int v = segtree[now].ms[i];
if (v > 0)
insert(1, q[v].b, -v);
else
ans[q[-v].id] = time, cnt++;
}
segtree[now].ms.clear();
segtree[now] = node(pos, pos);
return;
}
int mid = nl + (nr - nl) / 2;
if (pos <= mid)
clear(now << 1, pos);
else
clear((now << 1) | 1, pos);
update(now);
}
int main()
{
freopen("sm.in", "r", stdin);
freopen("sm.out", "w", stdout);
n = readint();
m = readint();
for (int i = 1; i <= n; i++)
{
int t = readint();
int a = readint();
int b = readint();
q[i] = mimang(t, a, b, i);
}
cnt = 0, now = 1, time = 0, pos = 0;
sort(q + 1, q + n + 1);
build(1, 1, m);
while (cnt < n)
{
if (query(1, 1, m) == 0)
{
if (pos == n)
break;
time = q[pos + 1].t;
pos++;
while (pos <= n && q[pos].t == time)
{
insert(1, q[pos].a, pos);
pos++;
}
pos--;
}
else
{
int left = query(1, 1, now - 1);
int right = query(1, now + 1, m);
long long ls;
if (right >= left)
ls = workr(1, now);
else
ls = workl(1, now);
if (pos == n || time + abs(now - ls) <= q[pos + 1].t)
{
time = time + abs(now - ls);
now = ls;
clear(1, ls);
if (time == q[pos + 1].t)
{
pos++;
while (pos <= n && q[pos].t == time)
{
insert(1, q[pos].a, pos);
pos++;
}
pos--;
}
}
else
{
pos++;
if (right >= left)
now += q[pos].t - time;
else
now -= q[pos].t - time;
time = q[pos].t;
while (pos <= n && q[pos].t == time)
{
insert(1, q[pos].a, pos);
pos++;
}
pos--;
}
}
}
for (int i = 1; i <= n; i++)
printf("%lld\n", ans[i]);
return 0;
}

『湖南省队集训』Philosopher

题目


“ By all means marry, if you get a good wife, you’ll be happy, if you get a bad one, you’ll become a philosopher.” ——Socrates

给定一个长度为$n$的序列,$m$次操作, 操作有两种:一种是将其一个区间升序/降序排序,一种是询问区间元素积的十进制下最高位是什么数。

输入第一行两个正整数$n$和$m$。第二行$n$个正整数代表初始序列。

后面$m$行每行为“1 le ri flag”代表将$[le,ri]$这个区间排序,如果$flag$为$1$则为升序排序。为$0$则是降序排序。

如果这行是“2 le ri”则是询问区间$[le,ri]$的元素积最高位。

对于每一个询问输出一行即为答案。

测试点编号 特征
$0 \sim 3$ 没有1操作
$4 \sim 7$ $n,m \leq 1000$且区间乘积在long long范围内
$8 \sim 11$ $n,m \leq 1000$
$12 \sim 19$ $n,m \leq 200000, 1 \leq val \leq n$

评分方式在前$12$个测试点时为去除行末空格后全文比较。

后$8$个测试点有spj。每个测试点如果与标准答案有超过$20$个不同的地方就得$0$分。否则$5$分。

『湖南省队集训』Wallace

题目


“看来华莱士先生不太懂历史和哲♂学” ——自长者与美国记者华莱士的谈话

有两颗各含$N$个点的无根树,每棵树中点分别被编号为$0,1,2,\dots,N-1$;注意两棵树并不保证同构。另外给一个$N$长的整数数组$Score[]$,记录$N$个编号的得分,$Score$中的每个元素可正可负。问题的任务是寻找集合$\{0,1,2,3,4,\dots,N-1\}$的一个最优子集$subset$(可以是空集),要求满足以下条件:

  1. 在第一棵树中,$subset$中包含的编号对应的点能构成一个连通的子图;即去掉这棵树中所有$subset$中不包含的点后,剩下的点依然是一棵连通的树。
  2. 在第二棵树中,$subset$中包含的编号对应的点也能构成一个连通的子图;
  3. 使$subset$包含编号的总得分尽可能的大;即$\sum_{i \in subset}Score[i]$
    能取到尽可能大的值。

输入第一行一个正整数$N$。第二行$N$个整数$Score[]$代表$0 \sim N-1$每个点的权值。

后面$N-1$行每行两个整数$u,v$代表第一棵树里面$u$点和$v$点之间有连边。

再后面$N-1$行每行两个整数$u,v$代表第二棵树里面$u$点和$v$点之间有连边。

输出一行一个整数代表这个$subset$包含编号的总分的最大值。

测试点编号 特征
$0 \sim 5$ $N \leq 15$
$6 \sim 9$ 两棵树形状与编号完全相同
$10 \sim 24$ $2 \leq N \leq 50$且$-1000 \leq score[i] \leq 1000$且$0 \leq u, v < N$

题解

因为选出的点集非空,我们不妨枚举每一个点,钦定它在点集内,然后计算此时的最大权值和,最后再取最优解,就可以不漏掉最优解了。我们令钦定的这个点做两棵树树根,通过DFS求出每个点在两棵树中的父亲,不难发现一个点和根节点连通的条件是它的父亲和根节点连通。

因此,如果要让某一个点加入连通块,必须先把它的父亲加入,这样问题就转化为每个点选取的必要条件是它在两棵树上的父亲都被选取,问题转化为经典的最大权闭合图模型,使用网络流算法即可解决。

代码

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
#include <cstdio>
#include <cstring>
#include <vector>
#include <algorithm>
#include <queue>
using namespace std;
#define MAXN 60
#define INF 19260817
int n, S, T, N;
int w[MAXN];
vector<int> link[2][MAXN];
bool vis[MAXN];
int fa[2][MAXN];
void dfs(int t, int now)
{
vis[now] = 1;
int sz = (int)link[t][now].size();
for (int i = 0; i < sz; i++)
{
int v = link[t][now][i];
if (!vis[v])
{
fa[t][v] = now;
dfs(t, v);
}
}
}
int cap[MAXN][MAXN];
int dep[MAXN];
queue<int> q;
bool bfs()
{
memset(dep, -1, sizeof(dep));
dep[S] = 1;
q.push(S);
while (!q.empty())
{
int v = q.front();
q.pop();
for (int i = 0; i < N; i++)
{
if (cap[v][i] && dep[i] < 0)
{
dep[i] = dep[v] + 1;
q.push(i);
}
}
}
return (dep[T] > 0);
}
int find(int s, int low)
{
if (s == T)
return low;
for (int i = 0; i < N; i++)
{
if (dep[i] == dep[s] + 1 && cap[s][i])
{
int t;
if (t = find(i, min(low, cap[s][i])))
{
cap[s][i] -= t;
cap[i][s] += t;
return t;
}
}
}
return 0;
}
int dinic()
{
int ans = 0;
while (bfs())
{
int d;
while (d = find(S, INF))
ans += d;
}
return ans;
}
int main()
{
freopen("wallace.in", "r", stdin);
freopen("wallace.out", "w", stdout);
scanf("%d", &n);
S = n + 3, T = n + 4, N = n + 5;
for (int i = 0; i < n; i++)
scanf("%d", &w[i]);
for (int t = 0; t < 2; t++)
{
for (int i = 1; i < n; i++)
{
int u, v;
scanf("%d%d", &u, &v);
link[t][u].push_back(v);
link[t][v].push_back(u);
}
}
int ans = 0;
for (int root = 0; root < n; root++)
{
memset(fa, 0, sizeof(fa));
memset(vis, 0, sizeof(vis));
dfs(0, root);
memset(vis, 0, sizeof(vis));
dfs(1, root);
memset(cap, 0, sizeof(cap));
for (int i = 0; i < n; i++)
if (w[i] >= 0)
cap[S][i] = w[i];
else
cap[i][T] = -w[i];
for (int i = 0; i < n; i++)
{
if (i != root)
{
cap[i][fa[0][i]] = INF;
cap[i][fa[1][i]] = INF;
}
}
dinic();
int t = 0;
for (int i = 0; i < n; i++)
t += cap[S][i];
ans = max(ans, t);
}
printf("%d\n", ans);
return 0;
}

『湖南省队集训』DeepDarkFantasy

题目


从东京出发,不久便到一处驿站,写道:日暮里。 ——鲁迅《藤野先生》

定义一个置换的平方为对$1 \sim n$的序列做两次该置换得到的序列。已知一个置换的平方,并且这个结果是一个排列, 求该置换。

输入第一行一个数$n$表示排列长度, 接下来一行$n$个数描述排列。

有解则输出一行$n$个数表示原排列。 否则输出一行一个$-1$。

测试点编号 特征
$0 \sim 1$ $n \leq 10$
$2 \sim 9$ $n \leq 1000000$

此题有spj。

题解

首先要解释清楚题意对吧,题面中置换和排列真是乱七八糟。

大意就是,初始排列是$(1, 2, \dots, n)$,你要构造一个置换,然后对这个排列进行置换,把得到的排列再进行一次置换,得到的结果为给定排列。举个例子,假设$n=4$,我们构造的置换是$\begin{pmatrix}1 & 2 & 3 & 4\\ 3 & 4 & 2 & 1\end{pmatrix}$,那么置换一次得到$(4, 3, 1, 2)$,再做一次得到$(2, 1, 4, 3)$,这就满足样例了。

我们再回顾一下置换的定义,就是把一个序列中的每个数放到一个指定的位置,而给出的排列则是每个位置上的数。因此,我们求出给出排列的逆,就得到了每个数所在的位置,也就是另一个置换。这时,就可以形象地说,我们要把置换“开方”,解除连做两次等于它的一个置换。

根据基本知识,一个置换可以分解成若干个循环。这些循环中,长度为奇数的循环是可以直接开方的,因为走两次向右移了一步,等价于每一次向右移$\frac{len+1}{2}$步,因此每个元素对应的结果就是在它后面这么多位的元素。长度为偶数的则需要找一个和它长度一致的,然后交叉合并就好了。这里不是很容易说清楚,还是画个图理解一下吧。

注意代码实现,最好保证排序、找等长循环等步骤严格线性,不然容易T。

代码

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
#include <cstdio>
#include <cctype>
#include <cstring>
#include <stack>
#include <vector>
#include <algorithm>
using namespace std;
#define MAXN 1000010
int readint()
{
int x = 0, c;
while (!isdigit(c = getchar()));
do
x = x * 10 + c - '0';
while (isdigit(c = getchar()));
return x;
}
int n;
int ff[MAXN], f[MAXN];
bool vis[MAXN];
vector<int> round[MAXN];
int cnt;
int temp[MAXN];
int times[MAXN];
inline void work(int st)
{
while (!vis[st])
{
vis[st] = 1;
round[cnt].push_back(st);
st = ff[st];
}
cnt++;
}
stack<char> put;
inline void printint(int x)
{
while (x)
{
put.push((x % 10) + '0');
x /= 10;
}
while (!put.empty())
{
putchar(put.top());
put.pop();
}
}
int main()
{
freopen("deepdarkfantasy.in", "r", stdin);
freopen("deepdarkfantasy.out", "w", stdout);
n = readint();
for (int i = 1; i <= n; i++)
{
int x = readint();
ff[x] = i;
}
memset(vis, 0, sizeof(vis));
for (int i = 1; i <= n; i++)
if(!vis[i])
work(i);
for (int i = 0; i < cnt; i++)
times[(int)round[i].size()]++;
for (int i = 1; i <= n; i++)
times[i] += times[i - 1];
for (int i = 0; i < n; i++)
temp[--times[(int)round[i].size()]] = i;
memset(vis, 0, sizeof(vis));
for (int i = 0; i < cnt; i++)
{
int sz = (int)round[temp[i]].size();
if (sz & 1)
{
for (int j = 0; j < sz; j++)
f[round[temp[i]][j]] = round[temp[i]][(j + (sz + 1) / 2) % sz];
}
else
{
if (vis[temp[i]])
continue;
if (i >= cnt - 1 || sz != (int)round[temp[i + 1]].size())
{
printf("-1\n");
return 0;
}
int t = temp[i + 1];
for (int j = 0; j < sz; j++)
f[round[temp[i]][j]] = round[t][j], f[round[t][j]] = round[temp[i]][(j + 1) % sz];
vis[temp[i + 1]] = true;
}
}
for (int i = 1; i <= n; i++)
{
printint(f[i]);
if (i < n)
putchar(' ');
else
putchar('\n');
}
return 0;
}
,