MX 暑假集训 7.20

【宝可梦×初音未来联动曲 第19首】アフターエポックス( After Epochs )【sasakure.UK feat. 初音ミク】

模拟赛,期望 \(100+100+100+25\),实际 \(100+100+15+60\)

T1: Multi-Subject Competition

题意

\(n\) 个人分布于 \(m\) 个组,每个人仅属于一个组且有一个能力值,你需要选出一些组,再在选出的组中每个组挑出同样的人数,使所有挑出的人能力值之和最大。

\(1\le n,m\le 10^5\)

solution

简单题,直接枚举每个组选出的人数即可。

一个组如果人数不够枚举的人数就删掉,用 set 维护,同组内按能力值降序排序,时间复杂度 \(O(n\log n)\)

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<set>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();int f=1;
	while (ch<48) {if(ch=='-')f=-1;ch=getchar();} 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();x*=f;
}

int n,m;
int sz[N],r[N];
vector<int> vec[N];
set<int> s;

int main() {
	freopen("subject.in","r",stdin);
	freopen("subject.out","w",stdout);
	read(n);read(m);
	f(i,1,n) {
		int v,p;
		read(p);read(v);
		vec[p].push_back(v);
		sz[p]++;
	}
	int mx=0,ans=0,sum=0;
	f(i,1,m) {
		mx=max(mx,sz[i]);
		sort(vec[i].begin(),vec[i].end(),[&](int x,int y){return x>y;});
		s.insert(i);
	}
	f(i,1,mx) {
		int sum=0;
		vector<int> did;
		for (int j:s) {
			if (sz[j]<i) {
				did.push_back(j);
				continue;
			}
			r[j]+=vec[j][i-1];
			sum+=max(0,r[j]);
		}
		for (int x:did) s.erase(x);
		ans=max(ans,sum);
	}
	printf("%d\n",ans);
	return 0;
}

T2: [ICPC 2022 Nanjing R] 索道

题意

给定长度为 \(n\) 的代价序列 \(a\),并给出一个最大间距 \(k\),你需要找出一个长度为 \(m\) 的子序列 \(b\),规定 \(b_0=0,b_{m+1}=n+1\),满足 \(0=b_0<b_1<b_2<\dots <b_m<b_{m+1}=n+1\),且对于任意 \(0\le i\le m\),都有 \(b_{i+1}-b_i\le k\),并给定一些位置,你需要保证这些位置必须被选入这个子序列内,一个合法子序列的权值为 \(\sum\limits_{i=1}^{m}a_{b_i}\)

\(q\) 次修改,每次给定 \(p,k\)\(a_p\leftarrow k\),然后求出合法子序列的最小代价,修改间是独立的,即每次修改后为还原会原先的权值序列。

\(1\le n\le 5\times 10^5,1\le k\le 3\times 10^3,1\le q\le 3\times 10^3\)

solution

做过原题,考场上秒了。

考虑静态怎么做,令 \(f_i\) 表示将 \(i\) 选入子序列,\([1,i]\) 满足条件的最小代价,这显然是一个单调队列优化 dp。

由于最大间距 \(k\) 的限制,每次修改后影响的区间并不是很大,所以令 \(g_i\) 表示将 \(i\) 选入子序列,\([i,n]\) 满足条件的最小代价,那么我们可以枚举一个点 \(i\),最小代价就是 \(f_i+g_i-a_i\),若修改了 \(p\),那么 \(f\)\([1,p-1]\) 的值不会改变,\(g\)\([p+1,n]\) 的值不会改变,那么只需要重新计算 \(f\) 数组在 \([p,p+k]\) 的值,再用 \(g\) 数组进行计算,即可得到最小代价。

时间复杂度 \(O(n+qk)\)

Code
#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=5e5+10;
constexpr ll inf=1e18+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int n,m,k;
int a[N];
char s[N];
ll dp1[N],dp2[N];
ll now[N];
deque<int> q;

