Believe it

相信不屈不挠的努力,相信战胜死亡的年轻

珂朵莉树

珂朵莉树是一颗树,我们用集合来维护,c++中集合是红黑树,所以我们借助此集合来完成珂朵莉树。
我们将区间分段,那么各段有唯一的左端点,我们将左端点放入集合,当我们遍历集合的时候,我们就得到了我们要的序列,此时我们维护了结构,但未维护值,进一步发现我们可以使用map,用键值对来维护更多信息,键用来维护树的结构,值来维护序列的值。

split

因为我们要维护区间信息,所以我们需要操作split来提取区间,本质上提取区间为提取单点,这一点在splay中表现的很出色,当我们提取出左端点和右端点的时候,区间也就被提取出来了,如果提取位置x,在红黑树中我们二分到x的位置,若此处是一个区间[l,r],我们将此区间拆分为[l,x-1][x,r]即可。

assign

我们提取出区间,删掉这些节点然后,插入一个新的节点即可

add

我们提取出区间,暴力更新所有节点即可

sum

我们提取出区间,暴力计算所有节点,使用快速幂

kth

我们提取出区间,还是暴力

什么时候选择此数据结构

数据随机且含有区间赋值操作,此数据结构的操作可以在splay上实现,并维护更多信息,map法仅仅只是编码简单了很多。

例题

C. Willem, Chtholly and Seniorious
odt代码
cf896cview raw
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
#include <bits/stdc++.h>
using namespace std;
/*
* cf896c
* 区间加,区间赋值,求区间k次方和,求区间第k小,数据随机,复杂度(n+m)lgn
*
* ODT -> old driver tree -> 珂朵莉树
*
* */
typedef long long ll;
ll qpow(ll a,ll b,ll p){ll r=1;for(;b;b>>=1,a=a*a%p)if(b&1)r=r*a%p;return r;}
typedef map<int,ll>::iterator IT;
IT split(map<int,ll>&t,int x){//将x分出来,使得这是某个区间的左端点
IT it=t.lower_bound(x);
if(it->first==x) return it;
return t.emplace(x,(--it)->second).first;
}
void assign(map<int,ll>&t,int l,int r,int w){
t.erase(split(t,l),split(t,r+1)), t.emplace(l,w);
}
void add(map<int,ll>&t,int l,int r,int w){
for(IT i=split(t,l),j=split(t,r+1);i!=j;++i) i->second+=w;
}
int sum(map<int,ll>&t,int l,int r,int y,int p){
int res=0;
for(IT j=split(t,l),k=split(t,r+1),i=j++;i!=k;i=j++) {// i=j++写在最后执行
res=(res+qpow(i->second%p,y,p)*(j->first-i->first))%p;
}
return res;
}
ll kth(map<int,ll>&t,int l,int r,int k){// 1<=k<=r-l+1
vector<pair<ll,int>>v;
for(IT j=split(t,l),k=split(t,r+1),i=j++;i!=k;i=j++) {
v.emplace_back(i->second,j->first-i->first);
}
sort(v.begin(),v.end());
for(auto x:v) {
if(k<=x.second) return x.first;
else k-=x.second;
}
assert(false);// k>r-l+1
}

ll seed;
ll rnd(){
ll ret = seed;
seed = (seed * 7 + 13) % 1000000007;
return ret;
}

int main(){
map<int,ll>tr;
int n,m,vmax;
cin>>n>>m>>seed>>vmax;
for(int i=1;i<=n;i++) tr[i]=rnd()%vmax+1;
tr[n+1]=-1;// 终点哨兵
int op,l,r,x,y;
for(int i=1;i<=m;i++) {
op=rnd()%4+1,l=rnd()%n+1,r=rnd()%n+1;
if(l>r)swap(l,r);
if(op==3) x=rnd()%(r-l+1)+1;
else x=rnd()%vmax+1;
if(op==4) y=rnd()%vmax+1;
switch(op){
case 1:add(tr,l,r,x);break;
case 2:assign(tr,l,r,x);break;
case 3:printf("%lld\n",kth(tr,l,r,x));break;
case 4:printf("%d\n",sum(tr,l,r,x,y));break;
}
}
}

生成树

一个无向图的生成树指的是从图中选若干边构成边集,全部点构成点集,使得这个边集加上点集恰好是一棵树。

生成树计数

一个无向无权图(允许重边不允许自环)的邻接矩阵为g,显然这是一个对称矩阵,g[u][v]代表边(u,v)的重数,即若存在一条边(u,v)则g[u][v]的值为1,若存在k条,则g[u][v]的值为k。
一个无向无权图(允许重边不允许自环)的度数矩阵为deg,显然这是一个对角矩阵,deg[u][u]代表点u的度数。
一个无向无权图(允许重边不允许自环)的基尔霍夫矩阵(拉普拉斯矩阵)为hoff,是度数矩阵减去邻接矩阵。
矩阵树定理说一个无向图的生成树的个数刚好等于基尔霍夫矩阵的行列式的任意n-1阶主子式(代数余子式)的行列式的绝对值。
生成树计数复杂度\(O(V^3+E)=O(V^3)\)
黑暗前的幻想乡
我们利用矩阵树定理就能轻松解决
黑暗前的幻想乡代码
p4336view raw
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
#include <bits/stdc++.h>
using namespace std;

/*
*
* 矩阵树定理
* 基尔霍夫矩阵定义为度数矩阵减去邻接矩阵
* 生成树的个数等于基尔霍夫矩阵的任意一个代数余子式的行列式的值的绝对值
* 完全图的生成树的个数是pow(n,n-2)
*
*
* p4336
* 容斥+矩阵树定理
*
*
* */

const int V=20,p=1e9+7;
typedef long long ll;
ll qpow(ll a,ll b){ll s=1;for(;b;b>>=1,a=a*a%p)if(b&1)s=s*a%p;return s;}
int hoff[V][V];// memset(hoff,0,sizeof(hoff));
void addedge(int u,int v){hoff[u][u]++,hoff[v][v]++,hoff[u][v]--,hoff[v][u]--;}
#define rep(i,j,k) for(int i=(j);i<=(k);++i)
static int det(int m[V][V],int n,int p){// 可以计算任意行列式的值 a[1..n][1..n]
int r=1;
rep(i,1,n) rep(j,1,n) m[i][j]=(m[i][j]%p+p)%p;
rep(i,1,n) rep(j,i+1,n) {//枚举行i,去消掉行j 目的是将他变成上三角
int &u=m[i][i],&v=m[j][i],a=1,b=0,c=0,d=1;
while(v){// x=m[i][i],y=m[j][i],则u=ax+by&&v=cx+dy)
int k=p-u/v;
a=(a+1ll*k*c)%p,b=(b+1ll*k*d)%p,u=(u+1ll*k*v)%p;//此行消元
swap(u,v),swap(a,c),swap(b,d),r=-r;//行互换 行列式值变为相反数
}
rep(k,i+1,n) {//消元
int x=m[i][k],y=m[j][k];
m[i][k]=(1ll*a*x+1ll*b*y)%p;
m[j][k]=(1ll*c*x+1ll*d*y)%p;
}
}
rep(i,1,n) r=1ll*r*m[i][i]%p;
return (r+p)%p;
}

typedef pair<int,int>pii;
int read(){int a;scanf("%d",&a);return a;}
int main(){
vector<pii> v[20];
int n=read();
rep(i,1,n-1) for(int x=read();x>=1;--x) v[i].emplace_back(read(),read());
int up=(1<<(n-1))-1,ans=0;
rep(i,0,up){
rep(x,1,n)rep(y,1,n) hoff[x][y]=0;
for(int j=i;j;j&=j-1) for(pii x:v[__builtin_ffs(j)])addedge(x.first,x.second);
if(__builtin_parity(i^up)&1) ans=(ans+p-det(hoff,n-1,p))%p;
else ans=(ans+det(hoff,n-1,p))%p;
}
cout<<ans<<endl;
}

最小生成树

有一个无向带权图,每条边有权\(x_i\),需要求出一个生成树T,并最小化\(\begin{aligned}\sum_{i\in T}x_i\end{aligned}\) kruskal算法:贪心从小到大枚举边合并森林即可。这里就不证明此算法了。
最小生成树复杂度\(O(V+ElgE)=O(ElgE)\)
最小生成树
最小生成树代码
p3366view raw
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
#include<bits/stdc++.h>
using namespace std;

const int N=5000+5;
int f[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}

struct edge{int u,v,w;};
edge g[200000+10];

int read(){int x;scanf("%d",&x);return x;}
int main(){
//最小生成树 复杂度O(mlgm)
//n为点数,m为边数,后面m行u->v边权w 保证联通且存在最小生成树
int n=read(),m=read();
for(int i=1;i<=n;i++) f[i]=i;
for(int i=1;i<=m;i++) g[i].u=read(),g[i].v=read(),g[i].w=read();
sort(g+1,g+1+m,[](edge l,edge r){return l.w<r.w;});
int ans=0;
for(int i=1;i<=m;i++) {
if(find(g[i].u)!=find(g[i].v)){
f[find(g[i].u)]=find(g[i].v);
ans+=g[i].w;
}
}
cout<<ans<<endl;
}

最小生成树计数

