25.8.6模拟赛
25.8.6模拟赛
T1 开挂:
性质分析:
性质 1:
对 \(a\) 中两个数而言,一个增加 \(p\),一个增加 \(q\),满足 \(p < q , b_i < b_j\),在所有 \(p + q\) 相等的情况下,\(p\) 与 \(b_i\) 配对、\(q\) 与 \(b_j\) 配对的答案比 \(q\) 与 \(b_i\) 配对、\(p\) 与 \(b_j\) 配对的答案更优。

性质 2:
对 \(a\) 中两个数而言,一个增加 \(p\),一个增加 \(q\),在所有 \(p + q\) 相等的情况下,\(p\) 和 \(q\) 的差距越大就越优。
举例:
一个增加 5、一个增加 3和一个增加 1、一个增加 7 两种方案,后者方案更优。
为什么?
因为我们可以任意指定 \(b_i\) 和 \(b_j\),在性质 1 的前提下,\(p\) 和 \(q\) 的差距越大,答案更优。
例如:

性质推广:
对 \(a\) 中任意多个数都满足上述性质。
solution:
先将 \(a\) 从小到大排序,排序后遍历,依次给相同的数分配一个值代表最终变成的数。如果分配的值与下一个不同的数相同,则停止分配,将剩下未分配的数插入大根堆(由于从小到大遍历,插入的数单调不降,所以也可以用栈)。
每遇到空隙(例如 3 和 5 之间缺了个 4),就将堆顶的数(之前未被分配值的数中最大的那个)取出来,把这个空隙分配给它(保证变化量更小),这样就能充分利用这个数列。
遍历结束后,如果还有未被分配的数,则用数列之外的数继续分配。
最后将变化量储存在堆中,按大的变化量分配小的 \(b\) 的顺序(性质 2)依次统计答案即可。
code:
#include<bits/stdc++.h>
#define ll unsigned long long
using namespace std;
const int N=1e6+5;
ll read(){
ll num=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=(num<<1)+(num<<3)+ch-'0';
ch=getchar();
}
return num*f;
}
int n;
ll ans=0;
ll a[N],b[N];
ll p[N];
priority_queue<ll> q;
stack<ll> s;
int main(){
freopen("openhook.in","r",stdin);
freopen("openhook.out","w",stdout);
n=read();
for(int i=1;i<=n;++i) a[i]=read();
for(int i=1;i<=n;++i) b[i]=read();
sort(a+1,a+n+1);
for(int i=n-1;i>=1;--i){
if(a[i]==a[i+1])
p[i]=p[i+1];
else
p[i]=a[i+1];
}
for(int i=2;i<=n;++i){
if(a[i]==a[i-1]){
s.push(a[i]);
}
else{
for(ll j=a[i-1]+1;!s.empty()&&j<a[i];++j){
q.push(j-s.top());
s.pop();
}
}
}
for(ll i=a[n]+1;!s.empty();++i){
q.push(i-s.top());
s.pop();
}
sort(b+1,b+n+1);
int i=1;
while(!q.empty()){
ans+=q.top()*b[i];
i++;
q.pop();
}
printf("%llu\n",ans);
fclose(stdin);fclose(stdout);
return 0;
}
T2 叁仟柒佰万:
性质分析:
对于最后分出来的每个段,\(mex\) 值都相等且都等于全局 \(mex\)。
证明:
设全局 \(mex\) 等于 \(K\),每段 \(mex\) 均等于 \(X\):
- 若 \(X < K\),由于序列中为 \(X\) 的数必然存在,\(X\) 必然在某个段中,与所有段的 \(mex\) 都等于 \(X\) 矛盾。
- 若 \(X > K\),那么 \(K\) 必然存在,与全局 \(mex\) 等于 \(K\) 矛盾。
于是只能是 \(X = K\)。
solution:
考虑 \(dp\)。
设 \(f_i\) 为前 \(i\) 个数的划分方案,可以想到枚举 \(i\) 之前所有的 \(j\),满足区间 \([j,i]\) 的 \(mex\) 等于全局 \(mex\)(由性质可得),\(f_i\) 即为所有 \(f_j\) 的和,转移方程:\(f_i = \sum f_j[mex([j,i]) = K]\),时间复杂度 \(O(n^2)\)。
考虑优化。
由于数列中不存在 \(K\),所以在 \(mex\) 已经等于 \(K\) 的情况下再往区间里加任何数列中的数都不会对 \(mex\) 产生影响。所以可以从 \(i\) 开始往前先找到一个 \(j\) 使得区间 \([j,i]\) 的 \(mex\) 等于全局 \(mex\),然后取 \(f_j\) 的前缀和即可。
由于 \(i\) 不断往后一直加数,所以指针 \(j\) 一定单调不降,于是可以双指针 + 前缀和就可以将时间复杂度优化到 \(O(n)\),就做完了。
#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=37000005,D=3e5+5;
const ll mod=1e9+7;
int read(){
int num=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9'){
num=(num<<1)+(num<<3)+ch-'0';
ch=getchar();
}
return num*f;
}
int t,n,mex=0;
int a[N];
int vis[D];
ll f[N];
int main(){
freopen("clods.in","r",stdin);
freopen("clods.out","w",stdout);
cin>>t;
while(t--){
n=read();
if(n!=37000000) memset(f,0,sizeof(f));
if(n!=37000000) memset(vis,0,sizeof(vis));
if(n==37000000){
int x=read(),y=read();
a[1]=0;
for(int i=2;i<=n;++i){
a[i]=(a[i-1]*x+y+i)&262143;
vis[a[i]]++;
}
}
else{
for(int i=1;i<=n;++i){
a[i]=read();
vis[a[i]]++;
}
}
mex=0;
for(int i=0;i<=n;++i){
if(vis[i]==0){
mex=i;
break;
}
}
int st=0;
int mexi=0;
memset(vis,0,sizeof(vis));
for(int i=1;i<=n;++i){
vis[a[i]]++;
while(vis[mexi]) mexi++;
if(mexi==mex){
st=i;
break;
}
}
f[st]=1;
ll s=1;
int j=1;
for(int i=st+1;i<=n;++i){
vis[a[i]]++;
while(j<i){
if(a[j]<mex&&vis[a[j]]==1) break;
vis[a[j]]--;
s=(s+f[j])%mod;
j++;
}
f[i]=s%mod;
}
printf("%lld\n",f[n]%mod);
}
fclose(stdin);fclose(stdout);
return 0;
}
T3 超级加倍(原题解):
算法 1
枚举点 \(x\), dfs 一遍, 找到所有合法的 \(y\), 时间复杂度 \(O(n^2)\), 期望得分 15 。
算法 2
对于菊花图, 分为过根和不过根两种路径分类讨论即可, 时间复杂度 \(O(n)\), 期望得分 25 。
算法 3
对于链, 假设我们选出的 \(x\) 在 \(y\) 左边, 可以维护 \(L_i,R_i\) 表示 \(i\) 左边第一个 \(> p_i\) 的数, 以及 \(i\) 右边第一 个 \(< p_i\) 的数可以单调栈预处理, 两个点合法当且仅当 \(R_x > y\) 且 \(L_y < x\), 可以看成一个三维偏序问题做到 \(O(n\log^2 n)\), 但是可以忽略 \(x < y\) 这个条件, 这样会把所有的 \(x > y\) 统计为合法, 最后减去即可, 时间复杂度 \(O(n\log n)\), 结合之前的算法期望得分 50 , 如果其中一些步骤做复杂了时间复杂度退化至 \(O(n\log^2 n)\) 仅能获得 40 分。
一种更简单的做法是先一遍单调栈求出 \(L_i\), 第二次在单调栈中维护所有的当前的后缀最小值, 在这个单调栈上进行二分, 可以做到 \(O(n\log n)\), 期望得分 50 。
算法 4
对于树的问题, 可以考虑点分治, 每次处理过分治中心的点对, 一个点合法需要满足是到根的最小值/最大值, 另外需要求出路径最小值/最大值来和路径的另外一边合并, 合并时仍存在二维偏序的限制, 必定会带一 个 \(\log\), 无法优化, 时间复杂度 \(O(n\log^2 n)\), 结合之前的算法期望得分 75 , 如果写丑了写成 \(n\log^3 n\) 仅能获得 65 分。
算法 5
对比算法 4 和算法 3 , 发现毫不相关, 且点分治本身并没有利用到太多性质, 回到算法 3,\(R_x > y\) 这类性质可以联想到笛卡尔树, 而笛卡尔树在树上的一个很类似的形态是 Kruskal 重构树, 按点权从小到大建树建成 \(T_1\), 从大到小建成 \(T_2\) (满足任意两点 \(x,y\) 在 \(T_1\) 中的 \(lca\) 是路径最小值, 在 \(T_2\) 中是路径最大值), 以上部分可以用并查集实现, 那么两点 \(x,y\) 满足条件当且仅当 \(x\) 在 \(T_1\) 中是 \(y\) 的祖先, \(y\) 在 \(T_2\) 中是 \(x\) 的祖先, 这是一个朴素的偏序问题, 在 \(T_1\) 中求出 DFS 序, 在 \(T_2\) 上 DFS, 用一个树状数组维护可行答案, 时间复杂度 \(O(n\log n)\), 由于复杂度瓶颈在树状数组, 常数非常小, 当然, 算法 3 也可以用同样的方法建出笛卡尔树然后转化为相同的问题, 同样也是 \(O(n\log n)\) 的, 期望得分 100 。
#include<bits/stdc++.h>
#define re register
using namespace std;
char rbuf[20000002];
int pt=-1;
inline int read(){
re int t=0;re char v=rbuf[++pt];
while(v<'0')v=rbuf[++pt];
while(v>='0')t=(t<<3)+(t<<1)+v-48,v=rbuf[++pt];
return t;
}
int n,p[2000002],Head[2000002],dfn[2000002],c[2000002],siz[2000002],tim,c1;
long long ans;
struct edge{int to,next;}E[4000002];
inline void add(re int x,re int y){E[++c1]=(edge){y,Head[x]},Head[x]=c1;}
inline void Add(re int x){for(;x<=n;x+=x&(-x))++c[x];}
inline void Del(re int x){for(;x<=n;x+=x&(-x))--c[x];}
inline int ask(re int x,re int s=0){for(;x;x^=x&(-x))s+=c[x];return s;}
struct Tree{
int head[2000002],cnt,fa[2000002];
edge e[2000002];
char v[2000002];
inline int root(re int x){return x==fa[x]?x:fa[x]=root(fa[x]);}
inline void add(re int x,re int y){e[++cnt]=(edge){y,head[x]},head[x]=cnt;}
inline void build(){
for(re int i=1;i<=n;++i)fa[i]=i;
for(re int i=1;i<=n;++i){
v[p[i]]=1;
for(re int j=Head[p[i]];j;j=E[j].next)if(v[E[j].to])add(p[i],root(E[j].to)),fa[root(E[j].to)]=p[i];
}
}
}T1,T2;
inline void dfs(re int x,re int y){
dfn[x]=++tim,siz[x]=1;
for(re int i=T1.head[x];i;i=T1.e[i].next)
dfs(T1.e[i].to,x),siz[x]+=siz[T1.e[i].to];
}
inline void calc(re int x,re int y){
ans+=ask(dfn[x]+siz[x]-1)-ask(dfn[x]),Add(dfn[x]);
for(re int i=T2.head[x];i;i=T2.e[i].next)calc(T2.e[i].to,x);
Del(dfn[x]);
}
int main(){
freopen("charity.in","r",stdin);
freopen("charity.out","w",stdout);
fread(rbuf,1,20000000,stdin);
n=read();
for(re int i=1;i<=n;++i)p[i]=n-i+1;
for(re int i=1,x;i<=n;++i){
x=read();
if(x)add(x,i),add(i,x);
}
T1.build(),reverse(p+1,p+n+1),T2.build();
dfs(1,1),calc(n,n);
printf("%lld",ans);
}
T4 欢乐豆(原题解):
算法 1
使用 Floyed 算法, 时间复杂度 \(O(n^3)\), 可以通过测试点 \(1,2,3\), 期望得分 12 。
算法 2
对于 \(m = 0\), 发现 \(x\to y\) 的最短路一定是直接花费 \(a_x\) 从 \(x\) 走到 \(y\), 即答案为 \(\sum a_i\times (n - 1)\), 直接计算即可, 结合算法 1 期望得分 20 。
算法 3
对于特殊性质 \(A\), 可以分类讨论, 一个点 \(x\) 走的方式一定是直接走 \(x\) 或者走一条 \(x\to y\) 的边再走一个 \(a_y\), 分类讨论即可, 可通过测试点 \(4 ∼ 8\) 。
算法 4
发现在整张大图中, 除了存在改变边权的点, 还有很多没有任何改动的点, 这启发我们对有改变边权的这 些点的导出子图单独考虑。
将每一条改变边权的边视为无向边, 得到一些连通块。
考虑 \((x,y)\) 两点间的最短路:
若 \((x,y)\) 不属于同一连通块, 那么 \(x\to y\) 的路径可以看作: \(x\) 在自己的连通块走, 走到一个点 \(z\), 此时 \(z\) 再往外走, 由于走的点不是同一连通块, 那么出边的边权一定是 \(a_z\), 此时直接走到 \(y\) 一定最优。此时可以发现找到的点 \(z\) 仅需要满足 \(dis(x,z) + a_z\) 最小, 其中 \(dis(x,z)\) 表示同一连通块两点间的距离。
也就是说我们把问题转化为了求出所有同一连通块的 \(dis(x,y)\) 。
对于边数 \(m\) 只有 500 , 其涉及的最大的连通块大小只有 501 , 可以直接 Floyed 求解, 结合之前的算法可以 通过测试点 \(1 ∼ 12\), 期望得分 48 。
注意这里有一个小细节, 一条边 \(x\to y\) 可能在边权被修改之后边权非常大, 此时可以先从 \(x\) 走到 \(z\), 再从 \(z\) 走到 \(y\), 其中 \(z\) 是连通块外部的一点, 可以发现选取的一定是 \(a\) 最小的 \(z\) 。
算法 5
再看求的这个问题, 可以看做如下形式:
有一个 \(n\) 个点 \(m\) 条边的有向图, \(n,m\le 3001\), 每条有向边有边权, 若一条边 \(x\to y\) 不存在于图上, 则视为存在一条 \(x\to y\) 的有向边, 边权为 \(a_x\), 也就是说, 在这个图上, 所有点的出边边权基本相同, 除了不超过 \(m\) 条边, 我们需要求的是全源最短路。
朴素的最短路算法是无法解决的, 但我们可以用数据结构优化。
由于边权非负, 可以模拟 Dijkstra 算法, 我们需要执行两种操作:
-
取出最全局最小值。
-
用边权更新权值。
使用线段树, 动态维护每个点的 \(dis\), 用 \(x\) 出边更新时将所有的出边指向的点 \(y\) 排序, 看成给所有的 \(y_i\) 单点取 \(\min\), 给所有的区间 \((y_i,y_{i+1})\) 区间取 \(\min\), 这两部分不重叠所以不会出现重复转移, 由于边数总共是 \(m\) , 所以涉及到的单点操作和区间操作都是 \(O(m)\) 级别的, 最后在将 \(x\) 在线段树上单点修改为 inf, 由于线段 树的所有操作都是 \(O(\log m)\) 的, 所以总复杂度 \(O(m^2\log m)\) 。
特殊性质 \(B\) 是给的是同样用优化的最短路算法但没有处理好重复转移的问题, 也就是说直接用 \(a_x\) 去转移 \(x\) 的所有出边, 然后再用每一条特殊边去转移, 这样可能存在更好写的写法。
最后加上算法 4 中的分类讨论, 总时间复杂度为 \(O(n + m^2\log m)\), 期望得分 100 。
#include<bits/stdc++.h>
#define re register
using namespace std;
char rbuf[20000002];
int pt=-1;
inline int read(){
re int t=0;re char v=rbuf[++pt];
while(v<'0')v=rbuf[++pt];
while(v>='0')t=(t<<3)+(t<<1)+v-48,v=rbuf[++pt];
return t;
}
int mn[12002],pos[12002],n,m,head[100002],cnt,fa[100002],a[100002],tg[12002],inf=1e9,X,Y,Z,sum[12002],dis[3002],pp[100002],pmn[100002],smn[100002];
struct edge{int to,next,w;}e[3002];
struct node{int x,y;bool operator <(const node A)const{return x<A.x;};};
long long ans;
inline int root(re int x){return x==fa[x]?x:fa[x]=root(fa[x]);}
vector<int>v[100002];
vector<node>E[3002];
inline void pu(re int p){
if(!sum[p]){mn[p]=inf;return;}
if(mn[p<<1]<mn[p<<1|1])mn[p]=mn[p<<1],pos[p]=pos[p<<1];
else mn[p]=mn[p<<1|1],pos[p]=pos[p<<1|1];
}
inline void CG(re int x,re int y){
if(!sum[x])return;
tg[x]=min(tg[x],y);
mn[x]=min(mn[x],y);
}
inline void pd(re int p){
if(tg[p]^inf){
CG(p<<1,tg[p]),CG(p<<1|1,tg[p]);
tg[p]=inf;
}
}
inline void del(re int p,re int l,re int r){
--sum[p];
if(l==r){pos[p]=0,mn[p]=inf;return;}
pd(p);
re int mid=l+r>>1;
if(X<=mid)del(p<<1,l,mid);
else del(p<<1|1,mid+1,r);
pu(p);
}
inline void cg(re int p,re int l,re int r){
if(!sum[p])return;
if(l>=X&&r<=Y)return CG(p,Z);
pd(p);
re int mid=l+r>>1;
if(X<=mid)cg(p<<1,l,mid);
if(Y>mid)cg(p<<1|1,mid+1,r);
pu(p);
}
inline void build(re int p,re int l,re int r){
sum[p]=r-l+1,tg[p]=inf;
if(l==r){mn[p]=inf-1,pos[p]=l;return;}
re int mid=l+r>>1;
build(p<<1,l,mid),build(p<<1|1,mid+1,r);
pu(p);
}
inline void work(re int x){
for(re int i=0;i<v[x].size();++i)pp[v[x][i]]=i+1;
re int S=v[x].size();
for(re int i=1;i<=S;++i)E[i].clear();
for(re int i=0;i<S;++i)for(re int j=head[v[x][i]];j;j=e[j].next)E[i+1].push_back((node){pp[e[j].to],e[j].w});
for(re int i=1;i<=S;++i)sort(E[i].begin(),E[i].end());
for(re int i=1;i<=S;++i){
build(1,1,S),X=Y=i,Z=0,cg(1,1,S);
for(re int j=1;j<=S;++j){
re int z=pos[1];dis[z]=mn[1];
X=z,del(1,1,S);
re int lst=0;
for(re int k=0;k<E[z].size();++k){
if(E[z][k].x-1>lst)X=lst+1,Y=E[z][k].x-1,Z=dis[z]+a[v[x][z-1]],cg(1,1,S);
X=Y=E[z][k].x,Z=dis[z]+min(a[v[x][z-1]]+min(pmn[x-1],smn[x+1]),E[z][k].y),cg(1,1,S),lst=E[z][k].x;
}
if(lst^S)X=lst+1,Y=S,Z=dis[z]+a[v[x][z-1]],cg(1,1,S);
}
re int mn=a[v[x][i-1]];
for(re int j=1;j<=S;++j)if(j^i)ans+=dis[j],mn=min(mn,dis[j]+a[v[x][j-1]]);
ans+=1ll*mn*(n-v[x].size());
}
}
signed main(){
freopen("happybean.in","r",stdin);
freopen("happybean.out","w",stdout);
fread(rbuf,1,20000000,stdin);
n=read(),m=read();
for(re int i=1;i<=n;++i)a[i]=read(),fa[i]=i;
for(re int i=1,x,y,z;i<=m;++i){
x=read(),y=read(),z=read();
e[++cnt]=(edge){y,head[x],z},head[x]=cnt;
fa[root(x)]=root(y);
}
for(re int i=1;i<=n;++i)v[root(i)].push_back(i);
pmn[0]=inf;
for(re int i=1;i<=n;++i){
pmn[i]=pmn[i-1];
if(i==root(i))for(re int j=0;j<v[i].size();++j)pmn[i]=min(pmn[i],a[v[i][j]]);
}
smn[n+1]=inf;
for(re int i=n;i;--i){
smn[i]=smn[i+1];
if(i==root(i))for(re int j=0;j<v[i].size();++j)smn[i]=min(smn[i],a[v[i][j]]);
}
for(re int i=1;i<=n;++i)if(i==root(i))work(i);
printf("%lld",ans);
}

浙公网安备 33010602011771号