inline void clear() {while (!q.empty()) q.pop_front();}
inline void solve() {
	read(n);read(k);
	f(i,0,n+1) dp1[i]=dp2[i]=now[i]=a[i]=0;
	s[0]=s[n+1]='1';
	f(i,1,n) read(a[i]); 
	scanf("%s",s+1);
	clear(); 
	q.push_back(0);
	f(i,1,n+1) {
		while (!q.empty()&&q.front()<i-k) q.pop_front();
		dp1[i]=dp1[q.front()]+a[i];
		while (!q.empty()&&dp1[q.back()]>=dp1[i]) q.pop_back();
		if (s[i]=='1') clear();
		q.push_back(i);
	}
	clear();
	q.push_back(n+1);
	F(i,n,0) {
		while (!q.empty()&&q.front()>i+k) q.pop_front();
		dp2[i]=dp2[q.front()]+a[i];
		while (!q.empty()&&dp2[q.back()]>=dp2[i]) q.pop_back();
		if (s[i]=='1') clear();
		q.push_back(i);
	}
	read(m);
	while (m--) {
		int x,y;
		read(x);read(y);
		swap(a[x],y);
		clear();
		ll ans=inf;
		int l=max(0,x-k),r=min(x+k,n+1);
		f(i,l,x-1) {
			now[i]=dp1[i];
			while (!q.empty()&&now[q.back()]>=now[i]) q.pop_back();
			if (s[i]=='1') clear();
			q.push_back(i);
		}
		f(i,x,r) {
			while (!q.empty()&&q.front()<i-k) q.pop_front();
			now[i]=now[q.front()]+a[i];
			while (!q.empty()&&now[q.back()]>=now[i]) q.pop_back();
			if (s[i]=='1') clear();
			q.push_back(i);
			if (i!=x) ans=min(ans,now[i]+dp2[i]-a[i]); 
		} 
		swap(a[x],y);
		printf("%lld\n",ans);
	}
} 

int main() {
	freopen("ropeway.in","r",stdin);
	freopen("ropeway.out","w",stdout);
	int T;
	read(T);
	while (T--) solve();
	return 0;
}

T3: A Simple MST Problem

题意

\(w(x)\)\(x\) 的不同质因子个数,现有编号在 \([l,r]\)\(r-l+1\) 个点,在两个编号分别为 \(i,j\) 的点之间连边的边权为 \(w(\operatorname{lcm} (i,j))\),求将 \([l,r]\) 的点组成点集的最小生成树边权和。

\(1\le l\le r\le 10^6\)

solution

有点搞,暴力 Kruskal 的时候忘记特判一个点了。

考虑 \([l,r]\) 内存在 \(x\) 满足 \(w(x)=1\) 怎么做。

容易发现,一个点 \(x\) 连向其他点的最小边权是 \(w(x)\),令 \(s\) 满足 \(w(s)=1\),那么考虑依次将其他点加入 \(s\) 所在连通块。

\(S_i\)\(i\) 的质因数集合,发现若 \(S_i\subseteq S_j\),那么 \(i\)\(j\) 连边一定不劣,所以我们按 \(S_i\) 大小升序排序,对于每个数 \(x\),若 \(x\)\(s\) 有公共因子,那么 \(x\) 可以直接向 \(s\) 连边,代价为 \(w(x)\),若存在一个点 \(j\)\(s\) 属于同一个连通块,且 \(S_x\subseteq S_j\),那么 \(x\)\(j\) 连边,代价同样为 \(w(x)\),这两种情况外,连边的边权一定大于 \(w(x)\),所以直接将 \(s\)\(x\) 连边,代价为 \(w(x)+1\)

加入一个点 \(i\) 后直接标记 \(S_i\) 的超集,时间复杂度 \(O(r\log r)\),使用一些科技可以优化到 \(O(r\log \log r)\)

\([l,r]\) 内不存在 \(x\) 满足 \(w(x)=1\),说明 \([l,r]\) 内没有质数,而 \([1,n]\) 内质数个数大约为 \(\frac{n}{\log n}\),看起来两个素数间间隔不大。

打个表发现 \(1\le l\le r\le 10^6\) 时,若 \([l,r]\) 内不存在 \(x\) 满足 \(w(x)=1\)\(r-l+1\) 的最大值为 \(113\),所以直接暴力跑最小生成树即可。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
#include<bitset>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=1e6+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