由于最小生成树各自边权构成的多重集合是一样的,并且易证不同的边权对最小生成树的影响是独立的,所以我们可以通过将边按照边权分类,分别求出每一种边权各自对联通块的贡献,然后利用计数的乘法原理合并即可。我们需要先求出任意一个最小生成树,当我们对某一种边权进行讨论的时候,我们需要将这个生成树中这一边权的边全删掉,然后对剩余联通块进行缩点并重新编号,将待选集合中的边映射到联通块间的边,并去掉自环。这样以后待选集合中的边的边权就相等了。这时我们可以借助矩阵树定理来求解。
最小生成树计数复杂度\(O(ElgE+V^3)=O(V^3)\)
最小生成树计数
最小生成树计数代码
p4208view raw
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
#include <bits/stdc++.h>
using namespace std;

/*
*
* 矩阵树定理
* 基尔霍夫矩阵定义为度数矩阵减去邻接矩阵
* 生成树的个数等于基尔霍夫矩阵的任意一个代数余子式的行列式的值的绝对值
* 完全图的生成树的个数是pow(n,n-2)
*
*
* p4208
* 最小生成树计数
*
*
* */
typedef long long ll;
#define rep(i,j,k) for(int i=(j);i<=int(k);++i)
#define SZ(x) int(x.size())

const int N=1e5;
int f[N];
inline int find(int x){return x==f[x]?x:f[x]=find(f[x]);}

vector<int>disc;
int getid(int x){return lower_bound(disc.begin(),disc.end(),x)-disc.begin()+1;}
ll qpow(ll a,ll b,ll p){ll s=1;for(;b;b>>=1,a=a*a%p)if(b&1)s=s*a%p;return s;}
struct mst{
struct sedge{int u,v,w;};
static const int V=105;
int hoff[V][V];// memset(hoff,0,sizeof(hoff));
void addedge(int u,int v){hoff[u][u]++,hoff[v][v]++,hoff[u][v]--,hoff[v][u]--;}
static int det(int m[V][V],int n,int p){// 可以计算任意行列式的值 a[1..n][1..n]
int r=1;
rep(i,1,n) rep(j,1,n) m[i][j]=(m[i][j]%p+p)%p;
rep(i,1,n) rep(j,i+1,n) {//枚举行i,去消掉行j 目的是将他变成上三角
int &u=m[i][i],&v=m[j][i],a=1,b=0,c=0,d=1;
while(v){// x=m[i][i],y=m[j][i],则u=ax+by&&v=cx+dy)
int k=p-u/v;
a=(a+1ll*k*c)%p,b=(b+1ll*k*d)%p,u=(u+1ll*k*v)%p;//此行消元
swap(u,v),swap(a,c),swap(b,d),r=-r;//行互换 行列式值变为相反数
}
rep(k,i+1,n) {//为了处理非质数的消元方案,这里采取了这种不明智的方案,导致常数增大了一倍
int x=m[i][k],y=m[j][k];
m[i][k]=(1ll*a*x+1ll*b*y)%p;
m[j][k]=(1ll*c*x+1ll*d*y)%p;
}
}
rep(i,1,n) r=1ll*r*m[i][i]%p;
return (r+p)%p;
}
int countmst(sedge*edge,int n,int m,int p){// 1..n
sort(edge+1,edge+1+m,[](sedge&a,sedge&b){return a.w<b.w;});
rep(i,1,n) f[i]=i;
vector<int>used;
rep(i,1,m) {
if(find(edge[i].u)!=find(edge[i].v)){
f[find(edge[i].u)]=find(edge[i].v);
used.push_back(i);
}
}
if(SZ(used)!=n-1) return 0;//无树
int ans=1;
for(int l=1,r=0;l<=m;l=r+1){
while(r+1<=m&&edge[r+1].w==edge[l].w) ++r;
rep(i,1,n) f[i]=i;// O(n*n)
for(int i:used) if(i<l||i>r) f[find(edge[i].u)]=find(edge[i].v);//O(n*n)
disc.clear();
rep(i,l,r) if(find(edge[i].u)!=find(edge[i].v)) {//准备离散化
disc.push_back(find(edge[i].u));
disc.push_back(find(edge[i].v));
}
sort(disc.begin(),disc.end());
disc.erase(unique(disc.begin(),disc.end()),disc.end());
rep(i,1,SZ(disc))rep(j,1,SZ(disc))hoff[i][j]=0;
rep(i,l,r) if(find(edge[i].u)!=find(edge[i].v)) { //利用矩阵树定理合并
addedge(getid(find(edge[i].u)),getid(find(edge[i].v)));
}
ans=1ll*ans*det(hoff,SZ(disc)-1,p)%p;
}
return ans;
}
}g;

int read(){int a;scanf("%d",&a);return a;}
int main(){
int n=read(),m=read();
vector<mst::sedge>edge;
rep(i,1,m) {
int u=read(),v=read(),w=read();
edge.push_back(mst::sedge{u,v,w});
}
cout<<g.countmst(edge.data()-1,n,m,31011)<<endl;
}

严格次小生成树

严格次小生成树和最小生成树的边权多重集合中只有一个边权不一样,这样我们就有了一个简单的算法,先求出任意一个最小生成树,然后枚举没有被选中为构建最小生成树的边,假设这个边为\((u,v,w_1)\),我们在最小生成树上求出点\(u\)到点\(v\)这条路径上的最大边权\(w_2\)和严格次大边权\(w_3\),若\(w_1=w_2\)则我们用此边换掉次大边,若\(w_1>w_2\)则我们用此边换掉最大边,这样我们最终能得到很多非最小生成树,从中选出最小的那个,他就是次小生成树,这个过程需要维护树上的路径信息,有算法都可以树剖、树上倍增、lct等等,我们这里使用树上倍增的办法来解决。
严格次小生成树时间复杂度\(O(ElgE+ElnV)=O(ElgE)\)
严格次小生成树
严格次小生成树代码
p4180view raw
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
#include<bits/stdc++.h>
using namespace std;

typedef pair<int,int> pii;
#define rep(i,j,k) for(int i=j;i<=int(k);++i)
#define per(i,j,k) for(int i=j;i>=int(k);--i)
#define repe(i,u) for(int i=head[u];~i;i=nex[i])
int lg[1<<18];// rep(i,2,1<<18) lg[i]=lg[i>>1]+1;
struct tree{
static const int V=1e5+5,E=2*V;
int head[V],n;
int to[E],nex[E],ew[E],m,tot;
void addedge1(int u,int v,int w) {to[++tot]=v,nex[tot]=head[u],ew[tot]=w,head[u]=tot;}
void addedge(int u,int v,int w){addedge1(u,v,w),addedge1(v,u,w);}
void ini(int _n){tot=-1; n=_n; rep(i,1,n)head[i]=-1;}
void upd(int*x,int*y){// max
if(x[1]==y[1]) x[0]=max(x[0],y[0]);
else if(x[1]>y[1]) x[0]=y[1];
else x[0]=x[1],x[1]=y[1];
}
void upd(int*x,int*y,int*z){upd(x,y),upd(x,z);}
int f[V][21],pw[V][21][2],dep[V];
void dfs(int u,int father,int w=0){
dep[u]=dep[father]+1;
f[u][0]=father;
pw[u][0][0]=0;
pw[u][0][1]=w;
rep(i,1,20) {
f[u][i]=f[f[u][i-1]][i-1]; // assert(f[0][i]=0);
upd(pw[u][i],pw[u][i-1],pw[f[u][i-1]][i-1]);
}
repe(i,u)if(to[i]!=father)dfs(to[i],u,ew[i]);
}
pii getbigedge(int x,int y){
int res[2]={0,0};
if(dep[x]<dep[y]) swap(x,y);
while(dep[x]>dep[y]) {
upd(res,pw[x][lg[dep[x]-dep[y]]]);
x=f[x][lg[dep[x]-dep[y]]];// 取下整
}if(x==y)return pii(res[0],res[1]);
per(i,lg[dep[x]-1],0) if(f[x][i]!=f[y][i]) {
upd(res,pw[x][i],pw[y][i]);
x=f[x][i],y=f[y][i];
}
upd(res,pw[x][0],pw[y][0]);
return pii(res[0],res[1]);
}
};

struct secondtmst{
static const int N=1e5+5;
int f[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}
struct edge{int u,v,w;};

long long kruskal(int n, vector<edge> &g, tree &t) {
vector<edge> nmst;
long long res=0;
int add=1e9;//设置边权最大值
rep(i,1,n) f[i]=i;
sort(g.begin(),g.end(),[](edge l,edge r){return l.w<r.w;});
t.ini(n);
for(edge e:g) {
if(find(e.u)!=find(e.v)) {
f[find(e.u)]=find(e.v);
t.addedge(e.u,e.v,e.w);
res+=e.w;
}else nmst.push_back(e);
}
t.dfs(1,0);

for(edge e:nmst) {
pii tmp=t.getbigedge(e.u,e.v);
if(tmp.second!=e.w) add=min(add,e.w-tmp.second);
if(tmp.first!=e.w) add=min(add,e.w-tmp.first);
}
return res+add;
}
};

tree t;
secondtmst mst;

int read(){int x;scanf("%d",&x);return x;}
int main(){
rep(i,2,1<<18) lg[i]=lg[i>>1]+1;
int n=read(),m=read();// n<1e5个点 m<3e5边 0<=w<=1e9
vector<secondtmst::edge>g(m);
rep(i,0,m-1) g[i].u=read(),g[i].v=read(),g[i].w=read();
cout<<mst.kruskal(n,g,t)<<endl;
}

