20211110 多校noip27

res

rk24/73 100+60+50+12

rk1 100+100+100+36
rk10 ycx 100+100+60+12

本来题目风格是比较适合我的,但考得很一般,关键是 T3 花的时间太长结果没搞出什么东西

T1 虽然不是正解,但 30min 就写完了,问题不大(记得算空间,差点 MLE)
T2 想到了前缀和优化 DP 和右端点固定时 mex 单调,但一没手模样例,二没中间输出,三没推性质,结果就是全场切的题我只拿了 60pts
T4 也类似,草草写完暴力导致白送的 8pts 没拿到
最失败的是 T3,一眼点分治+二维偏序,想了 30min 也不会优化,也没再想想其他做法。最后 2h 开始写,花了 1h+ 才写完 \(O(n\log^{2}n)\) 的做法,结果写假变成 \(O(n^{2}\log n)\),没细想就去 rush 树套树了,最后给 \(O(n\log^{3}n)\) 卡了 20min 常也没多跑过一个点

我是期望排在多校中前 15,但后三题都被吊打变成前 25 了
前两场模拟赛的垫底算是找回了码力,这场写起来很顺利且没有挂分,最后 40min 还 rush 出树套树(虽然是最简单的单点加矩形求和)
按照这个状态,noip 不会暴毙但也不会爆发,能否冲到 rk1 就看接下来 8 天能否把思维提上来,静心找性质、提取模型、分析复杂度瓶颈,以及最重要的保持心态

变数

开挂

考场写的线段树二分,空间复杂度不对但过了

考场代码
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(m^{2}\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();
}
posted @ 2021-11-10 21:51  401rk8  阅读(115)  评论(1编辑  收藏  举报