int w[N],v[N];
inline void init(int n) {
	bitset<N> vis;
	f(i,1,n) v[i]=1;
	f(i,2,n) {
		if (vis[i]) continue;
		for (int j=i;j<=n;j+=i) {
			vis.set(j);
			w[j]++;
			v[j]*=i;
		}
	}
}

inline int gcd(int x,int y) {return y?gcd(y,x%y):x;}
inline int W(int x,int y) {return w[x]+w[v[y]/gcd(v[x],v[y])];}

namespace Ff1 {
	struct Edge{int u,v;};
	vector<Edge> es[60];
	
	int f[N];
	inline int find(int x) {
		if (x==f[x]) return x;
		return f[x]=find(f[x]);
	}
	
	inline ll Kruskal(int l,int r) {
		int cnt=1,len=r-l+1;ll sum=0;
		f(i,l,r) f[i]=i;
		f(i,0,50) {
			for (Edge E:es[i]) {
				int u=find(E.u),v=find(E.v);
				if (u==v) continue;
				f[u]=v;sum+=i;cnt++;
				if (cnt==len) {
					f(i,0,50) es[i].clear();
					return sum;
				}
			}
		}
		return 0;
	}
	
	inline void solve(int l,int r) {
		f(i,l,r) f(j,l+1,r) es[W(i,j)].push_back({i,j});
		printf("%lld\n",Kruskal(l,r));
	}
}

namespace Ff2 {
	inline void solve(int l,int r,int s) {
		int sum=0;
		vector<int> did(r+2),tmp;
		f(i,l,r) {
			if (did[v[i]]) sum+=w[v[i]];
			else tmp.push_back(v[i]),did[v[i]]=1;
		}
		f(i,l,r) did[v[i]]=0;
		sort(tmp.begin(),tmp.end(),[&](int x,int y){return w[x]<w[y];});
		for (int i:tmp) {
			if (did[i]||gcd(s,i)!=1) sum+=w[i];
			else sum+=w[i]+1;
			for (int j=i;j<=r;j+=i) did[j]=1;
		}
		printf("%d\n",sum-1);
	}
}

inline void solve() {
	int l,r;read(l);read(r);
	if (l==1) {
		ll sum=0;
		f(i,l,r) sum+=w[i];
		printf("%lld\n",sum);
		return;
	}
	int s=0;
	f(i,l,r) if (w[i]==1) {s=i;break;}
	if (s) Ff2::solve(l,r,s);
	else Ff1::solve(l,r);
}

int main() {
	freopen("mst.in","r",stdin);
	freopen("mst.out","w",stdout);
	int T;init(N-1);
	read(T);
	while (T--) solve();
	return 0;
}

T4: Go go Baron Bunny!

[APIO2017] 斑斓之地

题意

给定一个 \(R\times C\) 的网格图,初始所有网格均为白色,并给定面积为 \(m\) 的连通块,将这 \(m\) 个位置染成黑色,\(q\) 次询问,每次给出一个矩形区域,求出保留这个矩形区域,白色格子构成的连通块个数。

\(1\le R,C\le 2\times 10^5,0\le m\le 10^5,1\le q\le 10^5\)

solution

根据平面图欧拉公式有 \(V-E+F=C+1\),考虑这个图的对偶图,把一个白色网格看作点,向相邻的白色网格连边,然后考虑黑色网格会影响哪些点,边和面。

点直接二维数点,边分为水平和竖直边,面为 \(2\times 2\) 个相邻白点组成的面,若询问矩形完全包含这条蛇,则面数还要加 \(1\)

影响的点就是被染黑的点,影响的边是染黑的点相邻的边,面是以染黑的点为端点的 \(2\times 2\) 的面。

用一棵可持久化线段树做扫描线,维护被减去的点,边,面,即可求出连通块个数。

时空复杂度 \(O(n\log n)\),也可以用离线树状数组直接扫描线做到空间 \(O(n)\),不过原题似乎强制在线所以不能离线树状数组。

代码是交互格式的。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;

int n,m;
int Lx,Rx,Ly,Ry;
int rt[N][4];
vector<int> tmp[N][4];

