0%

题意

给出一棵基环树, 求树上简单路径条数

\(3 \leq n \leq 2 \cdot 10^5\)

题解

将基环树看作一个环, 环上连接着一些树, 设第\(i\)棵树有\(cnt_i\)个节点.

路径分为两种情况:

  1. 在树内. 有\(\frac{cnt_i \cdot (cnt_i - 1)}{2}\)
  2. 由树内到树外, 有\(cnt_i \cdot (n - cnt_i)\)条(这里只计算了一半, 但当计算完全部子树后答案是完整的)

通过一遍\(DFS\)求出环, 再通过一次\(DFS\)求出每个子树大小, 答案为 \[ \sum_{i \in subtree}\frac{cnt_i\cdot(cnt_i-1)}{2} + cnt_{i}\cdot(n - cnt_i) \] 复杂度\(O(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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//E
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define endl '\n'
const int maxn = 2e5+10;
const int maxm = maxn*2;
int head[maxn],pa[maxn];
bool loop[maxn],vis[maxn];
struct Edge{
int f,t,next;
Edge(int f = 0,int t = 0,int next = 0):f(f),t(t),next(next){}
}edge[maxm];
int tcnt,cnt;
void addedge(int f,int t){
edge[cnt] = Edge(f,t,head[f]);
head[f] = cnt++;
}
bool ok = 0;
void pre(int u,int fa){
if(ok)return;
vis[u] = 1;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t;
if(v == fa or ok)continue;
if(vis[v]){
while(u != v){
loop[u] = 1;
u = pa[u];
ok = 1;
}
loop[v] = 1;
return;
}
pa[v] = u;
pre(v,u);
}
}
void DFS(int u,int fa){
tcnt++;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t;
if(v == fa or loop[v])continue;
DFS(v,u);
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while(t--){
int n;
cin >> n;
tcnt = 0;
ok = 0;
cnt = 0;
vector<ll>res;
for(int i = 0;i <= n;i++)head[i] = -1,loop[i] = 0,pa[i] = vis[i] = 0;
for(int i = 1;i <= n;i++){
int f,t;
cin >> f >> t;
addedge(f,t);
addedge(t,f);
}
pre(1,-1);
for(int i = 1;i <= n;i++){
if(loop[i]){
tcnt = 0;
DFS(i,-1);
res.push_back(tcnt);
}
}
ll ans = 0;
for(ll x:res)ans += x*(x-1)/2 + x*(n-x);
cout << ans << endl;
}
return 0;
}

题意

给出一棵\(n\)个点的带权无根树, 给出三个点的集合. 在每个集合任取一点, 在树上寻找新的一个点使得该点到三点的路径和最短. 求路径和期望

$ 1 n ^5,1 w $

题解

首先考虑这样一个问题: 给出树上三点\(a,b,c\), 求这三点汇聚到一点\(v\)的最短路径(设为\(f(a,b,c)\)) \[ f(a,b,c) = \min\{v\in V |dis(a,v) + dis(b,v) + dis(c,v)\} \]

image-20201119121551047

如上图所示, \(a,b,c\)\(3,4,5\). 观察易得汇合点\(v\)\(2\), 即\(a,b,c\)的LCA中深度最深的那个. 此时\(a,b\space a,c \space b,c\)间的每条边都恰好经过一次, 即: \[ f(a,b,c) = \frac{1}{2}(dis(a,b) + dis(a,c) + dis(b,c)) \] 由期望的性质\(E(X + Y) = E(X) + E(Y)\), 我们可以分别计算两个集合\(j,k\)中各点的距离和. 通过一遍DFS来计算子树\(u\)中属于集合\(i\)的节点个数\(num[u][i]\), 再对每条边分别统计贡献\(g\). 设\(cnt_i\)为集合\(i\)的大小. \[ g(e_i,j,k) = ((cnt_i - num[u][j])*num[u][k] + (cnt_k - num[u][k])*cnt_j)*e_i.w \] 答案即为 \[ \sum_{j = 1}^3 \sum_{k = j+1}^3\frac{\sum_{i = 1}^{n-1}g(e_i,j,k)}{2\cdot cnt_j \cdot cnt_k} \]

代码

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
//C
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef long double ld;
#define int long long
#define endl '\n'
const int maxn = 2e5+10;
const int maxm = 4e5+10;
int head[maxn],cnt,use[maxn][4],num[maxn][4];
int n;
ll sum[4][4];
struct Edge{
int f,t,w,next;
Edge(int f = 0,int t = 0,int w = 0,int next = 0):f(f),t(t),w(w),next(next){}
}edge[maxm];
void addedge(int f,int t,int w){
edge[cnt] = Edge(f,t,w,head[f]);
head[f] = cnt++;
}
void DFS(int u,int fa){
for(int i = 1;i <= 3;i++)if(use[u][i])num[u][i] = 1;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t,w = edge[i].w;
if(v == fa)continue;
DFS(v,u);
for(int j = 1;j <= 3;j++){
for(int k = j+1;k <= 3;k++){
sum[j][k] += (num[0][j] - num[v][j])*num[v][k]*w;
sum[j][k] += (num[0][k] - num[v][k])*num[v][j]*w;
}
}
for(int j = 1;j <= 3;j++)num[u][j] += num[v][j];

}
}
signed main(){
cout.setf(ios::fixed,ios::floatfield);
cout.precision(10);
ios::sync_with_stdio(0);
cin.tie(0);
memset(head,-1,sizeof(head));
cin >> n;
for(int i = 1;i < n;i++){
int f,t,w;
cin >> f >> t >> w;
addedge(f,t,w);
addedge(t,f,w);
}
for(int i = 1;i <= 3;i++){
int m;
cin >> m;
num[0][i] = m;
for(int j = 1;j <= m;j++){
int x;
cin >> x;
use[x][i] = 1;
}
}
DFS(1,-1);
ld ans = 0;
for(int i = 1;i <= 3;i++){
for(int j = i+1;j <= 3;j++)ans += (double)sum[i][j]/num[0][i]/num[0][j]/2;
}
cout << ans << endl;
return 0;
}

