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;
}

浙公网安备 33010602011771号