【做题记录】csp2025-提高组生成树和笛卡尔树专题
A. [COCI2009-2010#7] SVEMIR
显然 boruvka。将所有点分别按照 \(x\),\(y\),\(z\) 排序,更新最小边。时间复杂度 \(O(n\log^2 n)\)。
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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5;
int n,fa[maxn],sz[maxn];
pii mne[maxn];
struct node{
int x,y,z,id;
}a[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i].x>>a[i].y>>a[i].z;
a[i].id=fa[i]=i,sz[i]=1;
}
ll ans=0;
for(;;){
// puts("666");
for(int i=1;i<=n;i++){
mne[i]=mp(INT_MAX,0);
}
sort(a+1,a+n+1,[](const node &p,const node &q){return p.x<q.x;});
for(int i=1,u,v,w;i<n;i++){
u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].x-a[i].x;
if(u!=v){
mne[u]=min(mne[u],mp(w,v));
mne[v]=min(mne[v],mp(w,u));
}
}
sort(a+1,a+n+1,[](const node &p,const node &q){return p.y<q.y;});
for(int i=1,u,v,w;i<n;i++){
u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].y-a[i].y;
if(u!=v){
mne[u]=min(mne[u],mp(w,v));
mne[v]=min(mne[v],mp(w,u));
}
}
sort(a+1,a+n+1,[](const node &p,const node &q){return p.z<q.z;});
for(int i=1,u,v,w;i<n;i++){
u=find(a[i].id),v=find(a[i+1].id),w=a[i+1].z-a[i].z;
if(u!=v){
mne[u]=min(mne[u],mp(w,v));
mne[v]=min(mne[v],mp(w,u));
}
}
bool flag=0;
for(int i=1;i<=n;i++){
if(find(i)==i&&mne[i].fir<INT_MAX&&find(i)!=find(mne[i].sec)){
flag=1;
merge(i,mne[i].sec);
ans+=mne[i].fir;
}
}
if(!flag){
break;
}
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
B. [SCOI2012] 滑雪
对于第一问,按照高度决定连边方向,跑个 bfs 即可。
对于第二问,显然 prim 可做。但是在优先队列内,需要按照点的高度从大到小为第一关键字,距离从小到大为第二关键字。原因是我们建的是一张有向图,有些边从较低的点向较高的点是更新不到的。而如果依次考虑由高向低的点,则一定可以保证每条边都被考虑到,且考虑到每个点时指向它的边一定已经被考虑完了。时间复杂度 \(O(m\log n)\)。
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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,maxm=1e6+5;
int n,m,a[maxn],fa[maxn],sz[maxn];
ll dis[maxn];
bool vis[maxn],viss[maxn];
vector<pii> e[maxn];
queue<int> q;
struct cmp{
il bool operator()(const pii &x,const pii &y)const{
if(a[x.sec]!=a[y.sec]){
return a[x.sec]<a[y.sec];
}
return x.fir>y.fir;
}
};
priority_queue<pii,vector<pii>,cmp> p;
struct node{
int u,v,w;
il bool operator<(const node &x)const{
return w<x.w;
}
}ed[maxm<<1];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
// cout<<cplx::usdmem();
cin>>n>>m;
for(int i=1;i<=n;i++){
cin>>a[i];
fa[i]=i,sz[i]=1;
}
for(int i=1,u,v,w;i<=m;i++){
cin>>u>>v>>w;
if(a[u]>=a[v]){
e[u].pb(mp(v,w));
}
if(a[v]>=a[u]){
e[v].pb(mp(u,w));
}
}
int ans1=0;
q.push(1),vis[1]=1;
while(q.size()){
int u=q.front();
ans1++,q.pop();
for(pii i:e[u]){
int v=i.fir;
if(!vis[v]){
q.push(v),vis[v]=1;
}
}
}
memset(dis,0x3f,sizeof dis);
dis[1]=0,p.push(mp(0,1));
while(p.size()){
int u=p.top().sec;
// cout<<u<<"\n";
p.pop(),viss[u]=1;
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(!vis[v]){
continue;
}
if(!viss[v]&&dis[v]>w){
dis[v]=w;
p.push(mp(w,v));
}
}
}
ll ans2=0;
for(int i=1;i<=n;i++){
if(vis[i]){
ans2+=dis[i];
}
}
cout<<ans1<<" "<<ans2;
return 0;
}
}
int main(){return asbt::main();}
C. [USACO01OPEN] Earthquake
其中 \(S\) 为最终选择的边集。
则对于任意一个生成树 \(K\) 我们有:
于是对于任意实数 \(x\),考虑它与 \(ans\) 的大小关系:
- \(x>ans\)
则 \(\forall K,f<\sum_{i\in K}t_i\times x+c_i\)。 - \(x=ans\)
则 \(\forall K,f\le\sum_{i\in K}t_i\times x+c_i\)。 - \(x<ans\)
则 \(\exist K,f>\sum_{i\in K}t_i\times x+c_i\)。
于是二分答案,跑最小生成树 check 即可。时间复杂度 \(O(m\log^2)\)。
因为输出要求精确到小数点后第 \(4\) 位,所以要求二分出来的答案精确到小数点后第 \(5\) 位,所以 \(\varepsilon\) 应该赋为 \(10^{-6}\)。
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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=405,maxm=1e4+5;
const double eps=1e-6;
int n,m,f,fa[maxn],sz[maxn];
struct node{
int u,v,c,t;
}e[maxm];
il int find(int x){
return fa[x]!=x?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il double kruskal(double x){
sort(e+1,e+m+1,[=](const node &p,const node &q){return p.t*x+p.c<q.t*x+q.c;});
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
double res=0;
for(int i=1,u,v;i<=m;i++){
u=e[i].u,v=e[i].v;
if(find(u)!=find(v)){
merge(u,v);
res+=e[i].t*x+e[i].c;
}
}
// cout<<res<<"\n";
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>f;
for(int i=1;i<=m;i++){
cin>>e[i].u>>e[i].v>>e[i].c>>e[i].t;
}
double l=0,r=1e14;
while(r-l>eps){
double mid=(l+r)/2;
// cout<<mid<<" ";
if(kruskal(mid)>=f){
r=mid;
}
else{
l=mid;
}
}
printf("%.4f",l);
return 0;
}
}
int main(){return asbt::main();}
D. [蓝桥杯 2023 省 A] 网络稳定性
建出最大生成树,然后树剖 \(+\) 线段树链查询最小值即可。时间复杂度 \(O(q\log^2n)\)。
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
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,inf=0x3f3f3f3f;
int n,m,q,fa[maxn],sz[maxn],dis[maxn];
int dep[maxn],hes[maxn],top[maxn];
int dfn[maxn],idx[maxn],cnt,tr[maxn<<2];
struct node{
int u,v,w;
il bool operator<(const node &x)const{
return w>x.w;
}
}a[maxn*3];
vector<pii> e[maxn];
struct{
int fa[maxn],sz[maxn];
il void init(){
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
}
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il bool check(int u,int v){
return find(u)==find(v);
}
}D;
il void dfs1(int u){
dep[u]=dep[fa[u]]+1;
sz[u]=1;
int mxs=0;
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v==fa[u]){
continue;
}
fa[v]=u,dis[v]=w;
dfs1(v);
sz[u]+=sz[v];
if(mxs<sz[v]){
mxs=sz[v],hes[u]=v;
}
}
}
il void dfs2(int u){
dfn[u]=++cnt;
idx[cnt]=u;
if(!top[u]){
top[u]=u;
}
if(hes[u]){
top[hes[u]]=top[u];
dfs2(hes[u]);
}
for(pii i:e[u]){
int v=i.fir;
if(v!=fa[u]&&v!=hes[u]){
dfs2(v);
}
}
}
il void pushup(int id){
tr[id]=min(tr[lid],tr[rid]);
}
il void build(int id,int l,int r){
if(l==r){
tr[id]=dis[idx[l]];
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid);
build(rid,mid+1,r);
pushup(id);
}
il int query(int id,int L,int R,int l,int r){
if(l>r){
return inf;
}
if(L>=l&&R<=r){
return tr[id];
}
int mid=(L+R)>>1,res=inf;
if(l<=mid){
res=min(res,query(lid,L,mid,l,r));
}
if(r>mid){
res=min(res,query(rid,mid+1,R,l,r));
}
return res;
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+m+1);
D.init();
for(int i=1,u,v,w;i<=m;i++){
u=a[i].u,v=a[i].v,w=a[i].w;
if(D.find(u)!=D.find(v)){
D.merge(u,v);
e[u].pb(mp(v,w));
e[v].pb(mp(u,w));
}
}
for(int i=1;i<=n;i++){
if(!dep[i]){
dfs1(i),dfs2(i);
}
}
build(1,1,n);
while(q--){
int u,v;
cin>>u>>v;
if(!D.check(u,v)){
cout<<"-1\n";
}
else{
int res=inf;
while(top[u]!=top[v]){
if(dep[top[u]]<dep[top[v]]){
swap(u,v);
}
res=min(res,query(1,1,n,dfn[top[u]],dfn[u]));
u=fa[top[u]];
}
if(dep[u]>dep[v]){
swap(u,v);
}
res=min(res,query(1,1,n,dfn[u]+1,dfn[v]));
cout<<res<<"\n";
}
}
return 0;
}
}
int main(){return asbt::main();}
E. [THUPC2022 初赛] 最小公倍树
考虑枚举公因子 \(d\),\(d\) 在 \([l,r]\) 之间的倍数形如 \(\{k\times d,(k+1)\times d,\dots,(k+p)\times d\}\),显然所有点都向 \(k\times d\) 连边是最优的。于是边数为 \(O((r-l)\log)\)。然后再跑 kruskal,时间复杂度为 \(O((r-l)\log^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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5;
int n,m,fa[maxn],sz[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
struct node{
int u,v,w;
il bool operator<(const node &x)const{
return w<x.w;
}
};
vector<node> e;
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m;
for(int i=1;i<=m;i++){
for(int u=(n+i-1)/i*i,v=u+i;v<=m;v+=i){
e.pb((node){u,v,u/gcd(u,v)*v});
}
}
sort(e.begin(),e.end());
for(int i=n;i<=m;i++){
fa[i]=i,sz[i]=1;
}
int ans=0;
for(node i:e){
int u=i.u,v=i.v,w=i.w;
if(find(u)!=find(v)){
merge(u,v),ans+=w;
}
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
F. [BJWC2010] 严格次小生成树
首先建出最小生成树。
遍历每一条非树边,考虑连上它后形成的环,我们要从这个环上删掉原有的最大的一条边。当然边权和新边不能相等。考虑新边一定 \(\ge\) 原有的边的最大值,因此用倍增维护路径上的最大值和严格次大值即可。同时,新加 \(2\) 条边一定不如新加 \(1\) 条边更优,所以依次枚举非树边是正确的。时间复杂度 \(O(m\log m)\)。
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
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e5+5,maxm=3e5+5;
int n,m,dep[maxn],anc[maxn][22];
struct node{
int mx1,mx2;
node(int mx1=-1,int mx2=-1):mx1(mx1),mx2(mx2){}
il node operator+(const node &x)const{
int hp[7];
hp[1]=mx1,hp[2]=mx2,hp[3]=x.mx1,hp[4]=x.mx2;
sort(hp+1,hp+5,greater<int>());
hp[unique(hp+1,hp+5)-hp]=-1;
return node(hp[1],hp[2]);
}
}mxd[maxn][22];
struct edge{
int u,v,w;
il bool operator<(const edge &x)const{
return w<x.w;
}
}a[maxm];
bool vis[maxm];
vector<pii> e[maxn];
il void dfs(int u){
dep[u]=dep[anc[u][0]]+1;
for(int i=1;i<=20;i++){
anc[u][i]=anc[anc[u][i-1]][i-1];
mxd[u][i]=mxd[u][i-1]+mxd[anc[u][i-1]][i-1];
}
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v!=anc[u][0]){
anc[v][0]=u;
mxd[v][0].mx1=w;
dfs(v);
}
}
}
il node query(int u,int v){
if(dep[u]<dep[v]){
swap(u,v);
}
int ddep=dep[u]-dep[v],tmp=0;
node res;
while(ddep){
if(ddep&1){
res=res+mxd[u][tmp];
u=anc[u][tmp];
}
ddep>>=1,tmp++;
}
if(u==v){
return res;
}
for(int i=20;~i;i--){
if(anc[u][i]!=anc[v][i]){
res=res+mxd[u][i]+mxd[v][i];
u=anc[u][i],v=anc[v][i];
}
}
return res+mxd[u][0]+mxd[v][0];
}
struct{
int fa[maxn],sz[maxn];
il void init(){
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
}
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il bool check(int u,int v){
return find(u)==find(v);
}
}D;
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
// cout<<cplx::usdmem()<<"\n";
cin>>n>>m;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+m+1),D.init();
ll ans1=0,ans2=1e18;
for(int i=1,u,v,w;i<=m;i++){
u=a[i].u,v=a[i].v,w=a[i].w;
if(!D.check(u,v)){
D.merge(u,v);
vis[i]=1,ans1+=w;
e[u].pb(mp(v,w));
e[v].pb(mp(u,w));
}
}
dfs(1);
for(int i=1,u,v,w;i<=m;i++){
u=a[i].u,v=a[i].v,w=a[i].w;
if(vis[i]){
continue;
}
node tmp=query(u,v);
int hp[7];
hp[1]=tmp.mx1,hp[2]=tmp.mx2,hp[3]=w;
sort(hp+1,hp+4,greater<int>());
hp[unique(hp+1,hp+4)-hp]=-1;
if(~hp[2]){
ans2=min(ans2,ans1-hp[2]+hp[1]);
}
}
cout<<ans2;
return 0;
}
}
int main(){return asbt::main();}
G. [APIO2013] 道路费用
\(k\le 20\),考虑 \(O(2^k)\) 暴力枚举加入的边。但是边数很大,时间复杂度很高无法承受。
考虑在一开始强制选这 \(k\) 条边,然后跑最小生成树,此时加入的边就是一定会加入的边。设这个边集为 \(S\)。
将 \(S\) 连接的连通块缩成点,点数为 \(O(k)\)。再在原图上对这些点跑最小生成树,设加入的边集为 \(T\),则 \(T\) 为加入那 \(k\) 条边后有可能在最小生成树中的边。数量也为 \(O(k)\)。
然后暴力枚举强制加入的边,用 \(T\) 跑出最小生成树,再用 \(T\) 中的非树边限制强制加入的边即可。具体地,对于一条非树边 \((u,v)\),树上 \(u\) 到 \(v\) 的路径中的所有边都应该 \(\ge\) 这条非树边的边权。暴力跳父亲即可。
考虑统计答案,只需要计算通过每条边的人数,用 dfs 计算子树权值和即可。
时间复杂度 \(O(m\log m+2^kk^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
- 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
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=3e5+5;
int n,m,k,a[maxn],bel[maxn];
bool vis[maxn];
vector<int> dk;
vector<pii> e[maxn];
struct edge{
int u,v,w;
il bool operator<(const edge &x)const{
return w<x.w;
}
}em[maxn],ek[maxn];
vector<edge> et;
int fa[maxn],sz[maxn],ans;
int mxe[maxn],dep[maxn],tof[maxn];
il void init(){
for(int i=1;i<=n;i++){
fa[i]=i,sz[i]=1;
}
}
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
il void merge(int u,int v){
u=find(u),v=find(v);
if(u==v){
return ;
}
if(sz[u]>sz[v]){
sz[u]+=sz[v],fa[v]=u;
}
else{
sz[v]+=sz[u],fa[u]=v;
}
}
il void dfs(int u){
dep[u]=dep[fa[u]]+1;
sz[u]=a[u];
for(pii i:e[u]){
int v=i.fir,w=i.sec;
if(v!=fa[u]){
fa[v]=u;
tof[v]=w;
dfs(v);
sz[u]+=sz[v];
}
}
}
il void solve(int S){
for(int u:dk){
fa[u]=u,sz[u]=1,e[u].clear();
}
for(int i=1,u,v;i<=k;i++){
if(S>>(i-1)&1){
u=bel[ek[i].u],v=bel[ek[i].v];
if(find(u)==find(v)){
return ;
}
merge(u,v);
e[u].pb(mp(v,-i));
e[v].pb(mp(u,-i));
}
}
for(int i=0;i<et.size();i++){
vis[i]=0;
}
for(int i=0,u,v,w;i<et.size();i++){
u=et[i].u,v=et[i].v,w=et[i].w;
if(find(u)!=find(v)){
merge(u,v);
vis[i]=1;
e[u].pb(mp(v,w));
e[v].pb(mp(u,w));
}
}
for(int i=1;i<=k;i++){
mxe[i]=INT_MAX;
}
for(int u:dk){
dep[u]=tof[u]=fa[u]=sz[u]=0;
}
dfs(bel[1]);
// puts("666");
for(int i=0,u,v,w;i<et.size();i++){
// cout<<i<<"\n";
if(vis[i]){
continue;
}
u=et[i].u,v=et[i].v,w=et[i].w;
if(dep[u]<dep[v]){
swap(u,v);
}
// cout<<dep[u]<<" "<<dep[v]<<"\n";
while(dep[u]>dep[v]){
// puts("666");
if(tof[u]<0){
mxe[-tof[u]]=min(mxe[-tof[u]],w);
}
u=fa[u];
}
while(u!=v){
if(tof[u]<0){
mxe[-tof[u]]=min(mxe[-tof[u]],w);
}
if(tof[v]<0){
mxe[-tof[v]]=min(mxe[-tof[v]],w);
}
u=fa[u],v=fa[v];
}
}
for(int u:dk){
if(tof[u]<0){
tof[u]=-mxe[-tof[u]];
}
}
int res=0;
for(int u:dk){
if(tof[u]<0){
res-=tof[u]*sz[u];
}
}
ans=max(ans,res);
}
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
signed main(){
// cout<<cplx::usdmem();
ios::sync_with_stdio(0),cin.tie(0);
// freopen("toll5.in","r",stdin);
cin>>n>>m>>k;
// cout<<n<<" "<<m<<" "<<k<<"\n";
for(int i=1;i<=m;i++){
cin>>em[i].u>>em[i].v>>em[i].w;
}
init(),sort(em+1,em+m+1);
for(int i=1;i<=k;i++){
cin>>ek[i].u>>ek[i].v;
merge(ek[i].u,ek[i].v);
}
for(int i=1;i<=n;i++){
cin>>a[i];
}
for(int i=1,u,v;i<=m;i++){
u=em[i].u,v=em[i].v;
if(find(u)!=find(v)){
merge(u,v);
vis[i]=1;
}
}
init();
for(int i=1,u,v;i<=m;i++){
if(vis[i]){
u=find(em[i].u);
v=find(em[i].v);
if(sz[u]>sz[v]){
sz[u]+=sz[v];
a[u]+=a[v];
fa[v]=u;
}
else{
sz[v]+=sz[u];
a[v]+=a[u];
fa[u]=v;
}
}
}
for(int i=1;i<=n;i++){
bel[i]=find(i);
if(bel[i]==i){
dk.pb(i);
}
}
init();
for(int i=1,u,v,w;i<=m;i++){
u=bel[em[i].u],v=bel[em[i].v],w=em[i].w;
if(find(u)!=find(v)){
et.pb((edge){u,v,w});
merge(u,v);
}
}
for(int S=0;S<1<<k;S++){
// cout<<bitset<15>(S)<<"\n";
solve(S);
}
cout<<ans;
return 0;
}
}
signed main(){return asbt::main();}
/*
100000 299989 12
*/
H. [蓝桥杯 2015 省 A] 灾后重建
建 kruskal 重构树,问题转化为求 \(\operatorname{lca}\) 。
根号分治,对于 \(k>\sqrt{n}\) 暴力求,剩下的对于剩余系去建线段树区间查询。时间复杂度 \(O(q\sqrt{n})\)。
然而空间十分爆炸。原因是对于每个剩余系都建线段树是无法接受的。因此将查询离线下来,就可以只建一棵线段树,不断重构即可。然而原题数据没有卡这一点。
空间不正确的代码:
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
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,maxb=250;
int n,m,q,blen,fa[maxn],zhi[maxn];
vector<int> e[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct edge{
int u,v,w;
il bool operator<(const edge &x)const{
return w<x.w;
}
}a[maxn];
int dfn[maxn],oula[maxn],idx[maxn],cnt;
il void dfs(int u){
dfn[u]=++cnt;
oula[cnt]=cnt;
idx[cnt]=u;
for(int v:e[u]){
if(v!=fa[u]){
dfs(v);
oula[++cnt]=dfn[u];
}
}
}
struct{
int Log[maxn],st[maxn][22];
il void init(){
for(int i=2;i<=cnt;i++){
Log[i]=Log[i>>1]+1;
}
for(int i=1;i<=cnt;i++){
st[i][0]=oula[i];
}
for(int j=1;j<=Log[cnt];j++){
for(int i=1;i+(1<<j)-1<=cnt;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
}
il int query(int l,int r){
int tmp=Log[r-l+1];
return min(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
}
}ST;
il int lca(int u,int v){
if(dfn[u]>dfn[v]){
swap(u,v);
}
return idx[ST.query(dfn[u],dfn[v])];
}
vector<int> syx[maxb][maxb];
struct stree{
int num;
vector<int> tr;
il void pushup(int id){
tr[id]=lca(tr[lid],tr[rid]);
}
il void build(int id,int l,int r,int x,int y){
if(l>r){
return ;
}
if(l==r){
tr[id]=syx[x][y][l];
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid,x,y);
build(rid,mid+1,r,x,y);
pushup(id);
}
stree(int x=0,int y=0):num(syx[x][y].size()),tr(num<<2,0){
// puts("666");
build(1,0,num-1,x,y);
}
il int query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return tr[id];
}
int mid=(L+R)>>1;
if(r<=mid){
return query(lid,L,mid,l,r);
}
if(l>mid){
return query(rid,mid+1,R,l,r);
}
return lca(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r));
}
il int query(int id,int l,int r){
return query(1,0,num-1,l,r);
}
}SG[maxb][maxb];
bool vis[maxb][maxb];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// freopen("H.in","r",stdin);
// freopen("H.out","w",stdout);
// cout<<cplx::usdmem();
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+m+1);
int tot=n;
for(int i=1;i<=n<<1;i++){
fa[i]=i;
}
for(int i=1,u,v,w;i<=m;i++){
// puts("666");
u=a[i].u,v=a[i].v,w=a[i].w;
u=find(u),v=find(v);
if(u!=v){
fa[u]=fa[v]=++tot;
zhi[tot]=w;
e[tot].pb(u),e[tot].pb(v);
}
}
dfs(tot),ST.init();
blen=sqrt(n);
// cout<<blen<<"\n";
// bool begin;
// int num=0;
for(int i=1;i<=blen;i++){
for(int j=1;j<=n;j++){
syx[i][j%i].pb(j);
// num++;
}
}
// cout<<num<<"\n";
// bool end;
// cout<<(&end-&begin)/1048576.0<<"\n";
while(q--){
int l,r,k,c;
cin>>l>>r>>k>>c;
if(k>blen){
int tmp=l;
if(l%k<c){
tmp+=c-l%k;
}
else if(l%k>c){
tmp-=l%k-c;
tmp+=k;
}
for(int i=tmp+k;i<=r;i+=k){
tmp=lca(tmp,i);
}
cout<<zhi[tmp]<<"\n";
continue;
}
if(!vis[k][c]){
SG[k][c]=stree(k,c);
vis[k][c]=1;
}
cout<<zhi[SG[k][c].query(1,lwrb(syx[k][c].begin(),syx[k][c].end(),l)-syx[k][c].begin(),uprb(syx[k][c].begin(),syx[k][c].end(),r)-syx[k][c].begin()-1)]<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
离线卡空间的代码:
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
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=2e5+5,maxb=250;
int n,m,q,blen,fa[maxn],zhi[maxn];
vector<int> e[maxn];
il int find(int x){
return x!=fa[x]?fa[x]=find(fa[x]):x;
}
struct edge{
int u,v,w;
il bool operator<(const edge &x)const{
return w<x.w;
}
}a[maxn];
int dfn[maxn],oula[maxn],idx[maxn],cnt;
il void dfs(int u){
dfn[u]=++cnt;
oula[cnt]=cnt;
idx[cnt]=u;
for(int v:e[u]){
if(v!=fa[u]){
dfs(v);
oula[++cnt]=dfn[u];
}
}
}
struct{
int Log[maxn],st[maxn][22];
il void init(){
for(int i=2;i<=cnt;i++){
Log[i]=Log[i>>1]+1;
}
for(int i=1;i<=cnt;i++){
st[i][0]=oula[i];
}
for(int j=1;j<=Log[cnt];j++){
for(int i=1;i+(1<<j)-1<=cnt;i++){
st[i][j]=min(st[i][j-1],st[i+(1<<(j-1))][j-1]);
}
}
}
il int query(int l,int r){
int tmp=Log[r-l+1];
return min(st[l][tmp],st[r-(1<<tmp)+1][tmp]);
}
}ST;
il int lca(int u,int v){
if(dfn[u]>dfn[v]){
swap(u,v);
}
return idx[ST.query(dfn[u],dfn[v])];
}
vector<int> syx[maxb][maxb];
struct stree{
int num,tr[maxn<<2];
il void pushup(int id){
tr[id]=lca(tr[lid],tr[rid]);
}
il void build(int id,int l,int r,int x,int y){
if(l>r){
return ;
}
if(l==r){
tr[id]=syx[x][y][l];
return ;
}
int mid=(l+r)>>1;
build(lid,l,mid,x,y);
build(rid,mid+1,r,x,y);
pushup(id);
}
stree(int x=0,int y=0):num(syx[x][y].size()){
// puts("666");
build(1,0,num-1,x,y);
}
il int query(int id,int L,int R,int l,int r){
if(L>=l&&R<=r){
return tr[id];
}
int mid=(L+R)>>1;
if(r<=mid){
return query(lid,L,mid,l,r);
}
if(l>mid){
return query(rid,mid+1,R,l,r);
}
return lca(query(lid,L,mid,l,r),query(rid,mid+1,R,l,r));
}
il int query(int id,int l,int r){
return query(1,0,num-1,l,r);
}
}SG;
struct node{
int l,r,k,c,id;
il bool operator<(const node &x)const{
return k<x.k||k==x.k&&c<x.c;
}
}wt[maxn];
int ans[maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// freopen("H.in","r",stdin);
// freopen("H.out","w",stdout);
// cout<<cplx::usdmem();
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>q;
for(int i=1;i<=m;i++){
cin>>a[i].u>>a[i].v>>a[i].w;
}
sort(a+1,a+m+1);
int tot=n;
for(int i=1;i<=n<<1;i++){
fa[i]=i;
}
for(int i=1,u,v,w;i<=m;i++){
// puts("666");
u=a[i].u,v=a[i].v,w=a[i].w;
u=find(u),v=find(v);
if(u!=v){
fa[u]=fa[v]=++tot;
zhi[tot]=w;
e[tot].pb(u),e[tot].pb(v);
}
}
dfs(tot),ST.init();
blen=sqrt(n);
// cout<<blen<<"\n";
// bool begin;
// int num=0;
for(int i=1;i<=blen;i++){
for(int j=1;j<=n;j++){
syx[i][j%i].pb(j);
// num++;
}
}
// cout<<num<<"\n";
// bool end;
// cout<<(&end-&begin)/1048576.0<<"\n";
for(int i=1;i<=q;i++){
cin>>wt[i].l>>wt[i].r>>wt[i].k>>wt[i].c;
wt[i].id=i;
}
sort(wt+1,wt+q+1);
for(int i=1;i<=q;i++){
int l=wt[i].l,r=wt[i].r,k=wt[i].k,c=wt[i].c;
if(k>blen){
int tmp=l;
if(l%k<c){
tmp+=c-l%k;
}
else if(l%k>c){
tmp-=l%k-c;
tmp+=k;
}
for(int i=tmp+k;i<=r;i+=k){
tmp=lca(tmp,i);
}
ans[wt[i].id]=zhi[tmp];
continue;
}
if(k!=wt[i-1].k||c!=wt[i-1].c){
SG=stree(k,c);
}
ans[wt[i].id]=zhi[SG.query(1,lwrb(syx[k][c].begin(),syx[k][c].end(),l)-syx[k][c].begin(),uprb(syx[k][c].begin(),syx[k][c].end(),r)-syx[k][c].begin()-1)];
}
for(int i=1;i<=q;i++){
cout<<ans[i]<<"\n";
}
return 0;
}
}
int main(){return asbt::main();}
I. Mathematics Curriculum
子段最大值个数即为这个点在大根笛卡尔树中的深度。考虑建树过程即可证明。
设 \(f_{i,j,k}\) 表示 \(i\) 个点的笛卡尔树,其中 \(i\) 是最大的点,第 \(k\) 层有 \(j\) 个点。枚举左右子树即可得到方程:
边界条件 \(f_{i,1,1}=i!\)。转移是 \(O(n^5)\) 的,考虑优化。
- 对于 \(k>i\),只有 \(f_{i,0,k}=i!\),其它值都为 \(0\)。
- 三个乘数都不为 \(0\) 时转移才有效。
于是就可以通过。
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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=105;
int n,m,num,mod;
int fac[maxn],C[maxn][maxn];
int f[maxn][maxn][maxn];
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
// freopen("I.in","r",stdin);
ios::sync_with_stdio(0),cin.tie(0);
cin>>n>>m>>num>>mod;
fac[0]=C[0][0]=1;
for(int i=1;i<=n;i++){
fac[i]=fac[i-1]*1ll*i%mod;
C[i][0]=1;
for(int j=1;j<=i;j++){
C[i][j]=(C[i-1][j-1]+C[i-1][j])%mod;
}
}
for(int i=0;i<=m;i++){
f[0][0][i]=1;
}
f[1][0][0]=f[1][1][1]=1;
for(int i=2;i<=m;i++){
f[1][0][i]=1;
}
for(int i=2;i<=n;i++){
f[i][1][1]=fac[i];
for(int k=2;k<=m;k++){
if(k>i){
f[i][0][k]=fac[i];
continue;
}
for(int j=0;j<=i;j++){
for(int x=0;x<i;x++){
if(!C[i-1][x]){
continue;
}
for(int y=0;y<=j;y++){
if(f[x][y][k-1]&&f[i-1-x][j-y][k-1]){
(f[i][j][k]+=C[i-1][x]*1ll*f[x][y][k-1]%mod*f[i-1-x][j-y][k-1]%mod)%=mod;
}
}
}
}
}
}
cout<<f[n][num][m];
return 0;
}
}
int main(){return asbt::main();}
J. 「OICon-02」maxiMINImax
用单调栈可以求出以 \(a_i\) 为最小值和最大值的区间个数 \(qmn_i\) 和 \(qmx_i\)。
从小到大枚举第二个区间的最小值,记 \(p_i\) 表示 \(i\) 的位置,则对于 \(i\) 的答案即为:
简单推式子后得到:
\(
qmn_{p_i}(\sum{qmx_j}\sum{qmx_k}i^2-\sum{qmx_ja_j}\sum{qmx_k}i-\sum{qmx_j}\sum{qmx_ka_k}i+\sum{qmx_ja_j}\sum{qmx_ka_k})
\)
于是开两棵树状数组维护 \(qmx_j\) 和 \(qmx_ja_j\) 就好了。时间复杂度 \(O(n\log n)\)。
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
using namespace std;
namespace asbt{
namespace cplx{bool begin;}
const int maxn=1e6+5,mod=9712176;
int n,a[maxn],p[maxn],zhan[maxn];
int lmn[maxn],rmn[maxn];
int lmx[maxn],rmx[maxn];
int qmn[maxn],qmx[maxn];
struct{
int tr[maxn];
il int lowbit(int x){
return x&-x;
}
il void upd(int p,int v){
for(;p<=n;p+=lowbit(p)){
(tr[p]+=v)%=mod;
}
}
il int query(int p){
int res=0;
for(;p;p-=lowbit(p)){
(res+=tr[p])%=mod;
}
return res;
}
il int query(int l,int r){
return (query(r)-query(l-1)+mod)%mod;
}
}F1,F2;
namespace cplx{
bool end;
il double usdmem(){return (&begin-&end)/1048576.0;}
}
int main(){
ios::sync_with_stdio(0),cin.tie(0);
cin>>n;
for(int i=1;i<=n;i++){
cin>>a[i];
p[a[i]]=i;
}
for(int i=1,top=0;i<=n;i++){
while(top&&a[zhan[top]]>a[i]){
top--;
}
lmn[i]=zhan[top];
zhan[++top]=i;
}
for(int i=1,top=0;i<=n;i++){
while(top&&a[zhan[top]]<a[i]){
top--;
}
lmx[i]=zhan[top];
zhan[++top]=i;
}
zhan[0]=n+1;
for(int i=n,top=0;i;i--){
while(top&&a[zhan[top]]>a[i]){
top--;
}
rmn[i]=zhan[top];
zhan[++top]=i;
}
for(int i=n,top=0;i;i--){
while(top&&a[zhan[top]]<a[i]){
top--;
}
rmx[i]=zhan[top];
zhan[++top]=i;
}
for(int i=1;i<=n;i++){
qmn[i]=(i-lmn[i])*1ll*(rmn[i]-i)%mod;
qmx[i]=(i-lmx[i])*1ll*(rmx[i]-i)%mod;
}
int ans=0;
for(int i=1,xj,xaj,xk,xak;i<=n;i++){
xj=F1.query(1,p[i]-1);
xaj=F2.query(1,p[i]-1);
xk=F1.query(p[i]+1,n);
xak=F2.query(p[i]+1,n);
(ans+=qmn[p[i]]*1ll*(xj*1ll*xk%mod*i%mod*i%mod-xaj*1ll*xk%mod*i%mod-xj*1ll*xak%mod*i%mod+xaj*1ll*xak%mod)%mod)%=mod;
(ans+=mod)%=mod;
F1.upd(p[i],qmx[p[i]]);
F2.upd(p[i],qmx[p[i]]*1ll*i%mod);
}
cout<<ans;
return 0;
}
}
int main(){return asbt::main();}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步