PS: 当时我这题题意转述的时候锅了, 导致队友直接一个点分治敲上去, 敲完才发现不对劲=-= 不然这题是有可能做出来的

[CF1433G]Reducing Delivery Cost

题意

给出一张无向带权图\(G\)\(k\)个点对, 你可以将最多一条边的权值变为0, 求\(k\)个点对之间最短路之和的最小值

图中存在重边和自环.

\(1 \leq n \leq 1000, 1 \leq m \leq \frac{n(n-1)}{2},1 \leq k \leq 1000\)

题解

套路题, 一开始想麻烦了.

\(D[i][j]\)\((i,j)\)间的最短路. 对于每条边\((f,t)\)有两种情况:

  1. 无论该边权值是否为零, 都不在点对\((x,y)\)的最短路上. 删掉该边后答案仍为\(D[x][y]\).
  2. 删掉该边前/后该边位于点对\((x,y)\)的最短路上. 删掉该边后答案为\(\min(D[x][f]+D[t][y],D[x][t]+D[f][y])\)

我们跑\(n\)遍Dijkstra预处理出任意点对最短路,再对于每条边枚举\(k\)个点对即可求出答案.总复杂度\(O(nm\log n + km)\)

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
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int INF = 1e9+10;
const int maxn = 1e3+10;
const int maxm = maxn*maxn;
typedef pair<int,int> Node;
int head[maxn],vis[maxn],dis[maxn];
int D[maxn][maxn];
int viscnt[maxm];
struct Edge{
int f,t,w,next;
Edge(int f = 0,int t = 0,int w = 0,int next = 0):f(f),t(t),w(w),next(next){}
}edge[maxm];
int cnt = 0,n,m;
void addedge(int f,int t,int w){
edge[cnt] = Edge(f,t,w,head[f]);
head[f] = cnt++;
}
void dijkstra(int s){
for(int i = 0;i <= n;i++){
dis[i] = INF;
vis[i] = 0;
}
dis[s] = 0;
priority_queue<Node,vector<Node>,greater<Node> >pq;
pq.push({dis[s],s});
while(!pq.empty()){
int u = pq.top().second;
pq.pop();
if(vis[u])continue;
vis[u] = 1;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t,w = edge[i].w;
if(dis[v] > dis[u] + w){
dis[v] = dis[u] + w;
if(!vis[v]){
pq.push({dis[v],v});
}
}
}
}
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
memset(head,-1,sizeof(head));
memset(D,0x3f3f3f3f,sizeof(D));
int k;
cin >> n >> m >> k;
for(int i = 1;i <= m;i++){
int f,t,w;
cin >> f >> t >> w;
addedge(f,t,w);
addedge(t,f,w);
}
for(int i = 1;i <= n;i++){
dijkstra(i);
for(int j = 1;j <= n;j++)D[i][j] = D[j][i] = dis[j];
}
vector<Node>path;
for(int i = 1;i <= k;i++){
int x,y;
cin >> x >> y;
path.push_back({x,y});
}
ll ans = LLONG_MAX;
for(int i = 0;i < cnt;i++){
int f = edge[i].f,t = edge[i].t;
ll tans = 0;
for(int j = 0;j < k;j++){
int x = path[j].first,y = path[j].second;
tans += min({D[x][y],D[x][f]+D[t][y],D[x][t]+D[f][y]});
}
ans = min(ans,tans);
}
cout << ans << endl;
}

