春季测试 2023 题解
A.涂色游戏
题目描述
\(T\) 组数据,给定 \(n\) 行 \(m\) 列的网格,初始颜色全为 \(0\) 。
接下来 \(q\) 次操作:
0 x c:将第 \(x\) 行所有格子染为颜色 \(c\) 。1 x c:将第 \(x\) 列所有格子染为颜色 \(c\) 。
后染的颜色会覆盖先染的颜色,求每个格子的颜色。
数据范围
- \(1\le T\le 10,1\le n,m\le10^5,0\le q\le10^5,0\le c\le10^9,1\le\sum nm,\sum q\le 10^6\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
还在写时光倒流并查集?时间戳轻松搞定!
对每行每列记录最后一次染色的时间和颜色,每个格子取行列中时间戳较大的一个即可。
时间复杂度 \(\mathcal O(\sum nm+\sum q)\) 。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1e5+5;
int c,m,n,q,t,x,op;
int tim[2][maxn],val[2][maxn];
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1;i<=n;i++) tim[0][i]=val[0][i]=0;
for(int i=1;i<=m;i++) tim[1][i]=val[1][i]=0;
for(int i=1;i<=q;i++)
{
scanf("%d%d%d",&op,&x,&c);
tim[op][x]=i,val[op][x]=c;
}
for(int i=1;i<=n;i++)
for(int j=1;j<=m;j++)
printf("%d%c",tim[0][i]>=tim[1][j]?val[0][i]:val[1][j],j!=m?' ':'\n');
}
return 0;
}
B.幂次
题目描述
给定 \(n,k\) ,求 \([1,n]\) 中有多少正整数 \(x\) 可以表示成 \(a^b\) 的形式,其中 \(a,b\in\N^+,b\ge k\) 。
数据范围
- \(1\le n\le 10^{18},1\le k\le 100\) 。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{1GB}\) 。
分析
\(k=1\) 直接输出 \(n\) 。
\(k\ge 3\) 时 \(a\le n^\frac 13\) 且答案不超过 \(\mathcal O(n^\frac13)\) ,枚举 \(a,b\) 然后去重即可。
\(k=2\) 时, \(b\ge 3\) 的部分暴力枚举,删掉其中的完全平方数后再加上 \(\lfloor\sqrt n\rfloor\) 即可。
时间复杂度 \(\mathcal O(n^\frac13\log n)\) 。
温馨提示:平方根的精度问题可以用
sqrtl函数解决。
#include<bits/stdc++.h>
#define int long long
using namespace std;
const int maxn=2e6+5,inf=1e18+5;
int k,n,cnt;
int c[maxn];
void mul(int &x,int y)
{
if(y>inf/x) x=inf;
else x*=y;
}
void work(int k)
{
c[cnt=1]=1;
for(int i=2;;i++)
{
int x=1;
for(int j=1;j<=k;j++) mul(x,i);
if(x>n) break;
while(x<=n) c[++cnt]=x,mul(x,i);
}
sort(c+1,c+cnt+1);
cnt=unique(c+1,c+cnt+1)-c-1;
}
int sq(int x)
{
return x*x;
}
signed main()
{
scanf("%lld%lld",&n,&k);
if(k==1) printf("%lld\n",n),exit(0);
work(max(k,3ll));
if(k>=3) printf("%lld\n",cnt);
else
{
int res=cnt+sqrtl(n);
for(int i=1;i<=cnt;i++) res-=sq(sqrtl(c[i]))==c[i];
printf("%lld\n",res);
}
return 0;
}
C.圣诞树
题目描述
给定二维平面上的凸多边形,从纵坐标最大(如果多个点纵坐标相同,取编号最小的一个)的点开始,经过每个顶点恰好一次,求路径总长度最小值,构造方案。
数据范围
- \(3\le n\le 10^3,1\le |x_i|,|y_i|\le 10^7\) ,保证顶点按逆时针顺序给出。
时间限制 \(\texttt{1s}\) ,空间限制 \(\texttt{1GB}\) 。
分析
记起点为 \(x\) ,容易发现路径相交一定不优。
换言之,任意时刻我们经过的点一定是一段包含 \(x\) 的区间,并且当前在某个端点。
\(f_{l,r,0/1}\) 表示当前区间为 \([x-l,x+r]\) ,在左 \(/\) 右端点处的最小路径长度。
转移枚举下一步往哪里走即可,注意为构造方案我们需要记录前驱。
时间复杂度 \(\mathcal O(n^2)\) 。
温馨提示:下面的代码可以通过原题数据,交到 \(\text{UOJ}\) 上记得开
long double。
#include<bits/stdc++.h>
using namespace std;
const int maxn=1005;
const double inf=1e18;
int n,pos=1;
double res=inf,x[maxn],y[maxn];
double f[maxn][maxn][2];
struct node
{
int l,r,op;
}cur,pre[maxn][maxn][2];
inline int id(int x)
{
return (x+n-1)%n+1;
}
inline double dis(int a,int b)
{
return sqrt((x[a]-x[b])*(x[a]-x[b])+(y[a]-y[b])*(y[a]-y[b]));
}
void print(node cur)
{
int l=cur.l,r=cur.r,op=cur.op;
if(l||r) print(pre[l][r][op]);
printf("%d ",id(!op?pos-l:pos+r));
}
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++) scanf("%lf%lf",&x[i],&y[i]);
for(int i=1;i<=n;i++) if(y[i]>y[pos]) pos=i;
for(int l=0;l<n;l++)
for(int r=0;l+r<n;r++)
{
if(!l&&!r) continue;
f[l][r][0]=f[l][r][1]=inf;
for(int i=0;i<=1;i++)
{
int x=l-!i,y=r-i;
if(x<0||y<0) continue;
for(int j=0;j<=1;j++)
{
double now=f[x][y][j]+dis(id(!i?pos-l:pos+r),id(!j?pos-x:pos+y));
if(f[l][r][i]>now) f[l][r][i]=now,pre[l][r][i]={x,y,j};
}
}
}
for(int i=0;i<n;i++)
for(int j=0;j<=1;j++)
if(res>f[i][n-1-i][j])
res=f[i][n-1-i][j],cur={i,n-1-i,j};
print(cur),putchar('\n');
return 0;
}
D.密码锁
题目描述
\(T\) 组数据,给定一个 \(k\times n\) 的矩阵,每一列可以循环滚动。
求 \(\max\limits_{1\le i\le k}(\max\limits_{1\le j\le n}a_{i,j}-\min\limits_{1\le j\le n}a_{i,j})\) 的最小值。
数据范围
-
\(1\le T,k\le 4,1\le a_{i,j}\le 3\cdot 10^4\) ,保证同一个测试点内 \(k\) 相同。
-
对 \(1\le k\le 3\) ,有 \(1\le n\le 5\cdot 10^4,1\le\sum n\le 1.5\cdot 10^5\) 。
-
对 \(k=4\) ,有 \(1\le n\le 10^4,1\le\sum n\le 3\cdot 10^4\) 。
时间限制 \(\texttt{2.5s}\) ,空间限制 \(\texttt{512MB}\) 。
分析
记全局最小值为 \(mn\) ,全局最大值为 \(V=mx\) 。
对 \(k=1\) ,输出 \(mx-mn\) 即可,时间复杂度 \(\mathcal O(Tkn)\) 。
此后显然不会将 \(mn\) 和 \(mx\) 放在同一行。
对 \(k=2\) ,对于每一列,显然较小数字和 \(mn\) 放在同一行,较大数字和 \(mx\) 放在同一行最优,时间复杂度 \(\mathcal O(Tkn)\) 。
对 \(k=3\) ,钦定 \(mn\) 在第一行,枚举 \(mx\) 所在行。
二分答案,对于每一列,枚举所有(符合 \(mn,mx\) 所在行限制)的决策,最后一行有不超过 \(k\) 种取值。
问题转化为在数轴上撒了 \(\mathcal O(kn)\) 个点,点有 \(n\) 种颜色,询问是否存在长为 \(mid\) 的区间覆盖所有颜色。
开桶记录每种颜色的出现次数,扫一遍即可。
时间复杂度 \(\mathcal O(T\cdot k\cdot kn\log kn\cdot\log V)\) 。
对 \(k=4\) ,钦定 \(mn\) 在第一行,枚举 \(mx\) 所在行。
依然二分答案,问题转化为平面上有 \(\mathcal O(kn)\) 个点,点有颜色,询问是否存在边长为 \(mid\) 的正方形覆盖所有颜色。
考虑每种颜色会对哪些正方形右下角产生贡献,容易发现这是 \(\mathcal O(k)\) 个正方形的并。给区域所有点权值 \(+1\) , check 等价于询问是否存在一个点的权值为 \(n\) 。
矩形加显然可以用扫描线线段树刻画。
如果直接将矩形并容斥成矩形交,时间复杂度 \(\mathcal O(T\cdot k\cdot2^kn\cdot\log^2V)\) ,无法通过。
但是在扫描线意义下,每种颜色的贡献区间为 \(\mathcal O(k)\) 个线段的并集,考虑暴力维护这些线段。
对每种颜色用 multiset 维护点集,单次操作影响到的线段为 [max(y,pre+mid+1),min(y+mid,nxt-1)] 。
注意清空不要暴力遍历值域,否则会退化成 \(\mathcal O(T\log n\cdot V)\) 。
时间复杂度 \(\mathcal O(T\cdot k\cdot kn\cdot\log^2V)\) ,轻微卡常。
#include<bits/stdc++.h>
#define ls p<<1
#define rs p<<1|1
#define fi first
#define se second
#define mp make_pair
#define pii pair<int,int>
using namespace std;
const int v=3e4,maxn=5e4+5,inf=1e9;
int k,n,t,mn,mx;
int add[v<<2],val[v<<2];
vector<int> a[maxn];
inline void work(int x)
{
a[x].insert(a[x].begin(),a[x].back()),a[x].pop_back();
}
namespace task3
{
int cnt[maxn];
inline bool check(int mid)
{
for(int x=1;x<k;x++)
{
vector<pii> p;
int flg=1;
for(int i=1;i<=n;i++)
{
int cnt=0;
for(int j=1;j<=k;j++,work(i))
if(a[i][0]-mn<=mid&&mx-a[i][x]<=mid)
cnt++,p.push_back(mp(a[i][3-x],i));
flg&=cnt>0;
}
if(!flg) continue;
for(int i=1;i<=n;i++) cnt[i]=0;
sort(p.begin(),p.end());
for(int i=0,j=0,res=0;i<p.size();i++)
{
res+=!cnt[p[i].se]++;
while(j<p.size()&&p[i].fi-p[j].fi>mid) res-=!--cnt[p[j++].se];
if(res==n) return true;
}
}
return false;
}
}
inline void pushadd(int p,int v)
{
add[p]+=v,val[p]+=v;
}
inline void pushdown(int p)
{
if(!add[p]) return ;
pushadd(ls,add[p]),pushadd(rs,add[p]),add[p]=0;
}
inline void pushup(int p)
{
val[p]=max(val[ls],val[rs]);
}
inline void modify(int p,int l,int r,int L,int R,int v)
{
if(L<=l&&r<=R) return pushadd(p,v);
if(L>r||R<l) return ;
int mid=(l+r)/2;
pushdown(p);
modify(ls,l,mid,L,R,v);
modify(rs,mid+1,r,L,R,v);
pushup(p);
}
namespace task4
{
int mid;
struct point
{
int x,y,col;
};
inline bool cmp(const point &a,const point &b)
{
return a.x<b.x;
}
inline bool operator<(const point &a,const point &b)
{
return a.y<b.y;
}
multiset<point> s[maxn];
inline void add(const point &p,int op)
{
#define s s[col]
int x=p.x,y=p.y,col=p.col;
if(op==-1) s.erase(s.find(p));
auto it=s.lower_bound(p);
int nxt=it!=s.end()?(*it).y:inf;
int pre=it!=s.begin()?(*--it).y:-inf;
modify(1,1,v,max(y,pre+mid+1),min(y+mid,nxt-1),op);
if(op==1) s.insert(p);
#undef s
}
inline bool check(int _mid)
{
mid=_mid;
for(int x=1;x<k;x++)
{
vector<point> p;
int u=1+(x==1),v=3-(x==3),flg=1;
for(int i=1;i<=n;i++)
{
int cnt=0;
for(int j=1;j<=k;j++,work(i))
if(a[i][0]-mn<=mid&&mx-a[i][x]<=mid)
cnt++,p.push_back({a[i][u],a[i][v],i});
flg&=cnt>0;
}
if(!flg) continue;
sort(p.begin(),p.end(),cmp),flg=0;
for(int i=0,j=0;i<p.size();i++)
{
add(p[i],1);
while(j<p.size()&&p[i].x-p[j].x>mid) add(p[j++],-1);
if(val[1]==n)
{
flg=1;
break;
}
}
for(int i=1;i<=n;i++) while(!s[i].empty()) add(*s[i].begin(),-1);
if(flg) return true;
}
return false;
}
}
int main()
{
scanf("%d%d",&t,&k);
while(t--)
{
scanf("%d",&n),mn=inf,mx=0;
for(int i=1;i<=n;i++) a[i].resize(k);
for(int j=0;j<k;j++)
for(int i=1;i<=n;i++)
{
scanf("%d",&a[i][j]);
mn=min(mn,a[i][j]),mx=max(mx,a[i][j]);
}
if(k==1) printf("%d\n",mx-mn);
if(k==2)
{
int res=0;
for(int i=1;i<=n;i++)
{
if(a[i][0]>a[i][1]) work(i);
res=max(res,max(a[i][0]-mn,mx-a[i][1]));
}
printf("%d\n",res);
}
if(k>=3)
{
int l=-1,r=mx-mn;
while(r-l>1)
{
int mid=(l+r)/2;
if(k==3?task3::check(mid):task4::check(mid)) r=mid;
else l=mid;
}
printf("%d\n",r);
}
}
return 0;
}
本文来自博客园,作者:peiwenjun,转载请注明原文链接:https://www.cnblogs.com/peiwenjun/p/17320579.html
浙公网安备 33010602011771号