peiwenjun's blog 没有知识的荒原

春季测试 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;
}

posted on 2023-04-15 09:56  peiwenjun  阅读(16)  评论(0)    收藏  举报

导航