题意

给出一个长为\(n\)的数组\(\{a_i\}\), 用\(x\)异或数组中的每一个元素, 使得在新数组逆序对最少的情况下\(x\)最小.求满足题意的逆序对数和\(x\)

\(1 \le n \le 3 \cdot 10^5\)

题解

异或让我们考虑0-1Trie.

对于每一个数, 按照从高位到低位的顺序插入Trie, 并对其经过的每个节点维护该数字的下标. 具体来说,idx[u]记录了经过节点\(u\)的数字的下标. 由于我们按下标递增的顺序插入元素, 该数组是有序的.

有了idx[],再结合一个显然的事实: 两个数的大小只取决于其最高位, 我们就可高效计算每一位所影响的逆序对(0-1Trie中,左子树代表的值小于右子树).

具体做法: 从高位开始自上而下遍历整棵0-1Trie. 设当前节点为\(u\), 当\(u\)的两个子节点均存在时, 修改该位才能对逆序对数目产生影响. 利用前面维护的下标, 我们可以在线性时间内计算该位对逆序对数目的贡献.

1
2
3
4
5
int ptr = 0;//右子树idx[]的游标
for(int x:idx[l]){//遍历左子树的下标
while(ptr < idx[r].size() and idx[r][ptr] < x)ptr++;//左子树当前下标贡献的逆序对个数
inv += ptr;
}

我们使用\(sum[][]\)来记录该结果.\(sum[i][0]\)表示不修改第\(i\)位所贡献的逆序对, \(sum[i][1]\)表示修改该位贡献的逆序对.容易看出修改该位后, 原本的逆序对变成了顺序对, 反之亦然.

1
2
sum[dep][0] += inv;
sum[dep][1] += (ll)idx[l].size()*idx[r].size() - inv;

