2024.8 - 做题记录与方法总结 - Part 1

2024.8 - Record of Questions and Summary of Methodology

先分享一个歌单:

img

永无止境的八月

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 1u,vn

接下来一行 3 3 3 个非负数 Q , K , S Q, K, S Q,K,S ,其中 Q Q Q 表示总天数, K ∈ 0 , 1 K \in {0,1} K0,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×lastans1)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 1v0n 0 ≤ p 0 ≤ S 0 \leq p_0 \leq S 0p0S

对于输入中的每一行,如果该行包含多个数,则用单个空格将它们隔开。

输出格式

依次输出各组数据的答案。对于每组数据:

  • 输出 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.inreturn3.ans

该样例满足海拔为一种,且不强制在线。

样例 4

见附加文件中的 return4.inreturn4.ans

该样例满足图形态为一条链,且强制在线。

样例 5

见附加文件中的 return5.inreturn5.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+01)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+21)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+31)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 T3,所有测试点中的所有数据均满足如下限制:

  • n ≤ 2 × 1 0 5 n\leq 2\times 10^5 n2×105 m ≤ 4 × 1 0 5 m\leq 4\times 10^5 m4×105 Q ≤ 4 × 1 0 5 Q\leq 4\times 10^5 Q4×105 K ∈ { 0 , 1 } K\in\left\{0,1\right\} K{0,1} 1 ≤ S ≤ 1 0 9 1\leq S\leq 10^9 1S109
  • 对于所有边: l ≤ 1 0 4 l\leq 10^4 l104 a ≤ 1 0 9 a\leq 10^9 a109
  • 任意两点之间都直接或间接通过边相连。

为了方便你快速理解,我们在表格中使用了一些简单易懂的表述。在此,我们对这些内容作形式化的说明:

  • 图形态:对于表格中该项为 “一棵树” 或 “一条链” 的测试点,保证 m = n − 1 m = n-1 m=n1。除此之外,这两类测试点分别满足如下限制:
  • 一棵树:保证输入的图是一棵树,即保证边不会构成回路。
  • 一条链:保证所有边满足 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 01不保证一种
≤ 6 \leq 6 6 ≤ 10 \leq 10 10 10 10 102不保证一种
≤ 50 \leq 50 50 ≤ 150 \leq 150 150 100 100 1003不保证一种
≤ 100 \leq 100 100 ≤ 300 \leq 300 300 200 200 2004不保证一种
≤ 1500 \leq 1500 1500 ≤ 4000 \leq 4000 4000 2000 2000 20005不保证一种
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 1000006不保证一种
≤ 1500 \leq 1500 1500 = n − 1 =n-1 =n1 2000 2000 20007一条链不保证
≤ 1500 \leq 1500 1500 = n − 1 =n-1 =n1 2000 2000 20008一条链不保证
≤ 1500 \leq 1500 1500 = n − 1 =n-1 =n1 2000 2000 20009一条链不保证
≤ 200000 \leq 200000 200000 = n − 1 =n-1 =n1 100000 100000 10000010一棵树不保证
≤ 200000 \leq 200000 200000 = n − 1 =n-1 =n1 100000 100000 10000011一棵树不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 10000012不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 10000013不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 10000014不保证不保证
≤ 1500 \leq 1500 1500 ≤ 4000 \leq 4000 4000 2000 2000 200015不保证不保证
≤ 1500 \leq 1500 1500 ≤ 4000 \leq 4000 4000 2000 2000 200016不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 10000017不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 100000 100000 10000018不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 400000 400000 40000019不保证不保证
≤ 200000 \leq 200000 200000 ≤ 400000 \leq 400000 400000 400000 400000 40000020不保证不保证

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
题面翻译

在一个笛卡尔坐标系中,定义三种操作:

  1. add x y :在坐标系上标记一个点 ( x , y ) (x,y) (x,y),保证 ( x , y ) (x,y) (x,y) 在添加前不在坐标系上。

  2. remove x y :移除点 ( x , y ) (x,y) (x,y),保证 ( x , y ) (x,y) (x,y) 在移除前已在坐标系上。

  3. 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 n2×105 0 ≤ x , y ≤ 1 0 9 0 \le x,y \le 10^9 0x,y109

