2024.8 - Record of Questions and Summary of Methodology
先分享一个歌单:
永无止境的八月!
2024/08/01
先来点重量级的
P4768 [NOI2018] 归程
题面:
[NOI2018] 归程
题目描述
本题的故事发生在魔力之都,在这里我们将为你介绍一些必要的设定。
魔力之都可以抽象成一个 n n n 个节点、 m m m 条边的无向连通图(节点的编号从 1 1 1 至 n n n)。我们依次用 l , a l,a l,a 描述一条边的长度、海拔。
作为季风气候的代表城市,魔力之都时常有雨水相伴,因此道路积水总是不可避免的。由于整个城市的排水系统连通,因此有积水的边一定是海拔相对最低的一些边。我们用水位线来描述降雨的程度,它的意义是:所有海拔不超过水位线的边都是有积水的。
Yazid 是一名来自魔力之都的 OIer,刚参加完 ION2018 的他将踏上归程,回到他温暖的家。Yazid 的家恰好在魔力之都的 1 1 1 号节点。对于接下来 Q Q Q 天,每一天 Yazid 都会告诉你他的出发点 v v v ,以及当天的水位线 p p p。
每一天,Yazid 在出发点都拥有一辆车。这辆车由于一些故障不能经过有积水的边。Yazid 可以在任意节点下车,这样接下来他就可以步行经过有积水的边。但车会被留在他下车的节点并不会再被使用。
需要特殊说明的是,第二天车会被重置,这意味着:
- 车会在新的出发点被准备好。
- Yazid 不能利用之前在某处停放的车。
Yazid 非常讨厌在雨天步行,因此他希望在完成回家这一目标的同时,最小化他步行经过的边的总长度。请你帮助 Yazid 进行计算。
本题的部分测试点将强制在线,具体细节请见【输入格式】和【子任务】。
输入格式
单个测试点中包含多组数据。输入的第一行为一个非负整数 T T T,表示数据的组数。
接下来依次描述每组数据,对于每组数据:
第一行 2 2 2 个非负整数 n , m n,m n,m,分别表示节点数、边数。
接下来 m m m 行,每行 4 4 4 个正整数 u , v , l , a u, v, l, a u,v,l,a,描述一条连接节点 u , v u, v u,v 的、长度为 l l l、海拔为 a a a 的边。
在这里,我们保证 1 ≤ u , v ≤ n 1 \leq u,v \leq n 1≤u,v≤n。接下来一行 3 3 3 个非负数 Q , K , S Q, K, S Q,K,S ,其中 Q Q Q 表示总天数, K ∈ 0 , 1 K \in {0,1} K∈0,1 是一个会在下面被用到的系数, S S S 表示的是可能的最高水位线。
接下来 Q Q Q 行依次描述每天的状况。每行 2 2 2 个整数 v 0 , p 0 v_0, p_0 v0,p0 描述一天:
- 这一天的出发节点为 v = ( v 0 + K × l a s t a n s − 1 ) m o d n + 1 v = (v_0 + K \times \mathrm{lastans} - 1) \bmod n + 1 v=(v0+K×lastans−1)modn+1。
- 这一天的水位线为 p = ( p 0 + K × l a s t a n s ) m o d ( S + 1 ) p = (p_0 + K \times \mathrm{lastans}) \bmod (S + 1) p=(p0+K×lastans)mod(S+1)。
其中 l a s t a n s \mathrm{lastans} lastans 表示上一天的答案(最小步行总路程)。特别地,我们规定第 1 1 1 天时 l a s t a n s = 0 \mathrm{lastans} = 0 lastans=0。
在这里,我们保证 1 ≤ v 0 ≤ n 1 \leq v_0 \leq n 1≤v0≤n, 0 ≤ p 0 ≤ S 0 \leq p_0 \leq S 0≤p0≤S。对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。
输出格式
依次输出各组数据的答案。对于每组数据:
- 输出 Q Q Q 行每行一个整数,依次表示每天的最小步行总路程。
样例 #1
样例输入 #1
1 4 3 1 2 50 1 2 3 100 2 3 4 50 1 5 0 2 3 0 2 1 4 1 3 1 3 2
样例输出 #1
0 50 200 50 150
样例 #2
样例输入 #2
1 5 5 1 2 1 2 2 3 1 2 4 3 1 2 5 3 1 2 1 5 2 1 4 1 3 5 1 5 2 2 0 4 0
样例输出 #2
0 2 3 1
提示
更多样例
更多样例请在附加文件中下载。
样例 3
见附加文件中的
return3.in
与return3.ans
。该样例满足海拔为一种,且不强制在线。
样例 4
见附加文件中的
return4.in
与return4.ans
。该样例满足图形态为一条链,且强制在线。
样例 5
见附加文件中的
return5.in
与return5.ans
。该样例满足不强制在线。
样例 1 解释
第一天没有降水,Yazid 可以坐车直接回到家中。
第二天、第三天、第四天的积水情况相同,均为连接 1,2 号节点的边、连接 3,4 号点的边有积水。
对于第二天,Yazid 从 2 号点出发坐车只能去往 3 号节点,对回家没有帮助。因此 Yazid 只能纯靠徒步回家。
对于第三天,从 4 号节点出发的唯一一条边是有积水的,车也就变得无用了。Yazid 只能纯靠徒步回家。
对于第四天,Yazid 可以坐车先到达 2 号节点,再步行回家。
第五天所有的边都积水了,因此 Yazid 只能纯靠徒步回家。
样例 2 解释
本组数据强制在线。
第一天的答案是 0 0 0,因此第二天的 v = ( 5 + 0 − 1 ) m o d 5 + 1 = 5 v=\left( 5+0-1\right)\bmod 5+1=5 v=(5+0−1)mod5+1=5, p = ( 2 + 0 ) m o d ( 3 + 1 ) = 2 p=\left(2+0\right)\bmod\left(3+1\right)=2 p=(2+0)mod(3+1)=2。
第二天的答案是 2 2 2,因此第三天的 v = ( 2 + 2 − 1 ) m o d 5 + 1 = 4 v=\left( 2+2-1\right)\bmod 5+1=4 v=(2+2−1)mod5+1=4, p = ( 0 + 2 ) m o d ( 3 + 1 ) = 2 p=\left(0+2\right)\bmod\left(3+1\right)=2 p=(0+2)mod(3+1)=2。
第三天的答案是 3 3 3,因此第四天的 v = ( 4 + 3 − 1 ) m o d 5 + 1 = 2 v=\left( 4+3-1\right)\bmod 5+1=2 v=(4+3−1)mod5+1=2, p = ( 0 + 3 ) m o d ( 3 + 1 ) = 3 p=\left(0+3\right)\bmod\left(3+1\right)=3 p=(0+3)mod(3+1)=3。
数据范围与约定
所有测试点均保证 T ≤ 3 T\leq 3 T≤3,所有测试点中的所有数据均满足如下限制:
- n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n≤2×105, m ≤ 4 × 1 0 5 m\leq 4\times 10^5 m≤4×105, Q ≤ 4 × 1 0 5 Q\leq 4\times 10^5 Q≤4×105, K ∈ { 0 , 1 } K\in\left\{0,1\right\} K∈{0,1}, 1 ≤ S ≤ 1 0 9 1\leq S\leq 10^9 1≤S≤109。
- 对于所有边: l ≤ 1 0 4 l\leq 10^4 l≤104, a ≤ 1 0 9 a\leq 10^9 a≤109。
- 任意两点之间都直接或间接通过边相连。
为了方便你快速理解,我们在表格中使用了一些简单易懂的表述。在此,我们对这些内容作形式化的说明:
- 图形态:对于表格中该项为 “一棵树” 或 “一条链” 的测试点,保证 m = n − 1 m = n-1 m=n−1。除此之外,这两类测试点分别满足如下限制:
- 一棵树:保证输入的图是一棵树,即保证边不会构成回路。
- 一条链:保证所有边满足 u + 1 = v u + 1 = v u+1=v。
- 海拔:对于表格中该项为 “一种” 的测试点,保证对于所有边有 a = 1 a = 1 a=1。
- 强制在线:对于表格中该项为 “是” 的测试点,保证 K = 1 K = 1 K=1;如果该项为 “否”,则有 K = 0 K = 0 K=0。
- 对于所有测试点,如果上述对应项为 “不保证”,则对该项内容不作任何保证。
n n n m m m Q = Q= Q= 测试点 形态 海拔 强制在线 ≤ 1 \leq 1 ≤1 ≤ 0 \leq 0 ≤0 0 0 0 1 不保证 一种 否 ≤ 6 \leq 6 ≤6 ≤ 10 \leq 10 ≤10 10 10 10 2 不保证 一种 否 ≤ 50 \leq 50 ≤50 ≤ 150 \leq 150 ≤150 100 100 100 3 不保证 一种 否 ≤ 100 \leq 100 ≤100 ≤ 300 \leq 300 ≤300 200 200 200 4 不保证 一种 否 ≤ 1500 \leq 1500 ≤1500 ≤ 4000 \leq 4000 ≤4000 2000 2000 2000 5 不保证 一种 否 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 6 不保证 一种 否 ≤ 1500 \leq 1500 ≤1500 = n − 1 =n-1 =n−1 2000 2000 2000 7 一条链 不保证 否 ≤ 1500 \leq 1500 ≤1500 = n − 1 =n-1 =n−1 2000 2000 2000 8 一条链 不保证 否 ≤ 1500 \leq 1500 ≤1500 = n − 1 =n-1 =n−1 2000 2000 2000 9 一条链 不保证 否 ≤ 200000 \leq 200000 ≤200000 = n − 1 =n-1 =n−1 100000 100000 100000 10 一棵树 不保证 否 ≤ 200000 \leq 200000 ≤200000 = n − 1 =n-1 =n−1 100000 100000 100000 11 一棵树 不保证 是 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 12 不保证 不保证 否 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 13 不保证 不保证 否 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 14 不保证 不保证 否 ≤ 1500 \leq 1500 ≤1500 ≤ 4000 \leq 4000 ≤4000 2000 2000 2000 15 不保证 不保证 是 ≤ 1500 \leq 1500 ≤1500 ≤ 4000 \leq 4000 ≤4000 2000 2000 2000 16 不保证 不保证 是 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 17 不保证 不保证 是 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 100000 100000 100000 18 不保证 不保证 是 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 400000 400000 400000 19 不保证 不保证 是 ≤ 200000 \leq 200000 ≤200000 ≤ 400000 \leq 400000 ≤400000 400000 400000 400000 20 不保证 不保证 是
kruskal 重构树!
这个东西太 inba 了,在做kruskal的时候建新点,点权为最小/大生成树上的边权
搬自 luogu日报(OI-Wiki)
强大性质:原图中两个点间所有路径上的边最大权值的最小值,最小生成树上两点简单路径的边最大权值 ,Kruskal 重构树上两点 LCA 的点权。
我们模拟这个过程,开车开到离家最近的最远距离,然后找该点离根节点的最近距离
第 2 2 2 步可以先处理,直接对 根节点 跑一个 dij
关键是第 1 1 1 步,跑最大生成树(跑的越远越好),然后建最大生成树的 kruskal 重构树,
然后找最近的能跑的距离,考虑倍增维护 O ( log n ) O(\log n) O(logn)
最后找能到的离根的最近距离
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 400005,M = 400005;
struct edge{
int head[N<<1],nxt[M<<1],to[M<<1],val[M<<1],cnt;
edge() {memset(head,-1,sizeof(head));}
void add(int u,int v,int w) {
nxt[cnt] = head[u];
to[cnt] = v;
val[cnt] = w;
head[u] = cnt++;
}
}g,G;
struct ED{
int x,y,a;
}E[M];
struct node{
int x,d;
node(int x,int d) : x(x),d(d){}
friend bool operator < (node a,node b) {
return a.d > b.d;
}
};
bool cmp(ED a,ED b) {
return a.a > b.a;
}
int n,m,dis[N<<1],mn[N<<1],fa[N<<1][21],s[N<<1],v[N<<1];
bool vis[N<<1];
void dij() {
memset(vis,0,sizeof(vis));
memset(dis,0x3f,sizeof(dis));
priority_queue<node> q;
q.emplace(node(1,0));
dis[1] = 0;
while(q.size()) {
node t = q.top();
q.pop();
if(vis[t.x]) continue;
vis[t.x] = true;
for(int i = g.head[t.x];~i;i = g.nxt[i]) {
int y = g.to[i];
if(dis[y] > dis[t.x] + g.val[i]) {
dis[y] = dis[t.x] + g.val[i];
q.emplace(node(y,dis[y]));
}
}
}
}
int find(int x) {
if(s[x] ^ x) s[x] = find(s[x]);
return s[x];
}
void dfs(int x,int f) {
fa[x][0] = f;mn[x] = dis[x];
for(int i = 1;i<=20;i++) fa[x][i] = fa[fa[x][i-1]][i-1];
for(int i = G.head[x];~i;i = G.nxt[i]) {
int y = G.to[i];
dfs(y,x);
mn[x] = min(mn[x],mn[y]);
}
}
void kruskalTree(){
sort(E + 1,E + m + 1,cmp);
int now = n;
for(int i = 1;i<=n * 3;i++) s[i] = i;
for(int i = 1;i<=m;i++) {
int fx = find(E[i].x),fy = find(E[i].y);
if(fx ^ fy) {
s[fx] = s[fy] = ++now;
v[now] = E[i].a;
s[now] = now;
G.add(now,fx,1);G.add(now,fy,1);
}
}
dfs(now,0);
}
void solve() {
memset(g.head,-1,sizeof(g.head));
g.cnt = 0;
memset(G.head,-1,sizeof(G.head));
G.cnt = 0;
n = rd(),m = rd();
for(int i = 1;i<=m;i++) {
int x = rd(),y = rd(),l = rd(),a = rd();
E[i].x = x;E[i].y = y;E[i].a = a;
g.add(x,y,l);g.add(y,x,l);
}
dij();
kruskalTree();
int Q = rd(),K = rd(),S = rd();
int last = 0;
while(Q--) {
int V = rd(),p = rd();
V = (V + K * last - 1) % n + 1;
p = (p + K * last) % (S + 1);
for(int i = 20;i>=0;i--)
if(fa[V][i] && v[fa[V][i]] > p)
V = fa[V][i];
wt(last = mn[V]);
putchar('\n');
}
}
signed main() {
int T = rd();
while(T--) solve();
return 0;
}
2024/08/02
CF19D.Points
只能说“学了这么久的C++,才知道”,用普通的lower_bound
去二分set
中的元素会比用set
配套的set.lower_bound(x)
要慢不少
感谢这位iHatetheWorld提供的评论
本题题面:
Points
题面翻译
在一个笛卡尔坐标系中,定义三种操作:
add x y
:在坐标系上标记一个点 ( x , y ) (x,y) (x,y),保证 ( x , y ) (x,y) (x,y) 在添加前不在坐标系上。
remove x y
:移除点 ( x , y ) (x,y) (x,y),保证 ( x , y ) (x,y) (x,y) 在移除前已在坐标系上。
find x y
:找到所有已标记并在 ( x , y ) (x,y) (x,y) 右上方的点中,最左边的点,若点不唯一,选择最下面的一个点; 如果没有符合要求的点,给出-1
,否则给出 ( x , y ) (x,y) (x,y)。现有 n n n 个操作,对于每个 find 操作,输出结果。
n ≤ 2 × 1 0 5 n \le 2 \times 10^5 n≤2×105, 0 ≤ x , y ≤ 1 0 9 0 \le x,y \le 10^9 0≤x,y≤109。
样例 #1
样例输入 #1
7 add 1 1 add 3 4 find 0 0 remove 1 1 find 0 0 add 1 1 find 0 0
样例输出 #1
1 1 3 4 1 1
样例 #2
样例输入 #2
13 add 5 5 add 5 6 add 5 7 add 6 5 add 6 6 add 6 7 add 7 5 add 7 6 add 7 7 find 6 6 remove 7 7 find 6 6 find 4 4
样例输出 #2
7 7 -1 5 5
对每个 x x x 维护一个 y y y 的集合,同时记录在该 x x x 上的 y y y 的最大值
在线段树上二分找到离点最近的,最大值严格大于
y
y
y,横坐标严格大于
x
x
x 的 横坐标,然后在这个横坐标维护的
y
y
y 集合中二分(就是这里,不要使用std中的 lower_bound
)
用 set.lower_bound(y)
去找严格大于
y
y
y 的点,返回就可
记得离散化 x x x
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
struct FastIO
{
#define get( ) getchar( )
#define put(x) putchar(x)
public:
inline FastIO &operator >>(char &t) { t = get(); return *this; }
inline FastIO &operator >>(char *t) { while((*t = get()) != '\n') *(++t) = '\0'; return *this; }
template <typename type>
inline FastIO &operator >>(type &x) { x = 0; register int sig = 1; register char ch = get();
while (ch < 48 || ch > 57) { if (ch == '-') sig = -1; ch = get(); }
while (ch > 47 && ch < 58) x = (x << 3) + (x << 1) + (ch ^ 48),
ch = get(); x *= sig; return *this; }
template <typename type>
inline FastIO &operator <<(type x) { if (!x) put('0'); if (x < 0) put('-'), x = -x; static char vec[50];
register int len = 0; while (x) vec[len++] = x % 10 + '0', x /= 10;
while (len--) put(vec[len]); return *this; }
template <typename type>
inline FastIO &operator <<(type *t) { for (; *t; t++) put(*t); return *this; }
inline FastIO &operator <<(char t) { put(t); return *this; }
}IO;
int n;
const int N = 2e5+5;
struct Q{
int opt;
int x,y;
}Q[N];
int t[N],top;
namespace sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
set<int> s[N];
int mx[N<<2];
inline void init() {memset(mx,-1,sizeof(mx));}
inline void push_up(int p) {mx[p] = max(mx[ls],mx[rs]);}
inline void update(int p,int pl,int pr,int k,int d) {
if(pl == pr) {
s[pl].emplace(d);
mx[p] = *s[pl].rbegin();
return;
}
if(k <= mid) update(ls,pl,mid,k,d);
else update(rs,mid+1,pr,k,d);
push_up(p);
}
inline void del(int p,int pl,int pr,int k,int d) {
if(pl == pr) {
s[pl].erase(d);
if(s[pl].size()) mx[p] = *s[pl].rbegin();
else mx[p] = -1;
return;
}
if(k <= mid) del(ls,pl,mid,k,d);
else del(rs,mid+1,pr,k,d);
push_up(p);
}
inline pair<int,int> query(int p,int pl,int pr,int l,int y,int r = top + 1) {
if(pl == pr) return {pl,*s[pl].lower_bound(y)};
if(l <= mid && mx[ls] >= y) {
pair<int,int> a = query(ls,pl,mid,l,y);
if(~a.first) return a;
}
if(r > mid && mx[rs] >= y){
pair<int,int> a = query(rs,mid+1,pr,l,y);
if(~a.first) return a;
}
return {-1,-1};
}
}
signed main() {
sgt::init();
IO>>n;
for(int i = 1;i<=n;i++) {
char k[20];
scanf("%s",k);
IO>>Q[i].x>>Q[i].y;
if(k[0] == 'a') Q[i].opt = 1;
else if(k[0] == 'r') Q[i].opt = 2;
else Q[i].opt = 3;
t[++top] = Q[i].x;
}
sort(t + 1,t + top + 1);
top = unique(t + 1,t + top + 1) - t - 1;
for(int i = 1;i<=n;i++) Q[i].x = lower_bound(t + 1,t + top + 1,Q[i].x) - t;
for(int i = 1;i<=n;i++) {
int opt = Q[i].opt;
if(opt == 1)
sgt::update(1,1,top + 1,Q[i].x,Q[i].y);
else if(opt == 2)
sgt::del(1,1,top + 1,Q[i].x,Q[i].y);
else {
pair<int,int> a = sgt::query(1,1,top + 1,Q[i].x + 1,Q[i].y + 1);
if(~a.first) IO<<(t[a.first])<<' '<<(a.second)<<'\n';
else IO<<(a.first)<<'\n';
}
}
return 0;
}
P3868 [TJOI2009] 猜数字
题面:
[TJOI2009] 猜数字
题目描述
现有两组数字,每组 k k k 个。
第一组中的数字分别用 a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots ,a_k a1,a2,⋯,ak 表示,第二组中的数字分别用 b 1 , b 2 , ⋯ , b k b_1,b_2,\cdots ,b_k b1,b2,⋯,bk 表示。
其中第二组中的数字是两两互素的。求最小的 n ∈ N n\in \mathbb{N} n∈N,满足对于 ∀ i ∈ [ 1 , k ] \forall i\in [1,k] ∀i∈[1,k],有 b i ∣ ( n − a i ) b_i | (n-a_i) bi∣(n−ai)。
输入格式
第一行一个整数 k k k。
第二行 k k k 个整数,表示: a 1 , a 2 , ⋯ , a k a_1,a_2,\cdots ,a_k a1,a2,⋯,ak。
第三行 k k k 个整数,表示: b 1 , b 2 , ⋯ , b k b_1,b_2,\cdots ,b_k b1,b2,⋯,bk。
输出格式
输出一行一个整数,为所求的答案 n n n。
样例 #1
样例输入 #1
3 1 2 3 2 3 5
样例输出 #1
23
提示
对于 100 % 100\% 100% 的数据:
1 ≤ k ≤ 10 1\le k \le 10 1≤k≤10, ∣ a i ∣ ≤ 1 0 9 |a_i|\le 10^9 ∣ai∣≤109, 1 ≤ b i ≤ 6 × 1 0 3 1\le b_i\le 6\times 10^3 1≤bi≤6×103, ∏ i = 1 k b i ≤ 1 0 18 \prod_{i=1}^k b_i\le 10^{18} ∏i=1kbi≤1018。
每个测试点时限 1 1 1 秒。
注意:对于
C/C++
语言,对 64 64 64 位整型数应声明为long long
。若使用
scanf
,printf
函数(以及fscanf
,fprintf
等),应采用%lld
标识符。
CRT板子
b i ∣ ( n − a i ) ⇔ n ≡ a i ( mod b i ) b_i | (n-a_i) \Leftrightarrow n \equiv a_i (\operatorname{mod}\ b_i) bi∣(n−ai)⇔n≡ai(mod bi)
直接上 CRT 就好了,不过要用 __int128
#include<bits/stdc++.h>
using namespace std;
#define int __int128
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const signed N = 11;
void exgcd(int a,int b,int &x,int &y) {
if(b == 0) {
x = 1,y = 0;
return;
}
exgcd(b,a % b,y,x);
y -= (a / b) * x;
}
int CRT(int k,int *a,int *r) {
int n = 1,ans = 0;
for(int i = 1;i<=k;i++)
n = n * r[i];
for(int i = 1;i<=k;i++){
int m = n / r[i],b,y;
exgcd(m,r[i],b,y);
ans = (ans + a[i] * m * b % n) % n;
}
return (ans % n + n) % n;
}
int k,a[N],b[N];
signed main() {
k = rd();
for(int i = 1;i<=k;i++) a[i] = rd();
for(int i = 1;i<=k;i++) b[i] = rd();
wt(CRT(k,a,b));
return 0;
}
P1600 [NOIP2016 提高组] 天天爱跑步
题面:
[NOIP2016 提高组] 天天爱跑步
题目背景
NOIP2016 提高组 D1T2
题目描述
小 C 同学认为跑步非常有趣,于是决定制作一款叫做《天天爱跑步》的游戏。《天天爱跑步》是一个养成类游戏,需要玩家每天按时上线,完成打卡任务。
这个游戏的地图可以看作一棵包含 n n n 个结点和 n − 1 n-1 n−1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 1 1 1 到 n n n 的连续正整数。
现在有 m m m 个玩家,第 i i i 个玩家的起点为 s i s_i si,终点为 t i t_i ti。每天打卡任务开始时,所有玩家在第 0 0 0 秒同时从自己的起点出发,以每秒跑一条边的速度,不间断地沿着最短路径向着自己的终点跑去,跑到终点后该玩家就算完成了打卡任务。(由于地图是一棵树,所以每个人的路径是唯一的)
小 C 想知道游戏的活跃度,所以在每个结点上都放置了一个观察员。在结点 j j j 的观察员会选择在第 w j w_j wj 秒观察玩家,一个玩家能被这个观察员观察到当且仅当该玩家在第 w j w_j wj 秒也正好到达了结点 j j j。小 C 想知道每个观察员会观察到多少人?
注意:我们认为一个玩家到达自己的终点后该玩家就会结束游戏,他不能等待一 段时间后再被观察员观察到。 即对于把结点 j j j 作为终点的玩家:若他在第 w j w_j wj 秒前到达终点,则在结点 j j j 的观察员不能观察到该玩家;若他正好在第 w j w_j wj 秒到达终点,则在结点 j j j 的观察员可以观察到这个玩家。
输入格式
第一行有两个整数 n n n 和 m m m。其中 n n n 代表树的结点数量, 同时也是观察员的数量, m m m 代表玩家的数量。
接下来 n − 1 n-1 n−1 行每行两个整数 u u u 和 v v v,表示结点 u u u 到结点 v v v 有一条边。
接下来一行 n n n 个整数,其中第 j j j 个整数为 w j w_j wj , 表示结点 j j j 出现观察员的时间。
接下来 m m m 行,每行两个整数 s i s_i si,和 t i t_i ti,表示一个玩家的起点和终点。
对于所有的数据,保证 1 ≤ s i , t i ≤ n , 0 ≤ w j ≤ n 1\leq s_i,t_i\leq n, 0\leq w_j\leq n 1≤si,ti≤n,0≤wj≤n。
输出格式
输出 1 1 1 行 n n n 个整数,第 j j j 个整数表示结点 j j j 的观察员可以观察到多少人。
样例 #1
样例输入 #1
6 3 2 3 1 2 1 4 4 5 4 6 0 2 5 1 2 3 1 5 1 3 2 6
样例输出 #1
2 0 0 1 1 1
样例 #2
样例输入 #2
5 3 1 2 2 3 2 4 1 5 0 1 0 3 0 3 1 1 4 5 5
样例输出 #2
1 2 1 0 1
提示
样例 1 说明
对于 1 1 1 号点, w i = 0 w_i=0 wi=0,故只有起点为 1 1 1 号点的玩家才会被观察到,所以玩家 1 1 1 和玩家 2 2 2 被观察到,共有 2 2 2 人被观察到。
对于 2 2 2 号点,没有玩家在第 2 2 2 秒时在此结点,共 0 0 0 人被观察到。
对于 3 3 3 号点,没有玩家在第 5 5 5 秒时在此结点,共 0 0 0 人被观察到。
对于 4 4 4 号点,玩家 1 1 1 被观察到,共 1 1 1 人被观察到。
对于 5 5 5 号点,玩家 1 1 1 被观察到,共 1 1 1 人被观察到。
对于 6 6 6 号点,玩家 3 3 3 被观察到,共 1 1 1 人被观察到。
子任务
每个测试点的数据规模及特点如下表所示。
提示:数据范围的个位上的数字可以帮助判断是哪一种数据类型。
测试点编号 n = n= n= m = m= m= 约定 1 ∼ 2 1\sim 2 1∼2 991 991 991 991 991 991 所有人的起点等于自己的终点,即 ∀ i , s i = t i \forall i,\ s_i=t_i ∀i, si=ti 3 ∼ 4 3\sim 4 3∼4 992 992 992 992 992 992 所有 w j = 0 w_j=0 wj=0 5 5 5 993 993 993 993 993 993 无 6 ∼ 8 6\sim 8 6∼8 99994 99994 99994 99994 99994 99994 ∀ i ∈ [ 1 , n − 1 ] \forall i\in[1,n-1] ∀i∈[1,n−1], i i i 与 i + 1 i+1 i+1 有边。即树退化成 1 , 2 , … , n 1,2,\dots,n 1,2,…,n 按顺序连接的链 9 ∼ 12 9\sim 12 9∼12 99995 99995 99995 99995 99995 99995 所有 s i = 1 s_i=1 si=1 13 ∼ 16 13\sim 16 13∼16 99996 99996 99996 99996 99996 99996 所有 t i = 1 t_i=1 ti=1 17 ∼ 19 17\sim 19 17∼19 99997 99997 99997 99997 99997 99997 无 20 20 20 299998 299998 299998 299998 299998 299998 无 提示
(提示:由于原提示年代久远,不一定能完全反映现在的情况,现在已经对该提示做出了一定的修改,提示的原文可以在该剪贴板查看)
在最终评测时,调用栈占用的空间大小不会有单独的限制,但在我们的工作环境中默认会有 1 MiB 1 \text{MiB} 1MiB 的限制。 这可能会引起函数调用层数较多时,程序发生栈溢出崩溃,程序中较深层数的递归往往会导致这个问题。如果你的程序需要用到较大的栈空间,请务必注意该问题。
我们可以使用一些方法修改调用栈的大小限制。
- Linux
我们可以在终端中输入下列命令:
ulimit -s 1048576
。此命令的意义是,将调用栈的大小限制修改为 1048576 KiB = 1 GiB 1048576\text{KiB}=1 \text{GiB} 1048576KiB=1GiB。例如,对于如下程序
sample.cpp
:#include <bits/stdc++.h> using namespace std; int f[1000005]; void dfs(int a){ if(a == 0){ f[a] = 0; return; } dfs(a - 1); f[a] = f[a - 1] + 1; } int main(){ dfs(1000000); return 0; }
将上述源代码用命令
g++ sample.cpp -o sample
编译为可执行文件sample
后,使用./sample
执行程序。如果在没有使用命令
ulimit -s 1048576
的情况下运行该程序,sample
会因为栈溢出而崩溃;如果使用了上述命令后运行该程序,该程序则不会崩溃。特别地,当你打开多个终端时,它们并不会共享该命令,你需要分别对它们运行该命令。
请注意,调用栈占用的空间会计入总空间占用中,和程序其他部分占用的内存共同受到内存限制。
- Windows
如果你使用 Windows 下的 Dev-C++,请选择
工具-编译选项
并在如下区域填入以下命令-Wl,--stack=1073741824
,填入后注意确认“编译时加入以下命令的”的框是已勾选状态。此处
1073741824
的单位是 B/Bytes \text{B/Bytes} B/Bytes。
![]()
线段树合并
这个博主的这个博客写的太好了,我就不写了(肯定不是我懒)
AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
inline int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
inline void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int N = 300000;
int head[N],nxt[N<<1],to[N<<1],cnt;
inline void init() {memset(head,-1,sizeof(head));}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
int fa[N],son[N],siz[N],dep[N],id[N],top[N],num;
void dfs1(int x,int f) {
fa[x] = f;
dep[x] = dep[f] + 1;
siz[x] = 1;
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ f) {
dfs1(y,x);
siz[x] += siz[y];
if(siz[son[x]] < siz[y] ) son[x] = y;
}
}
}
void dfs2(int x,int topx) {
top[x] = topx;
id[x] = ++num;
if(!son[x]) return;
dfs2(son[x],topx);
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
}
}
int lca(int x,int y) {
while(top[x] != top[y]) {
if(dep[top[x]] < dep[top[y]]) swap(x,y);
x = fa[top[x]];
}
return dep[x] < dep[y] ? x : y;
}
int n,m;
namespace sgt{
int root[N<<1],L[N*55],R[N*55],v[N*55],num;
#define ls (L[p])
#define rs (R[p])
#define mid ((pl + pr) >> 1)
void update(int &p,int pl,int pr,int d,int V) {
if(!p) p = ++num;
if(pl == pr) {
v[p] += V;
return;
}
if(d <= mid) update(ls,pl,mid,d,V);
else update(rs,mid+1,pr,d,V);
}
int query(int p,int pl,int pr,int d) {
if(!p) return 0;
if(pl == pr) return v[p];
if(d <= mid) return query(ls,pl,mid,d);
else return query(rs,mid+1,pr,d);
}
int merge(int x,int y,int pl,int pr) {
if(!x || !y) return x + y;
if(pl == pr) v[x] += v[y];
else {
L[x] = merge(L[x],L[y],pl,mid);
R[x] = merge(R[x],R[y],mid+1,pr);
}
return x;
}
}
int w[N],ans[N];
inline void dfs(int x) {
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ fa[x]) {
dfs(y);
sgt::root[x] = sgt::merge(sgt::root[x],sgt::root[y],1,n<<1);
}
}
if(w[x] && n + dep[x] + w[x] <= n * 2)
ans[x] += sgt::query(sgt::root[x],1,n<<1,n + dep[x] + w[x]);
ans[x] += sgt::query(sgt::root[x],1,n<<1,n + dep[x] - w[x]);
}
signed main() {
init();
n = rd(),m = rd();
for(int i = 1;i<n;i++) {
int u = rd(),v = rd();
add(u,v);add(v,u);
}
dfs1(1,0);
dfs2(1,1);
for(int i = 1;i<=n;i++) w[i] = rd();
for(int i = 1;i<=m;i++) {
int x = rd(),y = rd();
int LCA = lca(x,y);
sgt::update(sgt::root[x],1,n<<1,n + dep[x],1);
sgt::update(sgt::root[y],1,n<<1,n + dep[LCA] * 2 - dep[x],1);
sgt::update(sgt::root[LCA],1,n<<1,n + dep[x],-1);
sgt::update(sgt::root[fa[LCA]],1,n<<1,n + dep[LCA] * 2 - dep[x],-1);
}
dfs(1);
for(int i = 1;i<=n;i++) wt(ans[i]),putchar(' ');
return 0;
}
2024/08/03
今天确实适合重构代码,重构完就知道哪里错了
2024-8-3_mx模拟赛
C. 树差
题目描述
腰有一棵树,这棵树有 个点,根为 号点,每个点上都可以有一定数量的痛。初始时树上的每个点都没有痛,痛的数量可以是负数。
腰要对这棵树进行一些操作,具体来说有下面三种操作:
- : 是 子树内一点, 和 的深度差是 ,则腰会在 点增加 个痛(注意这里增加的可以是负数,也就是事实上痛的数量在减少)。我们认为该操作 是在 处操作的。
- :腰想知道点 上有多少个痛。
- :腰反悔了,将所有还未撤销的在 及其子树中节点处操作的 所有操作 撤销。
然而腰不喜欢负数,所以你只需要回答痛的数量对 取模的结果。
输入格式
输入共 行。
第一行两个正整数,分别表示 , 。
第二行 个正整数,第 个正整数表示 号点的父亲编号。
第三行到第 行,每行一个操作,格式如题目描述所述。
输出格式
对于每个操作 ,输出一行一个整数表示这次询问的答案对 取模的结果。
样例
输入样例
5 11
1
1
3
3
1 1 0 2
2 1
2 2
3 3
2 4
3 1
1 3 1 3
2 3
2 4
3 1
2 1
输出样例
0
1000000005
4
1
1000000003
0
样例解释
答案取模前分别是 。
更多大样例在下发文件。
数据范围与提示
对于全部数据, , , , 。
具体的限制如下(留空表示无特殊限制):
测试点编号 | 特殊性质 | ||
---|---|---|---|
第 个点的父亲是第 个点 | |||
除根外所有点的父亲都是 号点 | |||
无操作 | |||
老师说的好,遇到奇怪的式子不要慌,说明这题非常好做,让出题人只能用奇怪式子迷惑你
遇到式子,第一步肯定是拆式子
( − 1 ) d ( a + b × d ) ,其中 d = ( x 和 y 之间的深度差 ) (-1)^d(a + b \times d),其中\ d = (x\ 和\ y\ 之间的深度差) (−1)d(a+b×d),其中 d=(x 和 y 之间的深度差)
所以
d
=
dep
y
−
dep
x
d = \operatorname{dep}_y - \operatorname{dep}_x
d=depy−depx
那么,我们把 d d d 带回去,再观察一下式子
( − 1 ) d ( a + b × d ) = ( − 1 ) dep y − dep x [ a + b × ( dep y − dep x ) ] = ( − 1 ) dep y ⋅ ( − 1 ) − dep x ( a + b ⋅ dep y − b ⋅ dep x ) = ( − 1 ) dep y ⋅ ( − 1 ) dep x ( a + b ⋅ dep y − b ⋅ dep x ) = ( − 1 ) dep y [ ( − 1 ) dep x a + ( − 1 ) dep x b dep y − ( − 1 ) dep x b dep x ] \begin{align*} (-1)^d(a + b \times d)& =(-1)^{\operatorname{dep}_y - \operatorname{dep}_x}[a + b \times(\operatorname{dep}_y - \operatorname{dep}_x)] \\ & = (-1)^{\operatorname{dep}_y}\cdot(-1)^{-\operatorname{dep}_x}(a + b\cdot \operatorname{dep}_y - b\cdot \operatorname{dep}_x) \\ & = (-1)^{\operatorname{dep}_y}\cdot(-1)^{\operatorname{dep}_x}(a + b\cdot \operatorname{dep}_y - b\cdot \operatorname{dep}_x) \\ & = (-1)^{\operatorname{dep}_y}[(-1)^{\operatorname{dep}_x}a + (-1)^{\operatorname{dep}_x}b\operatorname{dep}_y - (-1)^{\operatorname{dep}_x}b\operatorname{dep}_x] \end{align*} (−1)d(a+b×d)=(−1)depy−depx[a+b×(depy−depx)]=(−1)depy⋅(−1)−depx(a+b⋅depy−b⋅depx)=(−1)depy⋅(−1)depx(a+b⋅depy−b⋅depx)=(−1)depy[(−1)depxa+(−1)depxbdepy−(−1)depxbdepx]
因为我们是单点查询,我们在查 y y y 时,可以在求解时人为加上,不需要进入数据结构
那么我们就对每个点分类加上
x
x
x 带来的贡献
也就是维护
(
−
1
)
dep
x
a
(-1)^{\operatorname{dep}_x}a
(−1)depxa 、
(
−
1
)
dep
x
b
(-1)^{\operatorname{dep}_x}b
(−1)depxb、
−
(
−
1
)
dep
x
b
dep
x
- (-1)^{\operatorname{dep}_x}b\operatorname{dep}_x
−(−1)depxbdepx 这三个值
考虑开 3 3 3 个线段树维护
最有迷惑性的就是 opt 3 \operatorname{opt}_3 opt3,撤销?
撤销不过就是反向 opt 1 \operatorname{opt}_1 opt1
操作一共就 1 0 5 10^5 105 次,最坏就 25000 25000 25000 次撤销,干脆直接暴力 O ( m ) O(m) O(m) 撤销
如何找到需要撤销的点呢?
考虑 set \operatorname{set} set 维护每个点的新编号(即 dfn \operatorname{dfn} dfn 序)
然后二分找到在子树内 dfn \operatorname{dfn} dfn 最小的需要修改的点,暴力修改,删除,直到 dfn i \operatorname{dfn}_i dfni 的值大于等于 d f n x + siz x − 1 \operatorname{dfn_x} + \operatorname{siz}_x - 1 dfnx+sizx−1 (不在子树内了)
此题就是这样
O
(
m
log
n
+
n
log
n
)
O(m\log n + n \log n)
O(mlogn+nlogn) 解决了:
上 AC-code:
#include<bits/stdc++.h>
using namespace std;
#define int long long
int rd() {
int x = 0, w = 1;
char ch = 0;
while (ch < '0' || ch > '9') {
if (ch == '-') w = -1;
ch = getchar();
}
while (ch >= '0' && ch <= '9') {
x = x * 10 + (ch - '0');
ch = getchar();
}
return x * w;
}
void wt(int x) {
static int sta[35];
int f = 1;
if(x < 0) f = -1,x *= f;
int top = 0;
do {
sta[top++] = x % 10, x /= 10;
} while (x);
if(f == -1) putchar('-');
while (top) putchar(sta[--top] + 48);
}
const int mod = 1e9 + 7,N = 2e5+5;
int head[N],nxt[N<<1],to[N<<1],cnt;
void init() {memset(head,-1,sizeof(head));}
void add(int u,int v) {
nxt[cnt] = head[u];
to[cnt] = v;
head[u] = cnt++;
}
int n,m;
set<int> s;
int fa[N],son[N],siz[N],top[N],id[N],dep[N],num,_id[N];
void dfs1(int x) {
siz[x] = 1;
dep[x] = dep[fa[x]] + 1;
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ fa[x]) {
dfs1(y);
siz[x] += siz[y];
if(siz[son[x]] < siz[y]) son[x] = y;
}
}
}
void dfs2(int x,int topx) {
top[x] = topx;
id[x] = ++num;
_id[num] = x;
if(!son[x]) return;
dfs2(son[x],topx);
for(int i = head[x];~i;i = nxt[i]) {
int y = to[i];
if(y ^ fa[x] && y ^ son[x]) dfs2(y,y);
}
}
int f(int x) {return (x & 1) ? -1 : 1;}
struct sgt{
#define ls (p << 1)
#define rs (ls | 1)
#define mid ((pl + pr) >> 1)
int t[N<<2],tag[N<<2];
void push_up(int p) {t[p] = (t[ls] + t[rs] + mod) % mod;}
void addtag(int p,int pl,int pr,int d) {
tag[p] = (tag[p] + d);
t[p] = (t[p] + (pr - pl + 1) * d);
tag[p] %= mod;
t[p] %= mod;
}
void push_down(int p,int pl,int pr) {
if(tag[p]) {
addtag(ls,pl,mid,tag[p]);
addtag(rs,mid+1,pr,tag[p]);
tag[p] = 0;
}
}
void update(int p,int pl,int pr,int l,int r,int d) {
if(l <= pl && pr <= r) {addtag(p,pl,pr,d);return;}
push_down(p,pl,pr);
if(l <= mid) update(ls,pl,mid,l,r,d);
if(r > mid) update(rs,mid+1,pr,l,r,d);
push_up(p);
}
int query(int p,int pl,int pr,int k) {
if(pl == pr) return t[p];
push_down(p,pl,pr);
if(k <= mid) return query(ls,pl,mid,k);
else return query(rs,mid+1,pr,k);
}
}A,B,C;
int ra[N],rb[N];
void add() {
int x = rd(),a = rd(),b = rd();
ra[id[x]] = ra[id[x]] + a,rb[id[x]] = rb[id[x]] + b;
A.update(1,1,n,id[x],id[x] + siz[x] - 1,f(dep[x]) * a);
B.update(1,1,n,id[x],id[x] + siz[x] - 1,f(dep[x]) * b);
C.update(1,1,n,id[x],id[x] + siz[x] - 1,-1 * f(dep[x]) * b * dep[x]);
s.emplace(id[x]);
}
void query() {
int x = rd();
int ans = 0;
ans = (ans + A.query(1,1,n,id[x]) + mod) % mod;
ans = (ans + (B.query(1,1,n,id[x]) * dep[x] + mod) % mod + mod) % mod;
ans = (ans + C.query(1,1,n,id[x]) + mod) % mod;
ans = (ans * f(dep[x]) + mod) % mod;
wt(ans),putchar('\n');
}
void remove() {
int x = rd();
auto it = s.lower_bound(id[x]);
while(it != s.end() && (*it < id[x] + siz[x])){
int a = -ra[*it],b = -rb[*it];
ra[*it] = rb[*it] = 0;
A.update(1,1,n,*it,*it + siz[_id[*it]] - 1,f(dep[_id[*it]]) * a);
B.update(1,1,n,*it,*it + siz[_id[*it]] - 1,f(dep[_id[*it]]) * b);
C.update(1,1,n,*it,*it + siz[_id[*it]] - 1,-1 * f(dep[_id[*it]]) * b * dep[_id[*it]]);
it = s.erase(it);
if(it == s.end()) break;
}
}
signed main() {
init();
n = rd(),m = rd();
for(int i = 2;i<=n;i++) {
fa[i] = rd();
add(i,fa[i]);
add(fa[i],i);
}
dfs1(1);dfs2(1,1);
while(m--) {
int opt = rd();
switch(opt) {
case 1:
add();
break;
case 2:
query();
break;
case 3:
remove();
break;
}
}
return 0;
}