有了sum数组,答案便很容易统计:\(inv += \min(sum[i][0],sum[i][1])\). 而当\(sum[i][1] < sum[i][0]\)时, \(x\)的第\(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
//E
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define int long long
#define endl '\n'
const int maxn = 6e6+10;
ll sum[100][2];
struct Trie{
int size = 1;
int nxt[maxn][2];
vector<int>idx[maxn*2];
void clear(){
memset(nxt,0,sizeof(nxt));
}
void insert(int x,int pos){
int u = 0;
for(int i = 29;i >= 0;i--){
bool c = ((1ll<<i)&x);
if(!nxt[u][c])nxt[u][c] = size++;
u = nxt[u][c];
idx[u].push_back(pos);
}
}
void DFS(int pos,int dep){
int l = nxt[pos][0],r = nxt[pos][1];
if(l)DFS(l,dep-1);
if(r)DFS(r,dep-1);
if(!l or !r)return;
int ptr = 0,inv = 0;
for(int x:idx[l]){
while(ptr < idx[r].size() and idx[r][ptr] < x)ptr++;
inv += ptr;
}
sum[dep][0] += inv;
sum[dep][1] += (ll)idx[l].size()*idx[r].size() - inv;
}
}trie;
signed main(){
ios::sync_with_stdio(0);
cin.tie(nullptr);
int n;
cin >> n;
trie.clear();
for(int i = 1;i <= n;i++){
int x;
cin >> x;
trie.insert(x,i);
}
trie.DFS(0,29);
ll X = 0,inv = 0;
for(int i = 29;i >= 0;i--){
inv += min(sum[i][0],sum[i][1]);
if(sum[i][1] < sum[i][0])X += (1ll << i);
}
cout << inv << " " << X;
return 0;
}

题意

给出一张图, 图中有的边已确定方向(且不可更改), 另一些边尚未确定方向. 给所有未确定方向的边指定一个方向, 使得新的图不存在环. 如果无解输出NO

分析

一张图的拓扑排序是一个DAG. 我们首先仅考虑原图的有向边, 如果其中存在环那么无解. 否则求原图的拓扑序, 按拓扑序指派边的方向即可.

代码

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 <bits/stdc++.h>
using namespace std;
const int maxm = 4e5+10;
const int maxn = 2e5+10;
struct Edge{
int f,t,next;
Edge(int f = 0,int t = 0,int next = 0):f(f),t(t),next(next){}
}edge[maxm];
int cnt,head[maxm],vis[maxm],dep[maxn],in[maxn],dict[maxn];
void addedge(int f,int t){
edge[cnt] = Edge(f,t,head[f]);
head[f] = cnt++;
}
bool ok = 1;
pair<int,int>Node[maxn];
void BFS(int n){
int qwq = 0;
queue<int>q;
for(int i = 1;i <= n;i++)if(!in[i]){
q.push(i);
vis[i] = 1;
dep[i] = 0;
}
if(q.empty())ok = 0;
while(!q.empty()){
int u = q.front();
q.pop();
dep[u] = ++qwq;
for(int i = head[u];~i;i = edge[i].next){
int v = edge[i].t;
if(--in[v]){
continue;
}
q.push(v);
}
}
if(qwq != n)ok = 0;
}
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while(t--){
ok = 1;
int n,m;
cin >> n >> m;
for(int i = 0;i <= n;i++){
head[i] = -1;
dep[i] = dict[i] = in[i] = vis[i] = 0;
}
for(int i = 1;i <= m;i++){
int opt,f,t;
cin >> opt >> f >> t;
Node[i] = {f,t};
if(opt){
addedge(f,t);
in[t]++;
dict[i] = 1;
}
}
BFS(n);
if(!ok){
cout << "NO\n";
}
else{
cout << "YES\n";
for(int i = 1;i <= m;i++){
int & f = Node[i].first,& t = Node[i].second;
if(dep[f] > dep[t])cout << t << " " << f << endl;
else cout << f << " " << t << endl;
}
}
}

return 0;
}

[CCPC网络赛2019] B - array

题意

给出一个数列\(\{a_i\}\),维护两种操作:

  • 1 pos : 把\(a_{pos}\)加上\(10^7\)
  • 2 r k: 找到一个不小于\(k\)且不等于\(a_1,...,a_r\)的最小值\(x\)

共有\(m\)组询问,强制在线.

\(1 \leq n,m \leq 10^5\),\(1 \leq a_i \leq n\). 保证\(1 \leq pos,r,k\leq n\)

解法

建一棵权值线段树,每个点存储该数原来的位置.维护\(Set\)操作和\(minv\).

对于操作1,由限制易得等同于删除\(a_{pos}\).

对于操作2,我们搜索权值线段树里\([k,n]\)中大于\(r\)的第一个最小值,若不存在则答案为\(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
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
//HDU6703 
#include <bits/stdc++.h>
#define endl '\n'
using namespace std;
typedef long long ll;
const int INF = 1e9+7;
struct segtree{
const int NO_OPT = INT_MAX;
int size = 1;
vector<int>setv,maxv;
void init(int n){
while(size < n)size *= 2;
setv.assign(size*2,NO_OPT);
maxv.assign(size*2,0);
}
inline int ls(int x){
return (x<<1)|1;
}
inline int rs(int x){
return (x<<1)+2;
}
void pushdown(int x,int lx,int rx){
if(rx - lx == 1)return;
if(NO_OPT != setv[x]){
setv[ls(x)] = setv[rs(x)] = setv[x];
maxv[ls(x)] = maxv[rs(x)] = setv[x];
setv[x] = NO_OPT;
}
}
void pushup(int x){
maxv[x] = max(maxv[ls(x)],maxv[rs(x)]);
}
void Set(int l,int r,int v,int x,int lx,int rx){
if(l >= rx or lx >= r)return;
pushdown(x,lx,rx);
if(l <= lx and rx <= r){
setv[x] = v;
maxv[x] = v;
return;
}
int m = (lx+rx)/2;
Set(l,r,v,ls(x),lx,m);
Set(l,r,v,rs(x),m,rx);
pushup(x);
}
void Set(int l,int r,int v){
r++;
Set(l,r,v,0,0,size);
}
int kth(int l,int v,int x,int lx,int rx){
pushdown(x,lx,rx);
if(l >= rx or maxv[x] < v)return NO_OPT;
if(rx - lx == 1){
if(setv[x] != NO_OPT){
maxv[x] = setv[x];
setv[x] = NO_OPT;
}
if(maxv[x] >= v)return lx;
else return NO_OPT;
}
int m = (lx+rx)/2;
int idx1 = kth(l,v,ls(x),lx,m),idx2 = NO_OPT;
if(idx1 == NO_OPT)idx2 = kth(l,v,rs(x),m,rx);
return min(idx1,idx2);
}
int kth(int l,int v){
return kth(l,v,0,0,size);
}
};
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int t;
cin >> t;
while(t--){
int lstans = 0;
segtree seg;
int n,m;
cin >> n >> m;
vector<int>raw(n+10);
seg.init(1e5+10);
seg.Set(0,0,INF);
for(int i = 1;i <= n;i++){
cin >> raw[i];
seg.Set(raw[i],raw[i],i);
}
while(m--){
int opt,l,r;
cin >> opt >> l;
l ^= lstans;
if(opt == 1){
seg.Set(raw[l],raw[l],INF);
}
else{
cin >> r;
r ^= lstans;
lstans = seg.kth(r,l+1);
if(lstans == INT_MAX)lstans = n+1;
cout << lstans << endl;
}
}
}
return 0;
}

