res
rk24/73 100+60+50+12
rk1 100+100+100+36
rk10 ycx 100+100+60+12
开挂
考场写的线段树二分,空间复杂度不对但过了
考场代码
const int N = 1e6+5, X = 1e9+1e6;
int n,a[N],b[N];
ULL ans;
#define mid ((l+r)>>1)
#define ls(u) t[u].ch[0]
#define rs(u) t[u].ch[1]
#define up(u) t[u].siz=t[ls(u)].siz+t[rs(u)].siz
int rt,ind;
struct Node { int ch[2],siz; } t[int(1.8e7)];
int insert(int x,int &u=rt,int l=1,int r=X) {
if( r < x || t[u].siz == r-l+1 ) return -1;
if( !u ) u = ++ind;
if( l == r ) return ++t[u].siz, l;
int res = insert(x,ls(u),l,mid); if( res == -1 ) res = insert(x,rs(u),mid+1,r);
up(u); return res;
}
#undef mid
#undef ls
#undef rs
#undef up
signed main() {
freopen("openhook.in","r",stdin);
freopen("openhook.out","w",stdout);
read(n); For(i,1,n) read(a[i]); sort(a+1,a+n+1,greater<int>());
For(i,1,n) read(b[i]); sort(b+1,b+n+1);
For(i,1,n) a[i] = insert(a[i])-a[i]; sort(a+1,a+n+1,greater<int>());
For(i,1,n) ans += (ULL)a[i] * b[i];
write(ans);
// cerr<<sizeof(t)/1024.0/1024<<endl;
return 0;
}
考虑算出每个数最后加到多少,一个比较好的做法是栈。将 \(a_i\) 排序后从小到大扫,如果当前这个值有数就入栈,否则为栈顶分配当前值并出栈,栈空时直接跳到下一个数所在值。时间复杂度 \(O(n\log n)\),瓶颈在于排序
wcr
#include<bits/stdc++.h>
#define lt long long
#define inf 0x3f3f3f3f3f3f3f3f
using namespace std;
inline lt rd(){
lt x=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')f=-1; ch=getchar(); }
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+(ch^48); ch=getchar(); }
return x*f;
}
const int N=2000060;
int n;
lt da[N],db[N];
lt mn,mx;
lt stk[N],top;
lt ans[N],ant;
int main(){
freopen("openhook.in","r",stdin);
freopen("openhook.out","w",stdout);
n=rd();
// mn=0x3f3f3f3f;
for(int i=1;i<=n;++i){
da[i]=rd();
mn=min(da[i],mn);
mx=max(da[i],mx);
}
for(int i=1;i<=n;++i) db[i]=rd();
sort(db+1,db+1+n);
sort(da+1,da+1+n);
lt ii=mn,jl=0;
while(jl<n||top>=1){
if(da[jl+1]==ii){
while(da[jl+2]==ii){
stk[++top]=ii;
jl++;
}
jl++;
ii++;
}
else if(top){
ans[++ant]=ii-stk[top--];
ii++;
}
else ii=da[jl+1];
}
sort(ans+1,ans+ant+1);
unsigned long long ens=0,ac;
for(int i=1;i<=ant;++i){
ac=ans[i]*db[ant-i+1];
ens+=ac;
}
printf("%llu\n",ens);
return 0;
}
叁仟柒佰万
结论:合法的划分方案中每个子区间的 \(mex\) 一定是全局 \(mex\)
证明:设全局 \(mex\) 为 \(x\),每个子区间的 \(mex\) 为 \(y\),若 \(y>x\) 显然不合法;若 \(y<x\),由于 \(y+1\) 一定出现过,因此它归属的子区间 \(mex\) 一定大于 \(y\),不合法。
然后就是比较显然的 DP。设 \(f[i]\) 为以 \(i\) 为区间右端点的方案数,合法的左端点 \(j\) 一定是一段前缀(\(i\) 一定时左端点对应 \(mex\) 单调),用桶即可维护出最右的合法左端点,前缀和即可 \(O(1)\) 转移。时空复杂度 \(O(n)\),略微卡空间
code
const int N = 3.7e7+5, mod = 1e9+7;
int n,mex,a[N],f[N],cnt[N];
void MAIN() {
read(n);
if( n < 3.7e7 ) For(i,1,n) read(a[i]), cnt[a[i]] = 1;
else {
int x,y; read(x,y);
cnt[a[1] = 0] = 1; For(i,2,n) cnt[a[i] = (a[i-1] * x + y + i) & 262143] = 1;
} mex = 0; while( cnt[mex] ) ++mex;
mem(cnt,0,n), f[0] = 1;
For(i,1,n, j = 1, k = 0) {
++cnt[a[i]]; while( cnt[k] ) ++k;
if( k == mex ) {
while( (a[j] > mex || cnt[a[j]] > 1) && j < i ) --cnt[a[j++]];
f[i] = f[j-1] + f[i-1]; if( f[i] >= mod ) f[i] -= mod;
} else f[i] = f[i-1];
}
write((f[n]-f[n-1]+mod)%mod);
} signed main() {
freopen("clods.in","r",stdin);
freopen("clods.out","w",stdout);
int T; read(T); while( T-- ) {
mex = 0, mem(f,0,n), mem(cnt,0,n);
MAIN();
}
return ocl();
}
超级加倍
实际上点分治可以做到 \(\log^2\)。对于当前分治重心,把它各子树的链信息记录成 \(u,mx,mn\) 的形式,即点 \(u\) 到分治重心路径上最值为 \(mx,mn\),两条链能拼成合法路径(以 \(u\) 为最大值,\(v\) 为最小值)需要满足 \(u=mx_{u},v=mn_{v},u>mx_{v},mn_{u}>v\),典型的二维偏序,排序后 BIT 维护。
会多算 \(u,v\) 在同一子树的情况,减去每个子树单独的答案即可。
但点分治无法优化,且没有用到题目的性质。考虑建两棵 kruskal 重构树(或者叫树上笛卡尔树),使得 \(lca(u,v)\) 为 \(u,v\) 路径上的最值,那么 \(u,v\) 合法的条件为一棵树上 \(u\) 时 \(v\) 的祖先,另一棵树上 \(v\) 是 \(u\) 的祖先,预处理出一棵树的 dfs 序,dfs 另一棵树时 BIT 维护答案即可。时间复杂度 \(O(n\log n)\),注意 vector
存图会被卡常
code
const int N = 2e6+5;
int n,ind,mm,fa[N],head[N],to[N],nxt[N],in[N],out[N];
LL ans;
VI e[N];
struct BIT {
int t[N];
void add(int i,int x) { for(;i<=n;i+=i&-i)t[i]+=x; }
int sum(int l,int r) {
int res=0; for(--l;r>l;r-=r&-r)res+=t[r];
for(;l>r;l-=l&-l)res-=t[l]; return res;
}
} t;
int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); }
void adde(int x,int y) { to[++mm] = y, nxt[mm] = head[x], head[x] = mm; }
void dfs1(int u) {
in[u] = ++ind;
for(int i = head[u]; i; i = nxt[i]) dfs1(to[i]);
out[u] = ind;
}
void dfs2(int u) {
ans += t.sum(in[u],out[u]), t.add(in[u],1);
for(int i = head[u]; i; i = nxt[i]) dfs2(to[i]);
t.add(in[u],-1);
}
signed main() {
freopen("charity.in","r",stdin);
freopen("charity.out","w",stdout);
read(n); For(i,1,n, x=0) { read(x); if( x ) e[i].pb(x), e[x].pb(i); }
For(i,1,n) fa[i] = i;
For(i,2,n) for(auto j : e[i]) if( j < i ) adde(i,find(j)), fa[fa[j]] = i;
dfs1(n);
mm = 0; For(i,1,n) fa[i] = i, head[i] = 0;
rFor(i,n-1,1) for(auto j : e[i]) if( j > i ) adde(i,find(j)), fa[fa[j]] = i;
dfs2(1);
write(ans);
return ocl();
}
欢乐豆
如果 \(m=0\),从 \(u\) 出发到其他点的最短路一定是 \(a_u\),答案即为 \((n-1)\sum a_{u}\)
\(m\ne0\) 时影响的点不超过 \(6000\) 个,因此可以把这些点和被改变的边拿出来,这些点之间的最短路单独算,其他最短路同上
有一个细节是边 \((u,v)\) 可能被改的很大,那么最短路 \(u,v\) 可能是 \(u,x,v\),其中 \(x\) 是未被影响的点,显然选取的一定是 \(a\) 最小的 \(x\),把 \(x\) 也拿出来即可
问题变为一张 \(n\le6000,m\le3000\) 的有向图,边有边权,若边 \((u,v)\) 不在图上则边权为 \(a_{u}\),求全源最短路。
以每个点为起点跑一遍魔改 dij。松弛时,\(u\) 的出边将 \([1,n]\) 分成若干段,端点处边权给定,段内边权为 \(a_u\)。直接上线段树也行。
更好的做法是将 \(dis_{u}+a_{u}\) (即 \(u\) 能更新的段内点最短路)也放入堆中,那么每个点每次作为段内点被更新时路径长度不降,因此只有第一次更新有效,可以并查集维护未被更新的段内点(具体见代码,比较难说清楚)。端点和段数均为 \(O(m)\),因此复杂度仍为 \(O(nm\log m)\)
code
const int N = 1e5+5, M = 6e3+5;
int n,m,ind,a[N],id[M],pos[N],fa[M];
LL ans,mn,dis[N];
bool vis[N];
vector<PII> e[M];
struct Node {
int id; LL dis; bool op;
bool operator < (const Node &x) const { return dis > x.dis; }
};
priority_queue<Node> pq;
int find(int x) { return fa[x]==x ? x : fa[x]=find(fa[x]); }
void add(int u,LL d) {
if( fa[u] != u ) return; // 被更新过
dis[u] = d, fa[u] = u+1;
pq.push(Node{u,dis[u]+a[id[u]],1}); // u能更新的段内点最短路
for(auto i : e[u]) if( dis[u]+i.se < dis[i.fi] ) // 端点
pq.push(Node{i.fi, dis[i.fi]=dis[u]+i.se ,0});
}
void dij(int s) {
mem(dis,0x3f,ind); For(i,1,ind) fa[i] = i; fa[ind+1] = ind+1;
pq.push(Node{s,dis[s]=0,0});
while( pq.size() ) {
auto u = pq.top(); pq.pop();
if( !u.op ) add(u.id,u.dis); // 正常最短路
else { // 更新段内点
for(auto i : e[u.id]) vis[i.fi] = 1; // 标记端点
for(int i = find(1); i <= ind; i = find(i+1)) if( !vis[i] )
add(i,u.dis);
for(auto i : e[u.id]) vis[i.fi] = 0;
}
}
}
signed main() {
freopen("happybean.in","r",stdin);
freopen("happybean.out","w",stdout);
read(n,m);
a[0] = 1e9; For(i,1,n) read(a[i]);
For(i,1,m, x=0,y=0,z=0) {
read(x,y,z);
if( !pos[x] ) pos[ id[++ind]=x ] = ind;
if( !pos[y] ) pos[ id[++ind]=y ] = ind;
e[pos[x]].pb(pos[y],z);
}
For(i,1,n) if( !pos[i] && a[i] < a[mn] ) mn = i;
if( mn ) pos[ id[++ind]=mn ] = mn;
For(i,1,n) if( !pos[i] ) ans += (n-1ll) * a[i];
For(i,1,ind) {
dij(i), mn = 1e9;
For(j,1,ind) ans += dis[j], ckmin(mn,dis[j]+a[id[j]]);
ans += (n-ind) * mn;
}
write(ans);
return ocl();
}