因为多项式卷积和拉格朗日插值我还没学过,所以只能写 T1、T2 的题解了。
神兵 360 把我的 IDE 搞坏了(说什么我的 ide 有木马),赛时写代码写得很费力,也没有了格式化代码的美观。
不过明年这个时候估计切紫也不是很大问题了,来年再战中级组。
总结:T1 领签到太慢,T2 因为自己太菜了所以想了 3h 也没想出完全的正解,T3T4 看众多巨佬都没拿多少分就打打暴力就跳了。
感觉 T2 是决胜题,做出来 1=,没做出来只能混个暴力分跑路,也就顶多 2= 了。
进入正题。
初级组 T1 和 T2 是比签到还签到的题。
中级组签到提势如猛虎,拼尽全力终于战胜。
有 n n n 名乘客依次登上飞机,第 i i i 名乘客会在第 s i s_i si 秒于第 0 0 0 个座位处出现,可以每秒从第 i i i 个座位移动到第 i + 1 i+1 i+1 个位置。
第 i i i 名乘客会移动到第 p i p_i pi 个座位之后花费 a i a_i ai 秒放置自己的行李,然后就座。
然而,因为飞机的过道非常狭窄,人们无法在过道中交换先后位置。在前方乘客放置行李时,他们只能等在前一个人的后面(人的体积可以忽略不计)。
求出每一位乘客的就坐时间 c i c_i ci。
1 ≤ n ≤ 10 6 , 1 ≤ s i , p i , a i ≤ 10 9 1 \le n \le 10^6,1 \le s_i,p_i,a_i \le 10^9 1≤n≤106,1≤si,pi,ai≤109。
难点在于阅读题目。可以发现会导致一个人坐上座位的时间延后当且仅当有一个先上机的人的座位在他途径的路上,而且在他要上机的时候还没有坐下,这时候就必须要等他坐在他的座位上才能继续前进。
O ( n 2 ) O(n^2) O(n2) 的很好想。根据数学直觉可以写出以下代码:
#include
#define int long long
using namespace std;
const int N = 1000010;
int s[N], p[N], a[N];
int n;
int t[N];
signed main() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> s[i];
for (int i = 1; i <= n; i++)
cin >> p[i];
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
t[i] = s[i] + p[i] + a[i];
for (int j = 1; j < i; j++) {
if (p[j] > p[i])
continue;
t[i] = max(t[i], max(s[i], t[j]) + p[i] - p[j] + a[i]);
}
cout << t[i] << endl;
}
return 0;
}
不开 long long 见祖宗。
很容易发现 p[i]+a[i]
可以提出来,最后在输出答案时加上。经历一些变换可得:
for (int i = 1; i <= n; i++) {
t[i] = s[i];
for (int j = 1; j < i; j++) {
if (p[j] > p[i])
continue;
if (s[i] >= t[j])
t[i] = max(t[i], s[i] - p[j]);
else
t[i] = max(t[i], t[j] - p[j]);
}
t[i] += a[i] + p[i];
cout << t[i] << endl;
}
很容易发现 s[i] - p[j]
一定 < s[i]
,所以将初始值为 s[i]
的 t[i]
和其取最大值是完全没有必要的!
于是就变成了找 j j j 使得 t j − p j t_j - p_j tj−pj 最大,这个东西可以使用离散化之后树状数组来处理(也可以使用动态开点线段树),得到 O ( n log n ) O(n \log n) O(nlogn) 的复杂度,可以通过。
#include
#define mid ((l + r) >> 1)
#define int long long
using namespace std;
const int N = 1000010;
int s[N], p[N], a[N];
int n;
int t[N];
struct Seg_tree {
int node[N * 15], ls[N * 15], rs[N * 15], cnt = 1;
void pushup(int now) {
node[now] = max(node[ls[now]], node[rs[now]]);
}
int query(int now, int l, int r, int ql, int qr) {
if (ql <= l && r <= qr)
return node[now];
int res = 0;
if (ql <= mid)
res = max(res, query(ls[now] ? ls[now] : (ls[now] = ++cnt), l, mid, ql, qr));
if (mid < qr)
res = max(res, query(rs[now] ? rs[now] : (rs[now] = ++cnt), mid + 1, r, ql, qr));
return res;
}
void modify(int now, int l, int r, int pos, int val) {
if (l == r) {
node[now] = max(node[now], val);
return;
}
if (pos <= mid)
modify(ls[now] ? ls[now] : (ls[now] = ++cnt), l, mid, pos, val);
else
modify(rs[now] ? rs[now] : (rs[now] = ++cnt), mid + 1, r, pos, val);
pushup(now);
}
} st;
signed main() {
ios::sync_with_stdio(0), cin.tie(0), cout.tie(0);
//上面这行代码一定要加上,不然会 TLE 85
cin >> n;
for (int i = 1; i <= n; i++)
cin >> s[i];
for (int i = 1; i <= n; i++)
cin >> p[i];
for (int i = 1; i <= n; i++)
cin >> a[i];
for (int i = 1; i <= n; i++) {
t[i] = max(st.query(1, 1, 1e9, 1, p[i]), s[i]) + p[i] + a[i];
cout << t[i] << endl;
st.modify(1, 1, 1e9, p[i], t[i] - p[i]);
}
return 0;
}
这道题坠机了。
题意很简单。但是众所周知题意越简单事越大。
给定 n n n 点 m m m 边无向图,边带权。 q q q 次查询。每一次询问 ( u , v , w ) (u,v,w) (u,v,w) 求是否存在从 u → v u \to v u→v 的路径使得其路径边权和是 w w w 的倍数。特别地, w = 0 w=0 w=0 时如果路径边权和是 0 0 0 也可以。存在则输出 bougain
,不存在则输出 villea
。
路径可以经过重点和重边,不保证图是有向图。
带有一些特殊性质(使用输入的 o o o 表示),表格如下:
1 ≤ n , m , q ≤ 10 6 , 0 ≤ w ≤ 2 60 − 1 1 \le n,m,q \le 10^6,0 \le w \le 2^{60}-1 1≤n,m,q≤106,0≤w≤260−1
预估难度:NOIP T3 ∼ 省选 D1T1。那这道题是下位紫是吧。舒坦了。
实际上这个东西应该算思维题,只需要对二分图和 dfs 有亿点点深入的了解就行了。
事实证明我在赛时的想法就是正确的想法,而且我在赛时的时候已经逼近正解了,但是我并没有完全达到正解,所以说我的思维还是太慢了。
因为我在赛时的想法是正解思考链路的真子集(真的是真子集,正解没有想到的我都没有想到,但是却有一部分我独立想到了),所以就分两块讲:一块是自己的思考,一块是自己没有想到但是正解思考链路里面有的。
显然可以 dfs,但是这样的话遇到环就寄了。
发现两个点如果不连通,那么怎样都是输出 villea
。
很容易发现,当 ( u , v ) (u,v) (u,v) 之间存在一条路径,就可以在上面来回走重复的路径(奇偶性可得),最终得到 w w w 的倍数。
例如 w = 3 w = 3 w=3,可以 u → v → u → v u \to v \to u \to v u→v→u→v,路径上每一条边都被重复了 3 3 3 次,最终的和显然就是 3 3 3 的倍数。
这个时候所有边的边权都是 0 0 0,询问的 w w w 都是 0 0 0。
很容易发现,这个时候只要是路径就会满足条件,因为 0 0 0 是 0 0 0 的倍数。
所以可以使用 dfs/bfs/并查集 来判断都可以,就拿到了 5 pts。
实际上赛时我只挑了这个东西来打暴力(因为这个东西性价比最高),所以只得了 5pts。我一直在想正解以至于没有打其他的暴力了。
插叙:很容易发现,因为在普通的带余除法中 0 0 0 不能作为除数,所以在普通的情况下也需要特殊判断 w = 0 w=0 w=0 的情况。至于怎么判断,就可以使用这个 subtask 的方法,把边权为 0 0 0 的边提取出来然后判断连通性即可。
在没有特殊点的时候,路径可以带有环,这个时候怎么处理呢?
很容易发现,再复杂的路径,其都可以表示为 i → a i \to a i→a 的某一条不一定简单的路径和 a → j a \to j a→j 的某条不一定简单的路径。这三者可以相等。
于是就容易想到了 Floyd
算法。但是这个时候我们要判断的是是否是 k k k 的倍数,所以很容易想到可行性 Floyd
。
设 d p i , j , x dp_{i,j,x} dpi,j,x 表示从 i → j i \to j i→j 的任意一条不一定简单路径的边权和除 k k k 余 x x x 是否可行,转移不在阐述。这样可以做到 O ( n 3 × k 3 ) O(n^3 \times k^3) O(n3×k3),如果使用 bitset 优化以下的话还可以除以一个 64 64 64 还是 32 32 32,在 3s 的时限里面能过。
但是这种方法及其严苛,没有进一步优化的出路。
这样就可以拿到 [ 5 , 20 ] [5,20] [5,20] pts。
这个 subtask 还是很重要的,有助于启发正解。
这个时候就需要我们判断是否存在一个长度为偶数的路径,开始上难度了!
显然不连通是找不到的。
如果已经知道存在一个长度为偶数的路径,就是找到了(可以使用黑白染色)。
如果是奇数长度的路径,就需要找一个边数为奇数的环。(也需要使用黑白染色判断二分图,如果是二分图则总是会找不到,否则总是会找到)
总结一下。先对图进行黑白染色。如果不是二分图,就说明两个点之间一定存在偶数长度路径(如果天然就可以找到偶数的话一定可以,如果是奇数长度也可以通过奇数长度的环来补救)
如果是二分图,存在偶数长度路径当且仅当是同一边的点。(不言自明)
所以复杂度是线性的,直接拿下 40pts。
一般来说 subtask 5 和 subtask 6 的这两个是买一送一的,攻破了 subtask 5 再想不出来 subtask 6 就是你的问题了。
对于 w = 0 w=0 w=0 的情况,可以使用 subtask 4 的方法。
对于 w = 1 w=1 w=1 的情况,直接判断在不在同一个连通块里面即可。因为任何书都是 1 1 1 的倍数。
w = 3 w=3 w=3 同理。
对于 w = 2 w=2 w=2 的情况,显然容易想到使用 o = 2 o=2 o=2 的,但是边权不是 1 1 1 怎么办呢?
首先根据余数的可加性,可以通过 m o d 2 \bmod \ 2 mod 2 的方式把每一条边的边权都变成 0 0 0 或 1 1 1。对于边权为 0 0 0 的情况,怎么走都无所谓,所以可以把两边的颜色统一。对于边权为 1 1 1 的情况交替染色就可以了。然后采用 subtast 5 的判断方式判断即可。
复杂度也是线性的,55pts 拿下!
(真的,我考试时想到了这里,但是没时间写了)
w w w 为偶数的时候,不难想到将 w w w 化为 2 a × b 2^a \times b 2a×b 的形式。其中 b b b 为奇数, a a a 是正整数。
很容易发现 b b b 也可以通过像 w w w 为奇数的时候通过来回走同样的路径实现,转化为处理 w = 2 a w = 2^a w=2a 的情况。
(赛时思考止步于此,可以看见已经很近了吧)
下面的就是照着题解加上一点感受写得。
注意这里是树的情况,也就是一定是一个二分图,没有环,更不会有奇环。
观察到 2 a 2^a 2a 这种数,发现会形成倍数关系。
所以再询问的时候不难想到一定存在一个 x x x 使得当 a ≤ x a \le x a≤x 的时候一定存在路径,而 a > x a>x a>x 的时候一定不存在路径。为了更加准确,我们显然需要求出最大的 x x x。
首先 x x x 是有一个下界的。记整个连通块中所有边的 gcd \gcd gcd 为 g g g,设 g = 2 a × b g = 2^a \times b g=2a×b,则显然 x ≥ a x \ge a x≥a。
好的,那么后面我们就不能那么确定的了。直接把所有边的边权都除以 2 a 2 ^ a 2a 先,然后也把 w w w 除以 2 a 2^a 2a。(如果这个时候 w ≤ 2 a w\le 2^a w≤2a 那就直接输出可以了)
显然这个时候 w w w 还是一个偶数。
显然这个时候一定会出现边权为奇数的边。这时候 subtask 5 又发挥了作用:黑白染色。
但是这个时候边权不一定为 1 1 1 怎么办?subtask 6 又登场了。所以不管怎样,这个黑白染色必须要染!
染色结束后再看。 ( u , v ) (u,v) (u,v) 如果染成了不同的颜色,就说明这两个之间无论如何都是奇数了,显然不可以。
如果染成了相同的颜色,就说明一定存在。为什么呢?我们可以求出任何一条边数为奇数的路径,然后在上面重复走很多次就可以了。
这个 subtask 是真的有点动脑,但是东西还不错,分数来到了 70pts。
实际上 subtask 7,8 就已经涵盖了很多情况,只不过是从没有环变成了有环。所以我们再黑白染色之后还要再想想怎么判断。
如果此时这个图是二分图,就直接使用 subtask 7,8 说过的东西判断即可。
如果不是,就说明一定存在奇环。继续使用 subtask 5 的结论(即“如果不是二分图,就说明两个点之间一定存在偶数长度路径”),发现不管怎样都直接输出 bougain
即可。
感觉这道题出的挺好的,但是放在中级组 T2 显然不合适,这是提高组级别的比赛啊。
说实话我感觉这个子任务还是很吃香的,可以把你提前需要思考的东西都告诉你,到了后期解题阶段就直接咔咔结论乱用就可以了。
代码就不给了。实际上子任务的代码非常的长,尤其是 subtask 6 的时候。但是正解还真的是挺短的。