最小乘积生成树

有一个无向带权图(权为正数),每条边有权\(x_i\)和权\(y_i\),需要求出一个生成树T,记\(\begin{aligned}X=\sum_{i\in T}x_i,Y=\sum_{i\in T}y_i\end{aligned}\),要求最小化乘积\(XY\)
我们假设已经求出了所有生成树,他们的权为\((X_i,Y_i)\)我们把这个二元组看做二维平面上的点,则最小乘积生成树一定在凸包上。进一步分析,整个凸包都在第一象限,那么我们可以锁定出两个点了,他们一定在凸包上。分别求出最小的\(X_i\)对映点\(A\),和最小的\(Y_i\)对映点\(B\),那么答案就在\(AB\)左下方,我们求出一个点\(C\),若能让叉积\(AC*AB\)最大且为正数,则\(C\)一定也在凸包上。我们递归处理\(AC\)\(CB\)即可。凸包上的点期望为lg级别。
最小乘积生成树复杂度\(O(ElgElg(V!))=O(ElgElgV)\)
最小乘积生成树
最小乘积生成树代码
p5540view raw
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
#include<bits/stdc++.h>
using namespace std;

const int N=5000+5;
int f[N];
int find(int x){return x==f[x]?x:f[x]=find(f[x]);}

struct edge{int u,v,w,x,y;};
edge g[200000+10];
//最小乘积生成树 O(mlgmsqrt(lgn!))=O(mlgmlgn) //https://www.luogu.org/problem/P5540
void kruskal(int n,int m,int&x,int&y){
x=y=0;
for(int i=1;i<=n;++i) f[i]=i;
sort(g+1,g+1+m,[](edge l,edge r){return l.w<r.w;});
for(int i=1;i<=m;++i) {
if(find(g[i].u)!=find(g[i].v)){
f[find(g[i].u)]=find(g[i].v);
x+=g[i].x,y+=g[i].y;
}
}
}

int ansx,ansy;
void update(int x,int y){
if(x*y<ansx*ansy) ansx=x,ansy=y;
else if(x*y==ansx*ansy&&x<ansx) ansx=x,ansy=y;
}
void solve(int n,int m,int x1,int y1,int x2,int y2){
int x3,y3;
for(int i=1;i<=m;++i) g[i].w=g[i].y*(x2-x1)+g[i].x*(y1-y2);
kruskal(n,m,x3,y3);
update(x3,y3);
if((x2-x1)*(y3-y1)>=(x3-x1)*(y2-y1)) return;
solve(n,m,x1,y1,x3,y3);
solve(n,m,x3,y3,x2,y2);
}

int read(){int x;scanf("%d",&x);return x;}
int main(){
//n为点数,m为边数,后面m行u->v边权(x,y) 保证联通且存在最小生成树
int n=read(),m=read();
for(int i=1;i<=m;++i) g[i].u=read()+1,g[i].v=read()+1,g[i].x=read(),g[i].y=read();
int x1,y1,x2,y2;
for(int i=1;i<=m;++i) g[i].w=g[i].x;
kruskal(n,m,x1,y1);
for(int i=1;i<=m;++i) g[i].w=g[i].y;
kruskal(n,m,x2,y2);
ansx=x1,ansy=y1;
update(x2,y2);
solve(n,m,x1,y1,x2,y2);
cout<<ansx<<" "<<ansy<<endl;
}

最小差值生成树

有一个无向带权图,每条边有权\(x_i\),需要求出一个生成树T,让T的最大边权和最小边权的差值尽可能小。
我们对边集排序后,等价于求最短的一段区间,这个区间内部的边能够生成一棵树,这种连通性维护的问题,直接使用lct就可以了,
最小差值生成树时间复杂度\(O(ElgE+Elg(E+V))=O(ElgE)\)
最小差值生成树
最小差值生成树代码
p4234view raw
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
#include<bits/stdc++.h>
using namespace std;

//仅仅维护图联通的lct tle可能因为自环
struct graphlct{
static const int V=5e4+5,E=2e5+5;
static const int N=V+E;//拆点
int X[N],Y[N],W[N],n,m;//预先传入的参数
int c[N][2],f[N],t[N],s[N],rev[N],val[N];// t->times s->stack
set<int>e;
void ini(){// X是边起点从1开始,Y是终点从1开始,R是删除的顺序,越小越先删除 edgenums维护边的条数
e.clear();
for(int i=0;i<=n;i++)c[i][0]=c[i][1]=f[i]=rev[i]=0,t[i]=i,val[i]=2e9;
for(int i=n+1;i<=n+m;i++)c[i][0]=c[i][1]=f[i]=rev[i]=0,t[i]=i,val[i]=W[i-n];
}
inline void pushup(int x){
t[x]=x;
if(val[t[c[x][0]]]<val[t[x]]) t[x]=t[c[x][0]];
if(val[t[c[x][1]]]<val[t[x]]) t[x]=t[c[x][1]];
}
inline void pushdown(int x){
int l=c[x][0],r=c[x][1];
if(rev[x]){
rev[l]^=1;rev[r]^=1;rev[x]^=1;
swap(c[x][0],c[x][1]);
}
}
inline bool isroot(int x){return c[f[x]][0]!=x&&c[f[x]][1]!=x;}
inline void rotate(int x){
int y=f[x],z=f[y],xis=c[y][1]==x,yis=c[z][1]==y;//
if(!isroot(y)) c[z][yis]=x;//son
f[x]=z;f[y]=x;f[c[x][xis^1]]=y;//father
c[y][xis]=c[x][xis^1];c[x][xis^1]=y;//son
pushup(y);
}
inline void splay(int x){
s[s[0]=1]=x;//init stack
for(int i=x;!isroot(i);i=f[i])s[++s[0]]=f[i];//update stack
for(int i=s[0];i;i--)pushdown(s[i]);//pushroad
while(!isroot(x)){
int y=f[x],z=f[y];
if(!isroot(y)) (c[y][0]==x)^(c[z][0]==y)?rotate(y):rotate(x);
rotate(x);
}pushup(x);
}
inline void access(int x){for(int i=0; x; i=x, x=f[x])splay(x), c[x][1]=i,pushup(x);}
inline int treeroot(int x){access(x);splay(x);while(c[x][0])x=c[x][0];return x;}
inline void makeroot(int x){access(x);splay(x);rev[x]^=1;}// 让x变成根
inline void cut(int x,int y){makeroot(x);access(y);splay(y);f[x]=c[y][0]=0;pushup(y);}
inline void link(int x,int y){makeroot(x);f[x]=y;}
inline void cut2(int i){
makeroot(X[i]);
if(treeroot(Y[i])!=X[i]) return;
cut(X[i],n+i),cut(Y[i],n+i),e.erase(i);
}
inline void link2(int i){
makeroot(X[i]);
if(treeroot(Y[i])==X[i]) {// access(y) splay(y)
int p=t[Y[i]]-n;
if(W[p]>=W[i]) return;// 这个非常重要
cut(X[p],n+p),cut(Y[p],n+p),e.erase(p);
}
link(X[i],n+i),link(Y[i],n+i),e.insert(i);
}
}g;

const int E=2e5+555;
struct edge{int u,v,w;}e[E];

int read(){int x;scanf("%d",&x);return x;}
int main(){
g.n=read(),g.m=read();
for(int i=1;i<=g.m;++i) e[i].u=read(),e[i].v=read(),e[i].w=read();
sort(e+1,e+1+g.m,[](edge&a,edge&b){return a.w<b.w;});
for(int i=1;i<=g.m;++i) g.X[i]=e[i].u,g.Y[i]=e[i].v,g.W[i]=e[i].w;
g.ini();
int ans=1e9;
for(int i=1;i<=g.m;++i){
if(e[i].u==e[i].v) continue;
g.link2(i);
if(g.e.size()==g.n-1) ans=min(ans,g.W[*g.e.rbegin()]-g.W[*g.e.begin()]);
}
cout<<ans<<endl;
}

k度限制最小生成树

在最小生成树的要求下,多一个条件: 有一个定点的度数不能超过k。
k度限制最小生成树与k-1度限制最小生成树最多有一条边的区别。
时间复杂度\(O(ElgE+kV)\) k度限制最小生成树
k度限制最小生成树代码
poj1639view raw
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
//poj1639-k度限制最小生成树
// #include <bits/stdc++.h>
#include <algorithm>
#include <iostream>
#include <map>
#include <vector>
#include <cstdio>
using namespace std;
typedef pair<int, int> pii;
typedef map<int, int>::iterator IT;

const int V = 1e4, E = 1e5;
struct edge {
int u, v, w;
bool operator<(const edge& rhs) const { return w < rhs.w; }
} e[E], mxway[V];
map<int, int> g[V];
int lens[V];
int f[V];
int find(int u) { return u == f[u] ? u : f[u] = find(f[u]); }
void addedge(int u, int v, int w) {
if (g[u].find(u) != g[u].end()) w = min(w, g[u][v]);
g[u][v] = g[v][u] = w;
}
void eraedge(int u, int v) { g[u].erase(v), g[v].erase(u); }

void dfs(int u, int father) {
// for (pii x : g[u]) {
for (IT it = g[u].begin(); it != g[u].end(); ++it) {
pii x = *it;
if (x.first == father) continue;
if (x.second > mxway[u].w)
mxway[x.first] = edge{u, x.first, x.second};
else
mxway[x.first] = mxway[u];
dfs(x.first, u);
}
}