English version

We will use lazy tag to solve this problem. Let \(upv[x]\) be the maximum bound of node \(x\), and \(lowv[x]\) be the minimum bound of node \(x\). As we only care about single point value, the leaf node's \(lowv[]\) is the answer. Now the problem is how to maintain those two arrays. Now we assume we are operating on node \(x\), and the value of operation is \(h\).

Part 1: Adding phase

​ In this part we maintain \(lowv[x]\). If \(lowv[x] < h\) or there's no operation on \(x\), let \(lowv[x] = h\).

Part 2: Removing phase

​ We maintain \(upv[x]\) just like part one. If \(upv[x] > h\),let \(upv[x] = h\). Notice if \(lowv[x] > h\), let \(lowv[x] = h\).

Part 3: Pushdown.

​ Just apply Part 1 and 2 on child nodes(\(y\)). If node's \(lowv[y]\) greater than \(upv[x]\), cut it off and vice versa.

Code

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
//P-4-E 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int NO_OPT = INT_MAX;
struct segtree{
int size = 1;
vector<int>upv,lowv;
void init(int n){
while(size < n)size *= 2;
upv.assign(size*2,NO_OPT);
lowv.assign(size*2,NO_OPT);
}
inline int ls(int x){
return (x<<1)|1;
}
inline int rs(int x){
return (x<<1)+2;
}
void pushdown(int x,int lx,int rx){
if(rx - lx == 1)return;
if(upv[x] != NO_OPT){
upv[ls(x)] = min(upv[x],upv[ls(x)]);
upv[rs(x)] = min(upv[x],upv[rs(x)]);
if(lowv[ls(x)] != NO_OPT and lowv[ls(x)] > upv[x])lowv[ls(x)] = upv[x];
if(lowv[rs(x)] != NO_OPT and lowv[rs(x)] > upv[x])lowv[rs(x)] = upv[x];
upv[x] = NO_OPT;
}
if(lowv[x] != NO_OPT){
if(lowv[ls(x)] != NO_OPT)lowv[ls(x)] = max(lowv[ls(x)],lowv[x]);
else lowv[ls(x)] = lowv[x];
if(lowv[rs(x)] != NO_OPT)lowv[rs(x)] = max(lowv[rs(x)],lowv[x]);
else lowv[rs(x)] = lowv[x];
if(upv[ls(x)] != NO_OPT and upv[ls(x)] < lowv[x])upv[ls(x)] = lowv[x];
if(upv[rs(x)] != NO_OPT and upv[rs(x)] < lowv[x])upv[rs(x)] = lowv[x];
lowv[x] = NO_OPT;
}
}
void up(int l,int r,int v,int x,int lx,int rx){
if(l >= rx or lx >= r)return;
pushdown(x,lx,rx);
if(l <= lx and rx <= r){
upv[x] = min(upv[x],v);
if(lowv[x] != NO_OPT and lowv[x] > v)lowv[x] = v;
return;
}
int m = (lx+rx)/2;
up(l,r,v,ls(x),lx,m);
up(l,r,v,rs(x),m,rx);
return;
}
void up(int l,int r,int v){
up(l,r,v,0,0,size);
}
void low(int l,int r,int v,int x,int lx,int rx){
if(l >= rx or lx >= r)return;
pushdown(x,lx,rx);
if(l <= lx and rx <= r){
if(lowv[x] == NO_OPT or lowv[x] < v)lowv[x] = v;
return;
}
int m = (lx+rx)/2;
low(l,r,v,ls(x),lx,m);
low(l,r,v,rs(x),m,rx);
return;
}
void low(int l,int r,int v){
low(l,r,v,0,0,size);
}
int get(int idx,int x,int lx,int rx){
if(idx >= rx)return NO_OPT;
pushdown(x,lx,rx);
if(rx - lx == 1)return lowv[x];
int m = (lx+rx)/2;
if(idx < m)return get(idx,ls(x),lx,m);
else return get(idx,rs(x),m,rx);
}
int get(int idx){
return get(idx,0,0,size);
}
};
signed main(){
ios::sync_with_stdio(0);
cin.tie(0);
int n,m;
cin >> n >> m;
segtree seg;
seg.init(n+1);
while(m--){
int opt,l,r,v;
r++;
cin >> opt >> l >> r >> v;
r++;
if(opt == 1)seg.low(l,r,v);
else seg.up(l,r,v);
}
for(int i = 0;i < n;i++){
int ans = seg.get(i);
cout << (ans == NO_OPT?0:ans) << '\n';
}
return 0;
}