struct Segment_Tree {
	int tot;
	struct Node{int ls,rs,s;}tr[N<<6];
	#define ls(x) tr[x].ls
	#define rs(x) tr[x].rs
	
	inline int newNode(int x) {
		tr[++tot]=tr[x];
		return tot;
	}
	
	inline void update(int x,int l,int r,int &now) {
		now=newNode(now);
		tr[now].s++;
		if (l==r) return;
		int mid=l+r>>1;
		if (x<=mid) update(x,l,mid,ls(now));
		else update(x,mid+1,r,rs(now));
	}
	
	inline int query(int x,int y,int l,int r,int now) {
		if (x<=l&&y>=r) return tr[now].s;
		int mid=l+r>>1,res=0;
		if (x<=mid) res=query(x,y,l,mid,ls(now));
		if (y>mid) res+=query(x,y,mid+1,r,rs(now));
		return res;
	}
}tr[4];

inline void add(int x,int y,int &Lx,int &Rx,int &Ly,int &Ry) {
	Lx=min(x,Lx);Rx=max(x,Rx);Ly=min(y,Ly);Ry=max(y,Ry);
	tmp[y][0].push_back(x);
	if (y!=1) tmp[y][1].push_back(x);
	if (y!=m) tmp[y+1][1].push_back(x);
	if (x!=1) tmp[y][2].push_back(x);
	if (x!=n) tmp[y][2].push_back(x+1);
	if (x!=1&&y!=1) tmp[y-1][3].push_back(x-1);
	if (x!=n&&y!=1) tmp[y-1][3].push_back(x);
	if (x!=1&&y!=m) tmp[y][3].push_back(x-1);
	if (x!=n&&y!=m) tmp[y][3].push_back(x);
}

int colour(int ar, int ac, int br, int bc) {
	int l1=ar,l2=ac,r1=br,r2=bc;
	ll V=1ll*(r1-l1+1)*(r2-l2+1)-tr[0].query(l1,r1,1,n,rt[r2][0])+tr[0].query(l1,r1,1,n,rt[l2-1][0]);
	ll E=1ll*(r1-l1)*(r2-l2+1)+1ll*(r1-l1+1)*(r2-l2);
	if (l2^r2) E-=tr[1].query(l1,r1,1,n,rt[r2][1])-tr[1].query(l1,r1,1,n,rt[l2][1]);
	if (l1^r1) E-=tr[2].query(l1+1,r1,1,n,rt[r2][2])-tr[2].query(l1+1,r1,1,n,rt[l2-1][2]);
	ll F=1ll*(r1-l1)*(r2-l2)+(l1<Lx&&Rx<r1&&l2<Ly&&Ry<r2);
	if (l1!=r1&&l2!=r2) F-=tr[3].query(l1,r1-1,1,n,rt[r2-1][3])-tr[3].query(l1,r1-1,1,n,rt[l2-1][3]);
	return V-E+F;
}

void init(int _r,int _c,int sr,int sc,int Len,char *S) {
	n=_r;m=_c;int sx=sr,sy=sc;
	Lx=sx,Rx=sx,Ly=sy,Ry=sy;
	add(sx,sy,Lx,Rx,Ly,Ry);
	f(i,0,Len-1) {
		if (S[i]=='N') sx--;
		if (S[i]=='S') sx++;
		if (S[i]=='E') sy++;
		if (S[i]=='W') sy--;
		add(sx,sy,Lx,Rx,Ly,Ry);
	}
	f(i,1,m) f(j,0,3) {
		rt[i][j]=rt[i-1][j];
		sort(tmp[i][j].begin(),tmp[i][j].end());
		tmp[i][j].erase(unique(tmp[i][j].begin(),tmp[i][j].end()),tmp[i][j].end());
		for (int k:tmp[i][j]) tr[j].update(k,1,n,rt[i][j]);
	}
}

Master of Both VII

题意

给定一个正 \(n\) 边形,用 \(n-3\) 条不相交的弦将这个 \(n\) 边形切成 \(n-2\) 个三角形,也就是进行一个三角剖分,你并不知道这 \(n-3\) 条弦对应的端点编号,但是你可以进行以下询问:

  • 给定 \(u,v\),若 \((u,v)\) 是这 \(n-3\) 条弦中的一条,则返回 \(0\),否则返回有多少条弦穿过 \((u,v)\) 这条弦。