int klimitmst(int n, int m, int k) {
int res = 0;
// 求最小森林
sort(e + 1, e + m + 1);
for (int i = 1; i <= n; ++i) f[i] = i, lens[i] = 2e9;
for (int i = 1; i <= m; ++i) {
if (e[i].u > e[i].v) swap(e[i].u, e[i].v);
if (e[i].u == 1 || find(e[i].u) == find(e[i].v)) continue;
addedge(e[i].u, e[i].v, e[i].w);
f[find(e[i].u)] = find(e[i].v);
res += e[i].w;
}
// 求最小k0限制生成
int k0 = 0;
for (int i = 1; i <= m; ++i) {
if (e[i].u == 1) {
if (find(e[i].u) == find(e[i].v)) {
lens[e[i].v] = min(lens[e[i].v], e[i].w);
} else {
addedge(e[i].u, e[i].v, e[i].w);
f[find(e[i].u)] = find(e[i].v);
res += e[i].w;
k0++;
}
}
}
// 求最小k限制生成树
for (k0++; k0 <= k; k0++) {
for (int i = 1; i <= n; ++i) mxway[i].w = -1;
for (IT it = g[1].begin(); it != g[1].end(); ++it) dfs(it->first, 1);
// for (pii x : g[1]) dfs(x.first, 1);
int p = 2;
for (int i = 3; i <= n; ++i) {
if (lens[i] != 2e9 && lens[i] - mxway[i].w < lens[p] - mxway[p].w) p = i;
}
// cout << lens[p] << " " << mxway[p].w << endl;
if (lens[p] - mxway[p].w >= 0) break;
eraedge(mxway[p].u, mxway[p].v);
addedge(1, p, lens[p]);
res += lens[p] - mxway[p].w;
lens[p] = 2e9;
}
return res;
}

int read() {
int x;
scanf("%d", &x);
return x;
}

int main() {
// int n = read(), m = read(), k = read();
// for (int i = 1; i <= m; ++i)
// e[i].u = read(), e[i].v = read(), e[i].w = read();
// cout << klimitmst(n, m, k) << endl;
int n = 0, m = read();
map<string, int> id;
id["Park"] = ++n;
for (int i = 1; i <= m; ++i) {
string s1, s2;
cin >> s1 >> s2;
if (id.find(s1) == id.end()) id[s1] = ++n;
if (id.find(s2) == id.end()) id[s2] = ++n;
e[i] = edge{id[s1], id[s2], read()};
}
cout <<"Total miles driven: "<< klimitmst(n, m, read()) << endl;
}

最小直径生成树

给无向连通图,求一个直径最小的生成树。 以图的绝对中心为根的最短路树,是一个最小直径生成树。先用floyd求多源最短路,然后对每一条边,假设绝对中心在此边上,求出该绝对中心的偏心率,可以考虑从大到小枚举最短路闭包来实现,汇总得到绝对中心,最终以绝对中心为根,求最短路树。 时间复杂度\(O(n^3)\)
最小直径生成树代码
spoj1479view raw
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
//spoj1479-最小直径生成树
#include <bits/stdc++.h>
#define rep(i, j, k) for (int i = j; i <= int(k); ++i)
using namespace std;
typedef pair<int, int> pii;
struct MDST {
static const int V = 210;
int d[V][V], g[V][V], vis[V], rk[V][V], tocen[V], n;
void init(int _n) { // O(n^2)
n = _n;
rep(i, 1, n) {
rep(j, 1, n) g[i][j] = 1e9;
g[i][i] = 0;
}
}
void addedge(int u, int v, int w) {
g[u][v] = g[v][u] = min(g[u][v], 2 * w);
} //将边权扩大一倍
void centra(int& l, int& r, int& dl, int& dr) { // 图的绝对中心 O(n^3)
rep(i, 1, n) rep(j, 1, n) d[i][j] = g[i][j];
rep(k, 1, n) rep(i, 1, n) rep(j, 1, n) d[i][j] =
min(d[i][j], d[i][k] + d[k][j]);

rep(i, 1, n) {
rep(j, 1, n) rk[i][j] = j;
rep(j, 1, n) rep(k, j + 1, n) if (d[i][rk[i][j]] > d[i][rk[i][k]])
swap(rk[i][j], rk[i][k]);
}
int ans = 1e9;
rep(u, 1, n) {
if (d[u][rk[u][n]] * 2 < ans) ans = d[u][rk[u][n]] * 2, l = r = u;
rep(v, 1, n) if (g[u][v] != 1e9) {
for (int ii = n, mx = 0; ii >= 1; ii--) {
int i = rk[u][ii];
if (abs(mx - d[u][i]) < g[u][v]) {//两边相差小于边权,才能假设绝对中心在这条边上
int t = mx + d[u][i] + g[u][v];
if (t < ans) {
ans = t, l = u, r = v;
dl = t / 2 - d[u][i];
dr = g[u][v] - dl;
}
}
mx = max(mx, d[v][i]);
}
}
}
cout << ans / 2 << endl;
}
void spt() { // O(n^2)
int l, r, dl, dr;
centra(l, r, dl, dr);
rep(i, 1, n) vis[i] = 0, tocen[i] = min(d[l][i] + dl, d[r][i] + dr);
rep(i, 1, n) rep(j, 1, n) { // j -> i
if (i != l && i != r && i != j && tocen[j] + g[j][i] == tocen[i]) {
cout << min(i, j) << " " << max(i, j) << endl;
break;
}
}
cout << min(l, r) << " " << max(l, r) << endl;
}
} g;
int main() {
int n, m;
scanf("%d%d", &n, &m);
g.init(n);
rep(i, 1, m) {
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g.addedge(u, v, w);
}
g.spt();
}

最小比值生成树

有一个无向带权图(权为正数),每条边有权\(x_i\)和权\(y_i\),需要求出一个生成树T,记\(\begin{aligned}X=\sum_{i\in T}x_i,Y=\sum_{i\in T}y_i\end{aligned}\),要求最小化比值\(\frac{X}{Y}\). 我们设\(r=\frac{X}{Y}\)则有\(rY-X=0\)我们定义函数\(f(r)=rY-X\),为当以\(ry_i-x_i\)作为权值的时候的最大生成树的值,这里显然f(r)关于r单调增,当我们找到一个r使得f(r)等于0的时候,r就是我们分数规划要的答案。 时间复杂度\(O(lgn)*O(MST)\)
最小比值生成树代码
poj2728view raw
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
//poj2728-最小比值生成树
#include <algorithm>
#include <cmath>
#include <cstdio>
#include <iostream>
#include <assert.h>
using namespace std;
// 完全图用prim算法复杂度为O(n^2)
const int V = 1e3 + 5;
double A[V][V], B[V][V], W[V][V];
double totree[V];

int mst[V];
int x[V], y[V], h[V];

int main() {
int n;
while (scanf("%d", &n), n) {
for (int i = 1; i <= n; i++) scanf("%d%d%d", x + i, y + i, h + i);
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
double dx = x[i] - x[j], dy = y[i] - y[j], dh = h[i] - h[j];
A[i][j] = fabs(dh);
B[i][j] = sqrt(dx * dx + dy * dy);
}
}
double l = 0, r = 100;
while (r - l > 1e-8) {// 二分
double mid = (l + r) / 2;
for (int i = 1; i <= n; i++)
for (int j = 1; j <= n; j++) W[i][j] = mid * B[i][j] - A[i][j];// 分数规划

int s = 1;
for (int i = 1; i <= n; i++) mst[i] = 0, totree[i] = W[s][i]; // 距离
mst[s] = 1;
double sum = 0;
for (int t = 2; t <= n; t++) {
int u = -1;
for (int i = 1; i <= n; i++)
if (mst[i] == 0 && (u == -1 || totree[u] < totree[i])) u = i;
mst[u]=1;
sum+=totree[u];// 取出最远的点,求最大生成树
for (int i = 1; i <= n; i++)
if(mst[i]==0) totree[i] = max(totree[i], W[u][i]);//只有不在生成树中的点才被松弛
}// 生成树[?...sum] 与比值保持一致的单调性

sum > 0 ? r = mid : l = mid;// sum 大于0 则比值可以下降,则下移上界,反之上移下届
}
printf("%.3f\n", l);
}
}

name

Easy Math Problem ### descirption One day, Touma Kazusa encountered a easy math problem. Given n and k, she need to calculate the following sum modulo \(1e9+7\).
\[∑_{i=1}^n∑^n_{j=1}gcd(i,j)^klcm(i,j)\[gcd(i,j)∈prime\]\%(1e9+7) \] However, as a poor student, Kazusa obviously did not, so Touma Kazusa went to ask Kitahara Haruki. But Kitahara Haruki is too busy, in order to prove that he is a skilled man, so he threw this problem to you. Can you answer this easy math problem quickly?

input