题意简述

https://codeforces.com/edu/course/2/lesson/5/4/practice/contest/280801/problem/E

或:

https://www.luogu.com.cn/problem/P4560

长度为\(n\)的序列初始为空,维护两种操作:

  1. Add(L,R,v): 将\([L,R]\)中小于\(v\)的值改变为\(v\)
  2. Remove(L,R,v):将\([L,R]\)中大于\(v\)的值改变为\(v\)

给出\(m\)个操作,输出操作全部完成后的序列.

\(1 \leq n \leq 2\cdot10^6,1 \leq m \leq 5\cdot10^5\)

思路

对每个节点维护\(upv[]\)\(lowv[]\),表示该节点值的上限和下限.查询时对每个点做一次单点查询,答案为\(lowv[x]\).

下放节点时\(upv\)\(lowv\)的关系比较显然,细节请见代码.

dlstxdy!

杜教筛用于快速求数论函数的前缀和.当预处理了小于\(n^\frac{2}{3}\)的前缀和时,杜教筛的时间复杂度为\(O(n^\frac{2}{3})\)

形式化定义

给出数论函数\(f\),快速求\(S(n) = \sum_{i = 1}^nf(i)\)

解法

引理: \[ \sum_{i = 1}^n f \ast g (i) = \sum_{i = 1}^ng(i) \cdot S(\lfloor \frac{n}{i} \rfloor) \] 证明: $$ \[\begin{align} &\sum_{i = 1}^n f \ast g (i) \\ &= \sum_{i = 1}^n \sum_{d|n}f(d)\cdot g(\frac{n}{d})\\ &= \sum_{d = 1}^ng(d)\sum_{i*d = 1,d|i}^{i*d <= n}f(\frac{i}{d})\\ &= \sum_{d = 1}^ng(d)\sum_{i = 1}^{\lfloor \frac{n}{d} \rfloor}f(i)\\ &= \sum_{d = 1}^ng(d)\cdot S(\lfloor \frac{n}{d} \rfloor) \end{align}\] \[ 观察上式,我们能得到: \] \[\begin{align} &g(1) \cdot S(n) \\ &= \sum_{i = 1}^ng(i) \cdot S(\lfloor \frac{n}{i} \rfloor) -\sum_{i = 2}^ng(i) \cdot S(\lfloor \frac{n}{i} \rfloor)\\ &= \sum_{i = 1}^n f \ast g(i) - \sum_{i = 2}^ng(i) \cdot S(\lfloor \frac{n}{i} \rfloor)\\ \end{align}\] $$ 这个式子的后半部分可以用数论分块递归去求,如果我们构造的\(g\)能快速求出\(\sum_{i = 1}^nf\ast g(i)\),我们就能快速求出\(g(1)\cdot S(n)\).

例子

\(\mu\)

我们知道\(\mu \ast 1 = \epsilon\)