样例 #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} nN,满足对于 ∀ i ∈ [ 1 , k ] \forall i\in [1,k] i[1,k],有 b i ∣ ( n − a i ) b_i | (n-a_i) bi(nai)

输入格式

第一行一个整数 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 1k10 ∣ a i ∣ ≤ 1 0 9 |a_i|\le 10^9 ai109 1 ≤ b i ≤ 6 × 1 0 3 1\le b_i\le 6\times 10^3 1bi6×103 ∏ i = 1 k b i ≤ 1 0 18 \prod_{i=1}^k b_i\le 10^{18} i=1kbi1018

每个测试点时限 1 1 1 秒。

注意:对于 C/C++语言,对 64 64 64 位整型数应声明为 long long

若使用 scanfprintf函数(以及 fscanffprintf等),应采用 %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(nai)nai(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 n1 条边的树,每条边连接两个结点,且任意两个结点存在一条路径互相可达。树上结点编号为从 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 n1 行每行两个整数 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 1si,tin,0wjn

输出格式

输出 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 12 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 34 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 68 99994 99994 99994 99994 99994 99994 ∀ i ∈ [ 1 , n − 1 ] \forall i\in[1,n-1] i[1,n1] i i i i + 1 i+1 i+1 有边。即树退化成 1 , 2 , … , n 1,2,\dots,n 1,2,,n 按顺序连接的链
9 ∼ 12 9\sim 12 912 99995 99995 99995 99995 99995 99995所有 s i = 1 s_i=1 si=1
13 ∼ 16 13\sim 16 1316 99996 99996 99996 99996 99996 99996所有 t i = 1 t_i=1 ti=1
17 ∼ 19 17\sim 19 1719 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

img

今天确实适合重构代码,重构完就知道哪里错了

2024-8-3_mx模拟赛

C. 树差
题目描述

腰有一棵树,这棵树有 n 个点,根为 1 号点,每个点上都可以有一定数量的痛。初始时树上的每个点都没有痛,痛的数量可以是负数。

腰要对这棵树进行一些操作,具体来说有下面三种操作:

  • 1\ x\ a\ b y x 子树内一点, x y 的深度差是 d ,则腰会在 y 点增加 (-1)^d(a+b\times d) 个痛(注意这里增加的可以是负数,也就是事实上痛的数量在减少)。我们认为该操作 1 x 处操作的
  • 2\ x :腰想知道点 x 上有多少个痛。
  • 3\ x :腰反悔了,将所有还未撤销的 x 及其子树中节点处操作的 所有操作 1 撤销。

然而腰不喜欢负数,所以你只需要回答痛的数量对 10^9+7 取模的结果。

输入格式

输入共 m+2 行。

第一行两个正整数,分别表示 n m

第二行 n-1 个正整数,第 i 个正整数表示 i+1 号点的父亲编号。

第三行到第 m+2 行,每行一个操作,格式如题目描述所述。

输出格式

对于每个操作 2 ,输出一行一个整数表示这次询问的答案对 10^9+7 取模的结果。

样例
输入样例
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
样例解释

答案取模前分别是 0,-2,4,1,-4,0

更多大样例在下发文件。

数据范围与提示

对于全部数据, 1\leq n\leq 2\times 10^5 1\leq m\leq 10^5 1\leq x\leq n 0\leq |a|,|b|\leq 10^3

具体的限制如下(留空表示无特殊限制):

测试点编号 n\leq m\leq 特殊性质
1 10
2 10^3
3 5\times 10^3
4 i 个点的父亲是第 i-1 个点
5 除根外所有点的父亲都是 1 号点
6 无操作 3
7
8
9
10

老师说的好,遇到奇怪的式子不要慌,说明这题非常好做,让出题人只能用奇怪式子迷惑你

遇到式子,第一步肯定是拆式子

( − 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=depydepx

那么,我们把 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)depydepx[a+b×(depydepx)]=(1)depy(1)depx(a+bdepybdepx)=(1)depy(1)depx(a+bdepybdepx)=(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+sizx1 (不在子树内了)

此题就是这样 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;
}
  • 17
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值