There are multiple test cases.\((T=5)\) The first line of the input contains an integer\(T\), indicating the number of test cases. For each test case:
There are only two positive integers n and k which are separated by spaces.
\(1≤n≤1e10\) \(1≤k≤100\) ### output An integer representing your answer. ### sample input 1 10 2 ### sample output 2829 ### toturial \[ \begin{aligned} &\sum_{i=1}^n\sum_{j=1}^n i*j*gcd(i,j)^{k-1} gcd is prime \\=&\sum_{d\in prime} \sum_{i=1}^n\sum_{j=1}^nijd^{k-1}[gcd(i,j)=d] \\=&\sum_{d\in prime} \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ijd^{k+1}[gcd(i,j)=1] \\=&\sum_{d\in prime}d^{k+1} \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}ij[gcd(i,j)=1] \\=&\sum_{d\in prime}d^{k+1} \sum_{j=1}^{\lfloor\frac{n}{d}\rfloor}j^2\phi(j) \end{aligned} \] 我们可以对n分块了,前面可以min25筛 \(\begin{aligned}f(j)=j^2\phi(j)\end{aligned}\) \(\begin{aligned}g(j)=j^2\end{aligned}\) \(\begin{aligned}f\ast g(j)=\sum_{i|j}i^2\phi(i)(\frac{j}{i})^2=j^2\sum_{i|j}\phi(i)=j^2(\phi\ast 1)(j)=j^3\end{aligned}\) 于是后面可以杜教筛 ### 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
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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

// 模意义
const ll mod=1e9+7;
ll qpow(ll a,ll b){
assert(a<mod);
ll res=1;
while(b){
if(b&1) res=res*a%mod;
a=a*a%mod;
b>>=1;
}return res;
}
const ll inv2=qpow(2,mod-2),inv3=qpow(3,mod-2);
inline ll reduce(ll x){return x<0?x+mod:x;}
inline ll A(ll a,ll b){assert(a<mod&&b<mod); return reduce(a+b-mod);}
inline ll M(ll a,ll b){assert(a<2*mod&&b<2*mod); return a*b%mod;}
inline ll M(ll a,ll b,ll c){return M(M(a,b),c);}

//线性筛
// 3e7 int = 120mb
const ll maxn=2.5e7;
bitset<maxn>vis;
int siiphi[maxn];
ll p[1565927+100];
void f_ini(){
siiphi[1]=1;
for (ll i=2;i<maxn;i++){
if(!vis[i]) p[++p[0]]=i,siiphi[i]=i-1;
for (ll j=1;i*p[j]<maxn;j++){
vis[i*p[j]]=true;
if(i%p[j])siiphi[i*p[j]]=siiphi[i]*(p[j]-1);//由积性函数性质推
else{siiphi[i*p[j]]=siiphi[i]*p[j];break;}
}
}
for(ll i=1;i<maxn;i++) siiphi[i]=A(siiphi[i-1],M(i,i,siiphi[i]));
}

// 分块
const ll sqr=3e5;
ll id1[sqr],id2[sqr],w[sqr],idn,idm;// w[x] 第几大的分块值是多少
inline ll& id(ll x){return x<sqr?id1[x]:id2[idn/x];}//返回x是第几大的整除分块值
void ini(ll n){
idn=n;idm=0;
for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
id(n/l)=++idm;
w[idm]=n/l;
}
}

namespace min25shai{
ll g[sqr],sp[sqr];
ll getsum(ll x,ll n){// O(n) n次多项式有n+1项 y[0]...y[n] -> y[x]
static ll prepre[1000],suf[1000],r[1000]={1,1},y[1000],*pre=prepre+1;
if(y[999]!=++n) {//这里非常重要
y[999]=n;
for(ll i=1;i<=n;i++) y[i]=A(y[i-1],qpow(i,n-1));
for(ll i=2;i<=n;i++) r[i]=M(mod-mod/i,r[mod%i]);
for(ll i=2;i<=n;i++) r[i]=M(r[i],r[i-1]);
}
pre[-1]=suf[n+1]=1;
for(ll i=0;i<=n;++i) pre[i]=M(pre[i-1],x%mod-i+mod);//这个地方爆掉了
for(ll i=n;i>=0;i--) suf[i]=M(suf[i+1],i-x%mod+mod);//这个地方爆掉了
ll b=0;
for(ll i=0;i<=n;++i) {
ll up=M(pre[i-1],suf[i+1]);
ll down=M(r[i],r[n-i]);
b=A(b,M(y[i],up,down));
}
return b;
}
void min25(ll*g,ll n,ll k,ll(*f)(ll,ll),ll(*s)(ll,ll)){
for(ll i=1;i<=idm;++i) g[i]=A(s(w[i],k),mod-1);
for(ll j=1;p[j]*p[j]<=n;j++){
ll t=f(p[j],k);
sp[j]=A(sp[j-1],t);
for(ll i=1;w[i]>=p[j]*p[j];++i) g[i]=A(g[i],M(sp[j-1]-g[id(w[i]/p[j])]+mod,t));
// w[i]从大到小 当i等于m的时候 w[i]>=p[j]*p[j]恒不成立
}
}
}

namespace dujiaoshai{
// g(1)S(n)=(1≤i≤n)h(i)+(2≤d≤n)g(d)S(n/d)
// f(n)=n*n*phi(n)
// g(n)=n*n
// h(n)=n*n*n
ll s[sqr];// 前缀和
inline ll s1(ll n){return M(n,n+1,inv2);}
inline ll s2(ll n){return M(s1(n),2*n+1,inv3);}
inline ll s3(ll n){return M(s1(n),s1(n));}
void ini(){for(ll i=1;i<=idm;i++)s[i]=0;}
ll dujiao(ll n){
if(n<maxn) return siiphi[n];
if(s[id(n)]!=0) return s[id(n)];
s[id(n)]=s3(n%mod);
for(ll l=2,r;l<=n;l=r+1){
r=n/(n/l);
s[id(n)]-=(s2(r%mod)-s2((l-1)%mod))*dujiao(n/l)%mod;
}
return s[id(n)]=(s[id(n)]%mod+mod)%mod;
}
}

ll solve(ll n,ll k){
ini(n);
dujiaoshai::ini();
#define F(M) [](ll n,ll k){return ll(M);}
min25shai::min25(min25shai::g,n,k+1,F(qpow(n%mod,k)),F(min25shai::getsum(n,k)));
#undef F
ll res=0;
for(ll l=1,r;l<=n;l=r+1){
r=n/(n/l);
ll t1=dujiaoshai::dujiao(n/l);
ll t2=min25shai::g[id(r)];
if(l!=1) t2+=mod-min25shai::g[id(l-1)];
res+=M(t1,t2);
}
return res%mod;
}

inline ll read(){ll x;cin>>x;return x;}
int main() {
f_ini();
for(ll t=read();t>=1;t--){
ll n=read(),k=read();
cout<<solve(n,k)<<endl;
}
}

min25筛是什么

min25筛是一种筛法,他能以亚线性的时间复杂度筛出一类函数的前缀和

定义一部分符号

\(M(x) x\gt1\)代表\(x\)的最小质因子

我们再设\(P_j\)为第\(j\)小的质数, \(P_1=2,P_2=3,P_3=5...\)

先看最简单的第一类函数