因而有: \[ \begin{align} &1\cdot S(n) = S(n)\\ &= \sum_{i = 1}^n\mu \ast 1(i) - \sum_{i = 2}^n 1 \cdot S(\lfloor \frac{n}{i}\rfloor)\\ &= \sum_{i = 1}^n \epsilon - \sum_{i = 2}^n S(\lfloor \frac{n}{i}\rfloor)\\ &= 1 - \sum_{i = 2}^n S(\lfloor \frac{n}{i}\rfloor)\\ \end{align} \] 简单地把\(\mu \ast 1 = \epsilon\)带进上面的结论就行了.

代码:

(Mu为map,记忆化求过的μ.求之前先线性筛一遍小于maxn的μ.下面求φ同理)

注意\(g = 1\),减的时候别漏了(\(r-l+1\))=-=

1
2
3
4
5
6
7
8
9
10
ll du_mu(int x){
if(x < maxn)return mu[x];
else if(Mu[x])return Mu[x];
ll tans = 1;
for(int l = 2,r = 0;l <= x;l = r+1){
r = x/(x/l);
tans -= 1LL*(r - l + 1)*du_mu(x/l);
}
return Mu[x] = tans;
}

\(\varphi\)

和μ大同小异.我们有\(\varphi \ast 1 = id\),其中\(id(n) = n\) \[ \begin{align} &1\cdot S(n) = S(n)\\ &= \sum_{i = 1}^n\varphi \ast 1(i) - \sum_{i = 2}^n 1 \cdot S(\lfloor \frac{n}{i}\rfloor)\\ &= \sum_{i = 1}^n id(i) - \sum_{i = 2}^n S(\lfloor \frac{n}{i}\rfloor)\\ &= \frac{n*(n+1)}{2} - \sum_{i = 2}^n S(\lfloor \frac{n}{i}\rfloor)\\ \end{align} \] 代码:

1
2
3
4
5
6
7
8
9
10
ll du_phi(int x){
if(x < maxn)return phi[x];
else if(Phi[x])return Phi[x];
ll tans = 1LL*x*(x+1)/2;
for(int l = 2,r = 0;l <= x;l = r+1){
r = x/(x/l);
tans -= 1LL*(r-l+1)*du_phi(x/l);
}
return Phi[x] = tans;
}

**本文思路部分来自于这篇题解*:凄魉 题解 P2257 【YY的GCD】

题意

多组数据,求 \[ \sum_{i = 1}^n\sum_{j = 1}^m [\gcd(i,j) == k],k \in prime \]

\(T = 10^4,1 \leq n,m \leq 10^7\)

思路

前置

首先我们在求 \[ \sum_{i = 1}^n\sum_{j = 1}^m [\gcd(i,j) == 1] \]

时有这么一种做法,利用了莫比乌斯函数\(\mu\)的性质: \[ \sum_{d|n}\mu(d) = [n = 1] \]


\[ \begin{align} &\sum_{i=1}^{n}\sum_{i=1}^{m}[\gcd(i,j) == 1] \\ &=\sum_{i=1}^{n}\sum_{i=1}^{m}\epsilon(\gcd(i,j))\\ &=\sum_{i=1}^{n}\sum_{i=1}^{m}\sum_{d|gcd(i,j)}\mu(d)\\ &=\sum_{d = 1}^{\min(n,m)}\mu(d)\sum_{i = 1}^n[d|i]\sum_{j = 1}^{m}[d|j]\\ &=\sum_{d=1}^{\min(n,m)}\mu(d)\lfloor \frac{n}{d}\rfloor \lfloor \frac{m}{d} \rfloor \end{align} \] 那么我们只要构造一个数论函数\(f\),使得 \[ \sum_{d|n}f(d) = [n \in prime] \]

答案就变为了 \[ \sum_{d=1}^{\min(n,m)}f(d)\lfloor \frac{n}{d}\rfloor \lfloor \frac{m}{d} \rfloor \]

构造方法

形式化地,设数论函数\(g\),构造\(f\)使得\(\sum_{d|n}f(d) = g(n)\)

  1. 对于\(k \in [1,n]\),使\(f(k) = g(k)\)
  2. \(f(n) = f(n) - \sum_{d|n,d \neq n}f(d)\)

如此,我们有: \[ \sum_{d|n}f(d) = \sum_{d|n,d \neq n}f(d) + f(n) = g(n) \] 第二步可以采用类似埃氏筛的方法,复杂度\(O(n\ln n)\)