你希望在 \(n-3\) 次询问内确定这 \(n-3\) 条弦对应端点编号。

\(1\le n\le 100\)

solution

考虑 \(n\) 次怎么做,对于一个点 \(i\),令 \(w_i\) 表示对 \(i\) 左边的点与右边的点连成的弦对应的返回值。

显然必然存在一个点 \(w_i=0\),令这个点为 \(i_2\),左右的点分别为 \(i_1,i_3\),那么 \((i_1,i_3)\) 对应一条答案弦,且 \(i_2\) 不是任何答案弦的端点,删去 \(i_2\),就变成了一个子问题。

那么 \(w_{i_1}\)\(w_{i_3}\) 怎么更新呢,新的 \(w_{i_1}\) 实际上会减去 \(1\),即减去 \((i_1,i_3)\) 这条弦,由于 \(i_2\) 不作为任何答案弦的端点,所以删掉 \(i_2\) 并不会影响 \(w_{i_1}\) 的值,\(i_3\) 同理,减去 \(1\) 即可(这里画图会直观一些)。

那么缩减到只有三个点就结束了,这样 \(n\) 次就能解决这个问题。

那么如何做到 \(n-3\) 呢,我们考虑不问其中连续 \(3\) 个点的 \(w\),只用其他的点,令这三个点按顺序分为 \(i_1,i_2,i_3\),那么当除这三个点外的点 \(w\) 均不为 \(0\),说明 \(i_2\) 对除 \(i_1,i_2,i_3\) 以外的所有点都有一条答案弦,原因是至少存在两个点 \(w\)\(0\),若 \(w_{i_2}\)\(0\),则 \(w_{i_1}\)\(w_{i_3}\) 均不为 \(0\),所以只可能 \(w_{i_1}=w_{i_3}=0\),加上其他点 \(w\) 均不为 \(0\) 的限制,容易发现只有 \(i_2\)\(i_1,i_2,i_3\) 以外的所有点连边符合条件。

这样就优化到了 \(n-3\) 次。

Code
#include<cstdio>
#include<algorithm>
#include<vector>
using namespace std;
#define ll long long
#define qwq Ff472130
#define f(i,l,r) for (int i=l;i<=r;i++)
#define F(i,l,r) for (int i=l;i>=r;i--)
constexpr int N=2e5+10;
constexpr int inf=1e9+10;

inline void read(int &x) {
	x=0;
	char ch=getchar();
	while (ch<48) ch=getchar(); 
	while (ch>=48) x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
}

inline int ask(int u,int v) {
	printf("? %d %d\n",u,v);
	fflush(stdout);
	int x;read(x);
	return x;
}

vector<pair<int,int>> ans;
inline void answer(int u,int v) {ans.push_back(make_pair(u,v));}

int n;
int tmp[N],L[N],R[N];
inline void solve(vector<int> id) {
	if (id.size()==3) return;
	for (int x:id) {
		if (tmp[x]==0) {
			answer(L[x],R[x]);
			id.erase(lower_bound(id.begin(),id.end(),x));
			int l=L[x],r=R[x];
			L[r]=l;R[l]=r;
			tmp[l]--;tmp[r]--;
			return solve(id);
		}
	}
	for (int x:id) if (x!=n&&x!=1&&x!=n-1) answer(n,x);
}

inline void solve() {
	read(n);
	vector<int> st;
	f(i,1,n) st.push_back(i);
	f(i,2,n-2) {
		tmp[i]=ask(i-1,i+1);
		L[i]=i-1;R[i]=i+1;
	}
	tmp[1]=tmp[n]=tmp[n-1]=inf;
	solve(st);
	printf("! ");
	for (pair<int,int> p:ans) printf("%d %d ",p.first,p.second);
	putchar(10);
	fflush(stdout);
	int rd;read(rd);
	ans.clear();
}

int main() {
	int T;read(T);
	while (T--) solve();
	return 0;
}

额外的题单:

Coloring Game

Maxim and Calculator

posted @ 2026-07-20 15:16  Ff472130  阅读(8)  评论(0)    收藏  举报