\[ \begin{aligned} f(x)=\left\{\begin{matrix} x^k&x\in primes\\ 0&x \notin primes \end{matrix}\right. \end{aligned} \] 对于这个函数我们可以利用min25筛来达到\(O(\frac{n^\frac{3}{4}}{lg(n)})\)的时间复杂度,我们没有办法直接求这个函数的前缀和,但是我们可以另外设一个相对好求的函数\(h(x)=x^k\),通过h来求f,因为\(\begin{aligned}\sum_{i=2}^nh(i)[i\in primes]=\sum_{i=2}^nf(i)[i\in primes]\end{aligned}\)

\(\begin{aligned} g(n,j)=\sum_{i=2}^nh(i)[i \in primes或M(i)\gt P_j] \end{aligned}\)

即 i要么是质数,要么i的最小质因子大于\(P_j\)。对g函数的理解,我们甚至可以回忆埃式筛,每一轮都会选择一个最小的质数,然后筛掉他的所有倍数,最终唯有所有的质数不会被筛掉,我们的这个函数就是那些没有被筛掉的数的函数值的和。
\[ \begin{aligned} g(n,j)=\left\{\begin{matrix} g(n,j-1)-x&M(n)\le P_j\\ g(n,j-1)& M(n)\gt P_j \end{matrix}\right. \end{aligned} \] x处是什么呢?第j-1次的结果和第j次的结果有什么不同呢?第j次埃式筛筛掉了\(P_j\)的倍数,他们的最小质因子都是\(P_j\),所以
\[ \begin{aligned} x&=\sum_{i=2P_j}^nh(i)[M(i)=P_j] \\&=\sum_{i=2}^{\frac{n}{P_j}}h(iP_j)[M(iP_j)=P_j] \\&=h(P_j)\sum_{i=2}^{\frac{n}{P_j}}h(i)[M(i)\ge P_j] \\&=h(P_j)\sum_{i=2}^{\frac{n}{P_j}}h(i)[M(i)\gt P_{j-1}] \\&=h(P_j)(\sum_{i=2}^{\frac{n}{P_j}}h(i)[M(i)\gt P_{j-1}或i \in primes]-\sum_{i=1}^{j-1}h(P_i)) \\&=h(P_j)(g(\frac{n}{P_j},j-1)-\sum_{i=1}^{j-1}h(P_i)) \end{aligned} \]

最后就成了这个
\[ \begin{aligned} g(n,j)=\left\{\begin{matrix} g(n,j-1)-h(P_j)(g(\frac{n}{P_j},j-1)-\sum_{i=1}^{j-1}h(P_i))&M(n)\le P_j\\ g(n,j-1)& M(n)\gt P_j \end{matrix}\right. \end{aligned} \] 到这里就已经可以记忆化递归解决了,但是递归比较慢,我们考虑把它变成非递归,我们观察这个式子。

我们发现我们可以先枚举j因为\(g(n,j)\)是由\(g(?,j-1)\)推导过来的,然后从大到小枚举n,来更新数组,又因为n的前一项可能与\(\frac{n}{P_j}\)有关,所以我们可以把他们都用map映射一下,再进一步分析,根据整除分块的传递性,\(\frac{\frac{a}{b}}{c}=\frac{a}{bc}\)我们可以得出所有\(g(x,y)\)中x构成的集合,恰好是集合\(\{x|x=\frac{n}{t},t\in [1,n]\}\),最后预处理一下\(\sum^{j-1}_{i=1}h(P_i)\)即可,对于整除分块的映射,我们又可以加速到O(1)此处不做过多分析。

最后我们得到了这个\(O(\frac{n^{\frac{3}{4}}}{lg(n)})\)算法

再看复杂一些的第二类函数

第二类函数是抽象的积性函数\(f\)

如果我们能够通过一些方法求出\(\sum_{i=1}^{n}f(P_i)\)\(f(P_i^k)\),那么我们就能够很简单得推出f的前缀和。

对于\(\sum_{i=1}^{n}f(P_i)\), 我们这样来求,比如说f(x)在x是一个质数的时候能表示为某个简单多项式,那么我们就可以通过将多项式函数看做某些幂函数的线形组合,先求出幂函数各自的质数前缀和,然后相加就可以得到f的质数前缀和。

而对于另外一个\(f(P_i^k)\)则需要通过函数的定义来求了。

现在假设我们已经预处理出了\(\sum_{i=1}^xf(P_i)(x \in n的数论分块即x=\frac{n}{?})其实就是g(x,\infty)\)

我们设\(\begin{aligned}S(n,j)=\sum_{i=2}^nf(i)[M(i)\ge P_j]\end{aligned}\)注意和\(g(n,j)\)对比。
\[ \begin{aligned} &S(n,j) \\=&\sum_{i=j}^{P_i\le n}f(P_i)+f(P_i)S(\frac{n}{P_j},j+1)+f(P_i^2)S(\frac{n}{P_i^2},j+1)+f(P_i^3)S(\frac{n}{P_i^3},j+1)+... \end{aligned} \] 这里已经可以了,第一项可以通过两个前缀和相减得到,后边的递归。这就是min25筛的灵魂所在。

我们现在好好来分析一下这个递归式子。我们发现第一项是最好求的,就是第一类函数,但是后边的几项必须要求积性函数。这也是min25筛只能对积性函数起作用的地方。

min25筛能处理更多的函数吗?

我们暂定这些函数为f,显然我们必须要能够求出g和s,这就是min25筛,对于g,这里不对此作过多分析,没有这个必要,我们假定都是一类与幂函数线形组合有关的函数,抑或是某几项与幂函数有关,反正只要能够找到完全积性函数h在质数自变量和f函数存在相等关系即可。s的话,第一项简单差分,后边的看似要求f是积性函数,其实不然,我们仔细分析,其实他要求的是这样的要求: 假定y是x的最小质因子,\(z=y^k且z|x且k最大\),我们只要能够通过\(f(z)和f(\frac{x}{z})\)这两项能够推出f(x)即可,这里并没有强制要求\(f(x)=f(z)*f(\frac{x}{z})即f(x)=f(M(x))\)。举个例子,若\(f(x)=f(z)=f(y)=y\),我们也是可以求的。

贴一个求\(f(a^b)=a \bigotimes b\)\(f(x)=M(x)\)的代码

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
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

const ll sqr=2e5+10;// 2sqrt(n)
ll p[sqr],np[sqr]={1,1};
void prime_ini(){// 素数不可能筛到longlong范围
for(int i=2; i<sqr; i++){
if(!np[i])p[++p[0]]=i;//把质数收录
for(int j=1; 1ll*i*p[j]<sqr; j++){
np[i*p[j]]=1;//对每一个数字枚举他的最小因子
if(i%p[j]==0)break;//在往后的话就不是最小因子了
}
}
}

const ll mod=1e9+7;
ll w[sqr],g[sqr][2],sp[sqr][2],id1[sqr],id2[sqr],mpn;
inline ll& mp(ll x){return x<sqr?id1[x]:id2[mpn/x];}
void min25(ll n){// 计算质数位置之和的时候 只用到了f(x,1) 和 oddsum(x)
mpn=n;
ll m=0;
for(ll l=1,r;l<=n;l=r+1){// i从小到大 n/i从到小
r=n/(n/l);
mp(n/l)=++m;
w[m]=n/l;
g[m][0]=(w[m]-1)%mod;// f(x)=1, s(x)=x
g[m][1]=(__int128(w[m])*(w[m]+1)/2-1)%mod; // f(x)=x, s(x)=x(x+1)/2 这里的int128非常关键,因为n是1e10级别的
}//assert(w[m]==1);
for(ll j=1;p[j]*p[j]<=n;j++){
sp[j][0]=sp[j-1][0]+1;// f(x)=1
sp[j][1]=(sp[j-1][1]+p[j])%mod;// f(x)=x
for(ll i=1;w[i]>=p[j]*p[j];++i){// w[i]从大到小 当i等于m的时候 w[i]>=p[j]*p[j]恒不成立
g[i][0]-=(g[mp(w[i]/p[j])][0]-sp[j-1][0])*1;// f(x)=1
g[i][1]-=(g[mp(w[i]/p[j])][1]-sp[j-1][1])*p[j];// f(x)=x
g[i][0]=(g[i][0]%mod+mod)%mod;
g[i][1]=(g[i][1]%mod+mod)%mod;
}
}
}

// f(pow(a,b))=a^b f为积性函数
inline ll f(ll a,ll b){return a^b;} // 当且仅当a是一个素数
ll s(ll n,ll j){// sum of f(x) x<=n minfac(x)>=p[j]
ll res=(g[mp(n)][1]-g[mp(n)][0])-(sp[j-1][1]-sp[j-1][0])+2*mod;// 减掉p[j]前面的质数 : [p[j],n]上的质数的函数的和
if(n>=2&&j==1) res+=2;
for(ll k=j;p[k]*p[k]<=n;k++){// 枚举的最小质因子
for(ll x=p[k],e=1;x*p[k]<=n;x*=p[k],e++){//枚举该因子出现次数
res+=s(n/x,k+1)*f(p[k],e)%mod+f(p[k],e+1);// 每次增加2mod res不可能超过 long long
}
}
return res%mod;
}

// f(x)=minfac(x) f不为积性函数 但我们用积性函数来做他
typedef pair<ll,ll> pll;
pll s2(ll n,ll j){//
ll res1=g[mp(n)][0]-sp[j-1][0]+2*mod;
ll res2=g[mp(n)][1]-sp[j-1][1]+2*mod;// 减掉p[j]前面的质数 : [p[j],n]上的质数的函数的和
for(ll k=j;p[k]*p[k]<=n;k++){// 枚举的最小质因子
for(ll x=p[k],e=1;x*p[k]<=n;x*=p[k],e++){//枚举该因子出现次数
pll tmp=s2(n/x,k+1);
res1+=tmp.first*1%mod+1;
res2+=tmp.first*p[k]%mod+p[k];// 每次增加2mod res不可能超过 long long
}
}
return pll(res1%mod,res2%mod);
}

int main() {
prime_ini();
ll n;
while(cin>>n){
min25(n);
if(n==1) cout<<1<<endl;
else cout<<(s(n,1)+1)%mod<<endl;
}
}

中间结论

\[ f_n = \sum_{i=0}^n (-1)^i {n \choose i} g_i \Leftrightarrow g_n = \sum_{i=0}^n (-1)^i {n \choose i} f_i \]

\[ f_n = \sum_{i=0}^n {n \choose i} g_i \Leftrightarrow g_n = \sum_{i=0}^n (-1)^{n-i} {n \choose i} f_i \]

杜教筛

\[ g(1)\sum_{i=1}^nf(i)=\sum_{i=1}^{n}(f*g)(i)-\sum_{d=2}^{n}g(d) \sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i) \]

数论变换

\[ \begin{aligned} &e(n)=[n=1] \\&id(n)=n \\&I(n)=1 \\&d(n)=\sum_{x|n}1 (就是因子个数) \\&σ(n)=\sum_{x|n}x (就是因子和) \\&\mu(n) 莫比乌斯函数 \\&\phi(n) 欧拉函数 \\&I\ast I=d \\&I\ast id=σ \\&I\ast \phi=id \\&I\ast \mu=e \end{aligned} \]

自变量互质的前缀和函数分析

\[ \begin{aligned} &\sum_{i=1}^n{f(i)[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i*d)}\\ &\sum_{i=1}^n{[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)\lfloor\frac{n}{d}\rfloor}\\ &\sum_{i=1}^n{[gcd(i,n)=1]}=\phi(n)\\ &\sum_{i=1}^n{i[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)d\frac{\lfloor\frac{n}{d}\rfloor(\lfloor\frac{n}{d}\rfloor+1)}{2}}\\ &\sum_{i=1}^n{i[gcd(i,n)=1]}=\frac{n}{2}(\phi(n)+e(n))\\ &\sum_{i=1}^n\sum_{j=1}^nij[gcd(i,j)=1]=\sum_{j=1}^nj^2\phi(j)\\ \end{aligned} \]

###name 种花家的零食

###descirption 在很久以前,有一颗蓝星,蓝星上有一个种花家。 种花家有1到n共n包零食,同时种花家的兔子又有1到n共n个朋友(比如毛熊,鹰酱,脚盆鸡等)。 昨天,兔子的n个朋友都到他家来玩了。他的n个朋友瓜分了他的n包零食,每个人都恰好吃了一包零食,没有两个人吃了同一包零食。 兔子发现,第i个朋友吃第j包零食能获得的愉悦值是\(i\mod j\)。 今天,兔子想回忆起每个朋友吃的是哪包零食,他想不起来了,但是他却记得了所有人的愉悦值之和s。于是,兔子找上了你,请你构造出一种可能的方案。 由于兔子记忆力不好,他有可能记错了s,所以可能会存在无解的情况。

###input 一行两个整数\(n(1\leq n\leq 10^6)\)\(s(1\leq s\leq10^{18})\)

###output 如果不存在满足条件的方案,输出一行-1。 否则输出n行,每行一个整数,第i行的整数表示第i个朋友吃的是哪包零食。

###sample input 5 7

###sample output 1 4 3 5 2

###sample input 5 100

###sample output -1

###toturial 分析出上界为\(\frac{n(n-1)}{2}\)后,分类讨论,用数学归纳法证明特例即可

###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
#include<bits/stdc++.h>
#define rep(i,j,k) for(ll i=(j);i<=(k);++i)

using namespace std;
typedef long long ll;

const int maxn=1e6+10;
ll ans[maxn];
vector<ll>vec;
void solve(ll n,ll s){
if(n==1) {
vec.push_back(1);
return;
}
// choose n-1
s-=n-1;
n--;
if(s!=n*(n-1)/2-1&&s!=2&&s!=0&&s>0){
vec.push_back(n);
solve(n,s);
return;
}
n++;
s+=n-1;


solve(n-1,s);
}

int main() {
ll n,s;
cin>>n>>s;
if(s>n*(n-1)/2) ans[0]=-1;
else if(n==1){
if(s==0) ans[1]=1;
else ans[0]=-1;
}
else if(n==2){
if(s==0) ans[1]=1,ans[2]=2;
else if(s==1) ans[1]=2,ans[2]=1;
else ans[0]=-1;
}
else if(s==0) rep(i,1,n) ans[i]=i;
else if(s==2) {
ans[1]=3;;
ans[2]=1;
ans[3]=2;
rep(i,4,n) ans[i]=i;
}
else if(s==n*(n-1)/2-1){
if(n%2==0){
ans[1]=1;
rep(i,2,n-1) ans[i]=i+1;
ans[n]=2;
}
else{
ans[1]=3;
ans[2]=1;
rep(i,3,n-1) ans[i]=i+1;
ans[n]=2;
}
}
else {
vec.push_back(n);
solve(n,s);
rep(i,1,n) ans[i]=i;
int sz=vec.size();
// rep(i,0,sz-1) cout<<vec[i]<<endl;
// cout<<endl;
rep(i,0,sz-1)ans[vec[i]]=vec[(i-1+sz)%sz];
}
if(ans[0]==-1) printf("-1\n");
else {
ll ss=0;
rep(i,1,n) ss+=i%ans[i],printf("%lld\n",ans[i]);
assert(ss==s);
// cout<<s<<endl;
}
}

有这样一类问题,他们的形式常常是这个样子

\[ \begin{aligned} \sum_{i=1}^n{f(i)[gcd(i,j)=1]} \end{aligned} \]

我们来对他进行变形

\[ \begin{aligned} &\sum_{i=1}^n{f(i)[gcd(i,j)=1]}\\ =&\sum_{i=1}^n{f(i)e(gcd(i,j))}\\ =&\sum_{i=1}^n{f(i)(\mu*1)(gcd(i,j)}\\ =&\sum_{i=1}^n{f(i)\sum_{d|gcd(i,j)}\mu(d)}\\ =&\sum_{i=1}^n{f(i)\sum_{d|i,d|j}\mu(d)}\\ =&\sum_{d|j}{\mu(d)\sum_{d|i,1<=i<=n}f(i)}\\ =&\sum_{d|j}{\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i*d)}\\ \end{aligned} \]

如果\(f(i)=1\)

\[ \begin{aligned} \sum_{i=1}^n{[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)\lfloor\frac{n}{d}\rfloor}\\ \end{aligned} \] ## 更加特殊的 如果\(j=n\)

\[ \begin{aligned} \sum_{i=1}^n{[gcd(i,n)=1]}=\sum_{d|j}{\mu(d)\frac{n}{d}}=(\mu*id)(n)=\phi(n)\\ \end{aligned} \]

如果\(f(i)=i\)

\[ \begin{aligned} &\sum_{i=1}^n{i[gcd(i,j)=1]}\\ =&\sum_{d|j}{\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}i*d}\\ =&\sum_{d|j}{\mu(d)d\frac{\lfloor\frac{n}{d}\rfloor(\lfloor\frac{n}{d}\rfloor+1)}{2}}\\ \end{aligned} \] ## 更加特殊的 如果\(j=n\)

\[ \begin{aligned} &\sum_{i=1}^n{i[gcd(i,n)=1]}\\ =&\sum_{d|n}{\mu(d)d\frac{\frac{n}{d}(\frac{n}{d}+1)}{2}}\\ =&\frac{n}{2}\sum_{d|n}{\mu(d)(\frac{n}{d}+1)}\\ =&\frac{n}{2}(\sum_{d|n}{\mu(d)\frac{n}{d}}+\sum_{d|n}{\mu(d)})\\ =&\frac{n}{2}(\phi(n)+e(n))\\ \end{aligned} \]

总结

$ \[\begin{aligned} &\sum_{i=1}^n{f(i)[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}f(i*d)}\\ &\sum_{i=1}^n{[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)\lfloor\frac{n}{d}\rfloor}\\ &\sum_{i=1}^n{[gcd(i,n)=1]}=\phi(n)\\ &\sum_{i=1}^n{i[gcd(i,j)=1]}=\sum_{d|j}{\mu(d)d\frac{\lfloor\frac{n}{d}\rfloor(\lfloor\frac{n}{d}\rfloor+1)}{2}}\\ &\sum_{i=1}^n{i[gcd(i,n)=1]}=\frac{n}{2}(\phi(n)+e(n))\\ \end{aligned}\]

$

###name array

###descirption You are given an array \(a_1,a_2,...,a_n(∀i∈[1,n],1≤a_i≤n)\). Initially, each element of the array is unique.

Moreover, there are m instructions.

Each instruction is in one of the following two formats:

  1. (1,pos),indicating to change the value of \(a_{pos}\) to \(a_{pos}+10,000,000\);
  2. (2,r,k),indicating to ask the minimum value which is not equal to any \(a_i\) ( 1≤i≤r ) and not less than k.

Please print all results of the instructions in format 2.

###input The first line of the input contains an integer T(1≤T≤10), denoting the number of test cases.

In each test case, there are two integers n(1≤n≤100,000),m(1≤m≤100,000) in the first line, denoting the size of array a and the number of instructions.

In the second line, there are n distinct integers \(a_1,a_2,...,a_n (∀i∈[1,n],1≤a_i≤n)\),denoting the array. For the following m lines, each line is of format \((1,t_1) or (2,t_2,t_3)\). The parameters of each instruction are generated by such way :

For instructions in format 1 , we defined \(pos=t_1⊕LastAns\) . (It is promised that 1≤pos≤n)

For instructions in format 2 , we defined \(r=t_2⊕LastAns,k=t_3⊕LastAns\). (It is promised that 1≤r≤n,1≤k≤n )

(Note that ⊕ means the bitwise XOR operator. )

Before the first instruction of each test case, LastAns is equal to 0 .After each instruction in format 2, LastAns will be changed to the result of that instruction.

(∑n≤510,000,∑m≤510,000 )

###output For each instruction in format 2, output the answer in one line.

###sample input 3 5 9 4 3 1 2 5 2 1 1 2 2 2 2 6 7 2 1 3 2 6 3 2 0 4 1 5 2 3 7 2 4 3 10 6 1 2 4 6 3 5 9 10 7 8 2 7 2 1 2 2 0 5 2 11 10 1 3 2 3 2 10 10 9 7 5 3 4 10 6 2 1 8 1 10 2 8 9 1 12 2 15 15 1 12 2 1 3 1 9 1 12 2 2 2 1 9

###sample output 1 5 2 2 5 6 1 6 7 3 11 10 11 4 8 11

###hint note: After the generation procedure ,the instructions of the first test case are : 2 1 1, in format 2 and r=1 , k=1 2 3 3, in format 2 and r=3 , k=3 2 3 2, in format 2 and r=3 , k=2 2 3 1, in format 2 and r=3 , k=1 2 4 1, in format 2 and r=4 , k=1 2 5 1, in format 2 and r=5 , k=1 1 3 , in format 1 and pos=3 2 5 1, in format 2 and r=5 , k=1 2 5 2, in format 2 and r=5 , k=2

the instructions of the second test case are : 2 7 2, in format 2 and r=7 , k=2 1 5 , in format 1 and pos=5 2 7 2, in format 2 and r=7 , k=2 2 8 9, in format 2 and r=8 , k=9 1 8 , in format 1 and pos=8 2 8 9, in format 2 and r=8 , k=9

the instructions of the third test case are : 1 10 , in format 1 and pos=10 2 8 9 , in format 2 and r=8 , k=9 1 7 , in format 1 and pos=7 2 4 4 , in format 2 and r=4 , k=4 1 8 , in format 1 and pos=8 2 5 7 , in format 2 and r=5 , k=7 1 1 , in format 1 and pos=1 1 4 , in format 1 and pos=4 2 10 10, in format 2 and r=10 , k=10 1 2 , in format 1 and pos=2

###toturial1 先不考虑修改,若只有查询,我们发现每次都是前缀的查询,这里显然是可以使用主席树用log的复杂度完成的,然后我们考虑修改,我们发现修改等价于删除数字,那么这样一来,又因为每个数都是独一无二的,删除只会让答案变得更小,且恰好变成删掉的数字,我们可以尝试用一个集合记录所有删掉的数字,然后用lower_bound来查询,和主席树得到的答案取得最小值,就是真正的答案。证明过程很简单,分类证明即可。

###code1

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
// 主席树+set
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=int(k);++i)