1
2
3
for(int i = 1;i < maxn;i++)f[i] = g[i];
for(int i = 1;i < maxn;i++)
for(int j = i+i;j < maxn;j += i)f[j] -= f[i];

对于本题,我们设\(g(n) = [n \in prine]\)即可,可以在\(O(n)\)时间内筛出\(g\).

总复杂度:\(O(n + n\ln n + T \sqrt{\min(n,m)})\)

代码

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
//P2257
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7+10;
int f[maxn];
bool not_p[maxn];
vector<int>prime;
void sieve(){
not_p[1] = 1;
for(int i = 2;i < maxn;i++){
if(!not_p[i]){
f[i] = 1;
prime.push_back(i);
}
for(int p:prime){
if(ll(p)*i >= maxn)break;
not_p[p*i] = 1;
if(i%p == 0)break;
}
}
for(int i = 1;i < maxn;i++)
for(int j = i+i;j < maxn;j += i)f[j] -= f[i];
for(int i = 1;i < maxn;i++)f[i] += f[i-1];
}
ll cal(int x,int y){
ll tans = 0;
for(int l = 1,r = 0;l <= min(x,y);l = r+1){
r = min(x/(x/l),y/(y/l));
tans += 1LL*(f[r] - f[l-1])*(x/l)*(y/l);
}
return tans;
}
int main(){
sieve();
ios::sync_with_stdio(0);
int t;
cin >> t;
while(t--){
int n,m;
cin >> n >> m;
cout << cal(n,m) << endl;
}
return 0;
}

PS.不是我不喜欢用行间公式,是hexo渲染行间LaTex总出奇怪的bug,凑合着看吧=-=

另一种做法

以下均有\(k \in prime\) \[ \begin{align} &\sum_{k = 1}^n\sum_{i = 1}^n\sum_{j = 1}^n[\gcd(i,j) == k]\\ &=\sum_{k = 1}^n\sum_{i = 1}^{\frac{n}{k}}\sum_{j = 1}^{\frac{m}{k}}[\gcd(i,j) == 1]\\ &=\sum_{k = 1}^n\sum_{i = 1}^{\frac{n}{k}}\sum_{j = 1}^{\frac{m}{k}}\sum_{d|\gcd(i,j)}\varphi(d)\\ &=\sum_{k = 1}^n\sum_{d = 1}^{\frac{n}{k}}\varphi(d)\cdot \lfloor\frac{n}{kd}\rfloor\lfloor\frac{m}{kd}\rfloor\\ &设T = kd,枚举T\\ &=\sum_{k = 1}^n\sum_{d=1}^{\frac{n}{k}}\varphi(d)\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\\ &=\sum_{T = 1}^n\lfloor\frac{n}{T}\rfloor\lfloor\frac{m}{T}\rfloor\sum_{k|T}\mu(\frac{T}{k}) \end{align} \] 可以看出\(f(T) = \sum_{k|T}\mu(\frac{T}{k})\)可以预处理

复杂度:不会

1
2
3
for(int p:prime){
for(int j = 1;1ll*j*p < maxn;j++)f[j*p] += mu[j];
}

code:

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
//P2257_2 
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int maxn = 1e7+10;
bool not_p[maxn];
int mu[maxn];
ll f[maxn];
vector<int>prime;
void sieve_mu(){
mu[1] = not_p[1] = 1;
for(int i = 2;i < maxn;i++){
if(!not_p[i]){
prime.push_back(i);
mu[i] = -1;
}
for(int p:prime){
if(1ll*p*i >= maxn)break;
not_p[i*p] = 1;
if(i%p)mu[i*p] = -mu[i];
else{
mu[i*p] = 0;
break;
}
}
}
for(int p:prime){
for(int j = 1;1ll*j*p < maxn;j++)f[j*p] += mu[j];
}
for(int i = 1;i < maxn;i++)f[i] += f[i-1];
}
ll cal(int n,int m){
ll tans = 0;
for(int l = 1,r = 0;l <= min(n,m);l = r+1){
r = min(n/(n/l),m/(m/l));
tans += (f[r] - f[l-1])*(n/l)*(m/l);
}
return tans;
}
signed main(){
sieve_mu();
int t;
cin >> t;
while(t--){
int n,m;
cin >> n >> m;
cout << cal(n,m) << endl;
}
return 0;
}