inline int read(){int x;scanf("%d",&x);return x;}

const int maxn = 1e5+5;
int ls[maxn*20*1],rs[maxn*20*1],siz[maxn*20*1],tot,rt[maxn];//update用了几次,就要乘以多少
void update(int pre,int&u,int l,int r,int pos,int val){//把u按照pre复制,然后更新pos
u=++tot;
ls[u]=ls[pre];rs[u]=rs[pre];
siz[u]=siz[pre]+val;
if(l==r)return ;
int mid=(l+r)>>1;
if(pos<=mid) update(ls[pre],ls[u],l,mid,pos,val);
else update(rs[pre],rs[u],mid+1,r,pos,val);
}

int query(int u,int l,int r,int ql,int qr){
int mid=(l+r)>>1,res=1e9;
if(ql<=l&&r<=qr){
if(l==r)return siz[u]==0?l:1e9;
if(siz[ls[u]]!=mid-l+1) return query(ls[u],l,mid,ql,qr);
else return query(rs[u],mid+1,r,ql,qr);
}
if(ql<=mid)res=min(res,query(ls[u],l,mid,ql,qr));
if(res!=1e9)return res;
if(qr>=mid+1)res=min(res,query(rs[u],mid+1,r,ql,qr));
return res;
}

int a[maxn];
int main(){
int T=read();
rep(times,1,T){
tot=0;
set<int>se;
se.insert(1e9);
int n=read(),m=read();
rep(i,1,n) update(rt[i-1],rt[i],1,n+1,a[i]=read(),1);
int lastans=0;
rep(i,1,m){
if(read()==1) se.insert(a[read()^lastans]);
else{
int r=read()^lastans,k=read()^lastans;
printf("%d\n",lastans=min(*se.lower_bound(k),query(rt[r],1,n+1,k,n+1)));
}
}
}
}

###toturial2 逆向思维,反转键值,题目让我们在键区间[1,r]上找到最小的不小于k的值,我们反转后变成了在值区间[k,n+1]上找到值最小的键,其键不小于k,修改操作就成了把值所对的键修改为无穷大,这个问题用普通最值线段树很轻松就能解决

###code2

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
// 逆向思维 键值颠倒
#include<bits/stdc++.h>
using namespace std;
#define rep(i,j,k) for(int i=j;i<=int(k);++i)

inline int read(){int x;scanf("%d",&x);return x;}

#define ml ((l+r)>>1)
#define mr (ml+1)
const int maxn = 1e5+20;
int ls[maxn*2],rs[maxn*2],mx[maxn*2],a[maxn],pos[maxn],tot;//update用了几次,就要乘以多少
void build(int&u,int l,int r){
u=++tot;
if(l==r) {mx[u]=pos[l];return;}
build(ls[u],l,ml);
build(rs[u],mr,r);
mx[u]=max(mx[ls[u]],mx[rs[u]]);
}

void update(int&u,int l,int r,int q,int d){
if(l==r) {mx[u]=d;return;}
if(q<=ml) update(ls[u],l,ml,q,d);
else update(rs[u],mr,r,q,d);
mx[u]=max(mx[ls[u]],mx[rs[u]]);
}

int query(int u,int l,int r,int ql,int qr,int x){// >x
int ans=1e9;
if(ql<=l&&r<=qr){
if(mx[u]<=x) return 1e9;
if(l==r) return l;
ans=query(ls[u],l,ml,ql,qr,x);
if(ans!=1e9) return ans;
return query(rs[u],mr,r,ql,qr,x);
}
if(ml>=ql) ans=min(ans,query(ls[u],l,ml,ql,qr,x));
if(ans!=1e9) return ans;
if(mr<=qr) ans=min(ans,query(rs[u],mr,r,ql,qr,x));
return ans;
}

int main(){
int T=read();
rep(times,1,T){
tot=0;
int n=read(),m=read(),rt;
rep(i,1,n) a[i]=read(),pos[a[i]]=i;
a[n+1]=n+1,pos[n+1]=n+1;
build(rt,1,n+1);
int lastans=0;
rep(i,1,m){
if(read()==1) {
int val=a[read()^lastans];
update(rt,1,n+1,val,n+1);
// pos[val]=n+1;
}
else{
int r=read()^lastans,k=read()^lastans;
printf("%d\n",lastans=query(rt,1,n+1,k,n+1,r));
// rep(i,k,n+1) if(pos[i]>r) {cout<<" "<<i<<endl;lastans=i;break;}
}
}
}
}

###name

###descirption

###input

###output

###sample input

###sample output

###toturial 先来看一个简单的变形 \[ \begin{aligned} &\sum_{i=1}^{n}gcd(x,i)\\ =&\sum_{d|x}\sum_{i=1}^{n}[gcd(x,i)=d]\\ =&\sum_{d|x}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}[gcd(\frac{x}{d},i)=1]\\ =&\sum_{d|x}\sum_{i=1}^{\lfloor\frac{n}{d}\rfloor}\sum_{y|\frac{x}{d},y|i}\mu(y)\\ =&\sum_{y|x}\sum_{d|\frac{x}{y}}\sum_{y|i,i\leq\lfloor\frac{n}{d}\rfloor}\mu(y)\\ =&\sum_{y|x}\sum_{d|\frac{x}{y}}\frac{\lfloor\frac{n}{d}\rfloor}{y}\mu(y)\\ =&\sum{}\\ \end{aligned} \]

题目让我们求的东西是这个 \[ \begin{aligned} \sum_{i=1}^{n}gcd(\lfloor\sqrt[3]{i}\rfloor,i) \end{aligned} \]

###code

1
2
#include<bits/stdc++.h>
using namespace std;

###name String

###descirption Tom has a string containing only lowercase letters. He wants to choose a subsequence of the string whose length is k and lexicographical order is the smallest. It's simple and he solved it with ease. But Jerry, who likes to play with Tom, tells him that if he is able to find a lexicographically smallest subsequence satisfying following 26 constraints, he will not cause Tom trouble any more. The constraints are: the number of occurrences of the ith letter from a to z (indexed from 1 to 26) must in \([L_i,R_i]\). Tom gets dizzy, so he asks you for help.

###input The input contains multiple test cases. Process until the end of file. Each test case starts with a single line containing a string \(S(|S|≤10^5)\)and an integer k(1≤k≤|S|). Then 26 lines follow, each line two numbers$ L_i,R_i(0≤L_i≤R_i≤|S|)$. It's guaranteed that S consists of only lowercase letters, and \(∑|S|≤3×10^5\).

###output Output the answer string. If it doesn't exist, output −1.

###sample input aaabbb 3 0 3 2 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ###sample output abb

###toturial 遇到这种题一般要想到一位一位去构造,贪心的选择小的字母,从而构造出最小字典序,而这一步我们需要的是验证此字母是否合法。因为是在选子序列,所以我们只需要统计后缀是否满足要求即可,后缀中的字母都满足数量足够即可

###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
#include<bits/stdc++.h>
using namespace std;

#define rep(i,j,k) for(int i=(j);i<=(k);++i)
#define per(i,j,k) for(int i=(j);i>=(k);--i)

const int maxn=1e5+5;
char s[maxn];
int k;
int l[maxn],r[maxn];

int suf[maxn][26],ct[maxn][26];
char ans[maxn];

int main(){
while(~scanf("%s%d",s,&k)){
rep(i,0,25) scanf("%d%d",l+i,r+i);
int len=strlen(s);

rep(j,0,25) suf[len][j]=-1,ct[len][j]=0;
per(i,len-1,0){
rep(j,0,25) suf[i][j]=suf[i+1][j],ct[i][j]=ct[i+1][j];
suf[i][s[i]-'a']=i;
ct[i][s[i]-'a']++;
}

int cur=0;// no choose
rep(i,0,k-1){
rep(j,0,25){
if(suf[cur][j]!=-1) {
l[j]--,r[j]--;
int nex=suf[cur][j]+1;
int ok=1,nd=0;
for(int t=0;t<26;t++){
if((nex==len?0:ct[nex][t])<l[t]||r[t]<0) ok=0;
nd+=max(0,l[t]);
}
if(ok==1&&i+1+nd<=k){
ans[i]=j+'a';
cur=nex;
break;
}
l[j]++,r[j]++;
}
if(j==25){
printf("-1\n");
goto failed;
}
}
}
for(int i=0;i<k;i++) printf("%c",ans[i]);
printf("\n");

failed:;
}
}