2023.7.20模拟赛题解

由于这次模拟赛题目我改完了质量挺高,因此来总结一下讲解题目。

T1

T1貌似是一个学长之前的一场模拟赛题目

题意:

给定一个长度为 \(n(n\le 10^6)\)\(01\) 串,求有多少个长度不小于 \(3\) 的区间 \([l,r]\) 满足:

  1. \((l,r)\) 的范围内能够选择一个 \(1\),我们称之为 \(mid\)
  2. 区间 \([l,mid]\) 和区间 \([mid,r]\)\(1\) 的个数相同。

思路:

看数据范围显然要用时间复杂度为 \(O(n)\) 的算法通过,先看看暴力怎么打。

首先转化一下题意,相当于在求有多少个区间有奇数个 \(1\)

直接枚举 \(l\),向右扩展 \(r\),统计奇数个 \(1\) 的区间,时间复杂度 \(O(n^3)\)

显然可以用前缀和维护一下 \(1\) 的个数, \(O(1)\) 求区间 \(1\) 的个数的奇偶性,时间复杂度为 \(O(n^2)\)

可以给每个 \(1\) 维护一个 \(ne\),存储到下一个 \(1\) 的距离,再分别维护一下奇数个和偶数个 \(1\)\(ne\) 的和。

统计答案时分成奇偶两种情况统计,遍历整个区间中的每个 \(1\),统计到左边第一个 \(1\) 为止 \(0\) 的个数和到当前 \(1\) 的奇偶性,就可以 \(O(n)\) 求解答案。

说了这么多不如直接看代码

代码

#include<iostream>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=1e6+10;
ll n,ne[N],cnt;
ll s[N],one[N],sum[2];
char a[N];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int T;
    cin>>T>>n;
    for(int i=1;i<=n;i++)
    {
    	cin>>a[i];
    	if(a[i]=='1')one[++cnt]=i;
    }
    one[cnt+1]=n+1;
    for(int i=1;i<=cnt;i++)ne[i]=one[i+1]-one[i];
    for(int i=1;i<=cnt;i+=2)sum[1]+=ne[i];
    for(int i=2;i<=cnt;i+=2)sum[0]+=ne[i];
    ll res=0;
    cnt=0;
    for(int i=1,j=1;i<=n;i++)
    {
        cnt++;//维护到左边一个1为止0的个数
        if(a[i]=='1')
        {
			sum[j&1]-=ne[j];//对于已经遍历到的点,左边部分要删去
            res+=(cnt-1)*(ne[j]-1)+cnt*sum[j&1];//分别统计奇数个1和偶数个1贡献的答案
            j++;
            cnt=0;
        }
    }
    cout<<res;
    return 0;
}

T2

题意

给定 \(n(n \le 10^5)\) 个人的坐标 \((x_{preson},y_{preson})\),以及 \(n\) 个出口的坐标 \((x_{exit},y_{exit})\),每个出口只能通行一个人并且每个人只能从满足 \(x_{preson}\le x_{exit}\wedge y_{preson}\le y_{exit}\) 的出口通过,保证数据合法并且保证所有 \(x\)\(y\) 均不同,求最多有多少人能从出口通过。

时间限制:500 ms

思路

显然是一道贪心+二位偏序问题。或许这类问题很广,需要总结总结。

根据数据范围,我们需要时间复杂度为 \(O(n\log n)\) 的算法才能通过本题。

根据二位偏序的一般套路,我们可以将人和出口一起进行一个双关键字降序排序。然后分成两种情况遍历所有点:

  1. 当前点是出口坐标,设出口横坐标为 \(x_{exit}\),它后面的所有人的横坐标最大值为 \(x_{pre}\)。由于是降序排列,所以一定满足 \(x_{exit}\ge x_{pre}\),因此可以直接用数据结构维护当前点的纵坐标 \(y_{exit}\)
  2. 当前点是人的坐标,查一下数据结构中是否有满足条件的最小的纵坐标。如果有,就统计结果并把满足条件的最小纵坐标删去即可。

以上过程就是本题的正解,数据结构可以用线段树、树状数组等实现。由于 set 比较好写,所以本蒟蒻用 set 实现本题,具体细节请看代码。

代码

#include<iostream>
#include<set>
#include<algorithm>
using namespace std;
typedef pair<int,int>PII;
#define x first
#define y second
const int N=1e5+10;
int n;
struct Node
{
    int x,y,type;
    bool operator<(const Node&A)const
    {
        if(x!=A.x)return x>A.x;//重载运算符为双关键字排序
        return y>A.y;
    }
}p[N<<1];
set<int>S;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0),cout.tie(0);
    int num;
    cin>>num>>n;
    for(int i=1;i<=n;i++)cin>>p[i].x>>p[i].y,p[i].type=0;
    for(int i=n+1;i<=n*2;i++)cin>>p[i].x>>p[i].y,p[i].type=1;
    sort(p+1,p+1+n*2);
    int res=0;
    for(int i=1;i<=n*2;i++)
        if(p[i].type)S.insert(p[i].y);
        else 
        {
            auto it=S.lower_bound(p[i].y);
            auto t=*it;
            if(it!=S.end())
            {
                res++;
                S.erase(it);
            }
        }
    cout<<res;
    return 0;
}

T3

P.S.本题代码参考了potrem大佬的博客

题意

给定一个大小为 \(n\times m(n,m\le 12)\) 的图案,图案全部由大写字母组成,对于这个图案能够进行两种操作:

  1. 交换其中两行
  2. 交换其中两列

求能否通过有限次操作(可能为 \(0\))使得该图案中心对称,多组测试数据 \(T\le 10\)
时间限制:500 ms

思路

本人考场上没来得及看这一题,但是 dfs (希望有时间补)能通过本题,不过也比较难写,在这里稍微讲一下。

直接暴力枚举预处理一下所有能够匹配的行和列,实现时可以 sort 一边方便实现,应该也可以 hash 实现。

首先每次 dfs 最中间的行,因为最中间的行不需要匹配,美剧数量较少。其次,每次枚举上一行和与最中间对称的下一行,找到能够匹配的两行,再递归下一行,否则说明不合法,返回。

当递归出一种合法结果后,再 dfs 最中间的列,操作与行同理。当列也匹配完后,check 一下匹配结果,如果不同直接返回即可,否则更改 flag,输出结果即可。

\(n\)\(m\) 看作是同阶的,那么预处理时间复杂度为 \(O(n^3\log n)\),dfs 行和列的总复杂度应该是 \(O(n^6)\),check函数是 \(O(n^2)\)

总复杂度看起来很高,但是程序很难跑满,其实在数据面前跑的还是挺快的,每个测试点能在不超过 5 ms 的速度跑完。

代码

#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
const int N=15;
int n,m;
int c_col[N],c_row[N];//列行
bool used_col[N],used_row[N];
bool st_col[N][N],st_row[N][N];
char mp[N][N];
bool flag;
int tmp1[N],tmp2[N];
int read()
{
    int x=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x;
}
void check()
{
    for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
            if(mp[c_row[i]][c_col[j]]!=mp[c_row[n-i+1]][c_col[m-j+1]])return;
    flag=1;
}
void dfs_col(int col,int type)
{
    if(flag)return;//找到答案直接返回
    if(!col)return check(),void();//枚举完检查是否合理
    if(type)
        for(int i=1;i<=m;i++)
        {
            used_col[i]=1;
            c_col[col]=i;
            dfs_col(col-1,0);
            used_col[i]=0;
        }
    else 
        for(int i=1;i<=m;i++)
            if(!used_col[i])
                for(int j=i+1;j<=m;j++)
                    if(!used_col[j]&&st_col[i][j])
                    {
                        used_col[i]=used_col[j]=1;
                        c_col[col]=i,c_col[m-col+1]=j;
                        dfs_col(col-1,0);
                        used_col[i]=used_col[j]=0;
                    }
}
void dfs_row(int row,int type)
{
    if(flag)return;//已经找到一组解就返回
    if(!row)return dfs_col((m+1)/2,m&1),void();//行排列完之后枚举列
    if(type)//当前是奇数行
        for(int i=1;i<=n;i++)//枚举每一行
        {
            used_row[i]=1;//标记已经选出当前行
            c_row[row]=i;//标记选出的行编号为当前行
            dfs_row(row-1,0);//递归寻找上一行
            used_row[i]=0;//恢复现场
        }
    else //当前是偶数行
        for(int i=1;i<=n;i++)
            if(!used_row[i])//如果当前行还没有被选
                for(int j=i+1;j<=n;j++)//枚举它后面的每一行哪一行可以和它匹配
                    if(!used_row[j]&&st_row[i][j])//如果当前行没有被选并且与之前选出的那一行匹配
                    {
                        used_row[i]=used_row[j]=1;//标记两行被使用
                        c_row[row]=i,c_row[n-row+1]=j;//分配两行到两边
                        dfs_row(row-1,0);//递归求上一行
                        used_row[i]=used_row[j]=0;//恢复现场
                    }
}
int main()
{
    int num=read(),T=read();
    while(T--)
    {
        n=read(),m=read();
        for(int i=1;i<=n;i++)  //存储图
            scanf("%s",mp[i]+1);
        for(int i=1;i<=n;i++)
            for(int j=1;j<=n;j++)//枚举两行
            {
                for(int k=1;k<=m;k++)//取出两行
                {
                    tmp1[k]=mp[i][k];
                    tmp2[k]=mp[j][k];
                }
                st_row[i][j]=1;
                sort(tmp1+1,tmp1+1+m),sort(tmp2+1,tmp2+1+m);//排序方便比较
                for(int k=1;k<=m;k++)
                    if(tmp1[k]!=tmp2[k])
                        st_row[i][j]=0;//如果两行集合不同,标记两行集合不同
            }
        for(int i=1;i<=m;i++)
            for(int j=1;j<=m;j++)//枚举两列
            {
                for(int k=1;k<=n;k++)//取出两列
                {
                    tmp1[k]=mp[k][i];
                    tmp2[k]=mp[k][j];
                }
                st_col[i][j]=1;
                sort(tmp1+1,tmp1+1+n),sort(tmp2+1,tmp2+1+n);//同上
                for(int k=1;k<=n;k++)
                    if(tmp1[k]!=tmp2[k])
                        st_col[i][j]=0;//如果两列集合不同,标记两列集合不同
            }
        flag=0;//多测不清空,爆零两行泪
        dfs_row((n+1)/2,n&1);//先枚举最中间的一行
        puts(flag?"YES":"NO");
    }
    return 0;
}

貌似这道题还有一些随机算法也能过,crimson000神在考场上用模拟退火怎么还有SA成功切掉本题,成为本次模拟赛唯一一位切掉 T3 的神。让我们一起%%%他

经过大佬的许可,本帖把本题的退火代码放在这里,有问题直接去洛谷上 %%他 问他就好了,本蒟蒻不保证能解答哦

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;

inline ll read()
{
    ll 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 * 10 + ch - '0';
        ch = getchar();
    }
    return x * f;
}

const int N = 14;
char cp[N][N], a[N][N];
int n, m;
bool flag;

inline int calc()
{
    ll res = 0;
    for(int i = 1; i <= n; i ++ )
        for(int j = 1; j <= m; j ++ )
            res += (a[i][j] != a[n - i + 1][m - j + 1]);
    return res;
}

mt19937 rnd(time(0));
inline int rand(int l, int r)
{
    return rnd() % (r - l + 1) + l;
}

inline void swap(int x, int y, int op)
{
    if(!op)
    {
        for(int i = 1; i <= n; i ++ )
            swap(a[x][i], a[y][i]);
    }
    else
    {
        for(int i = 1; i <= m; i ++ )
            swap(a[i][x], a[i][y]);
    }
}

inline void SA()
{
    int st = calc();
    if(!st) flag = true;
    for(double t = 1000; t >= 0.1; t *= 0.99)
    {
        int op = rand(0, 1);
        if(!op)
        {
            int x = rand(1, n);
            int y = rand(1, n);
            swap(x, y, op);
            int nst = calc();
            if(!nst) flag = true;
            int delta = nst - st;
            if(exp(-delta / t) < (double)rand() / RAND_MAX)  
                swap(x, y, op);
            else st = nst;
        }
        else
        {
            int x = rand(1, m);
            int y = rand(1, m);
            swap(x, y, op);
            int nst = calc();
            if(!nst) flag = true;
            int delta = nst - st;
            if(exp(-delta / t) < (double)rand() / RAND_MAX)  
                swap(x, y, op);
            else st = nst;
        }
    }
    if(!st) flag = true;
}

int main()
{
    #ifdef LOCAL
        freopen("in.in", "r", stdin);
        freopen("out.out", "w", stdout);
    #endif
    
    // double st = clock();

    int idd = read();
    int T = read();

    while(T -- )
    {
        flag = false;
        n = read(), m = read();
        for(int i = 1; i <= n; i ++ ) scanf("%s", cp[i] + 1);

        for(int i = 1; i <= 100; i ++ ) 
        {
            memcpy(a, cp, sizeof cp);
            SA();
            if(flag) 
            {
                puts("YES");
                break;
            }
        }
        
        if(!flag) puts("NO");
    }

    // cerr << (clock() - st) / CLOCKS_PER_SEC << endl;

    return 0;
}

T4

推荐jzp学长关于本题的题解,绝对写得比我清楚

题意

\(n(n\le 10^{18})\) 个人,\(0\) 时刻每个人都在自己的家乡 \(i\) 号城市。每经过 \(1\) 单位时间,位于城市 \(i\) 的所有人都会去城市 \(a_i\),并且整个过程中 \(a_i\) 不变,规定 \(i \ne a_i\)
求是否存在至少一组合法 \(a_i\),满足 \(k(k\le 10^{15})\) 天后,所有人同时重新回到家乡(可以途径家乡)。多组测试数据 \(T\le 10^4\)

保证一个测试点中不同的 \(k\) 的个数至多为 \(50\)

时间限制:5 s

思路

转化题意为:对于给定数 \(k\) 的所有质因子 \(p_1,p_2,p_3,\dots,p_m\),求能否找到一组解 \(x\) 满足

\[p_1x_1\cdot p_2x_2\cdot \dots \cdot p_mx_m=n \]

\(k\) 进行分类讨论:

  1. 只有 \(1\) 个质因子,暴力取模看是否为 \(0\) 即可。
  2. \(2\) 个质因子,可以考虑用扩展欧拉定理(暂时没有写博客,也许以后会补)
  3. 不少于 \(3\) 个质因子,考虑用同余最短路(以后可能会补)求解。

由于 \(k\) 的个数不多于 \(50\),可以考虑记忆化减小常数。

具体细节还是参考 jzp 学长的博客为妙。

代码

参考了xyc1719大佬的题解

#include<iostream>
#include<queue>
#include<cstring>
using namespace std;
typedef long long ll;
const int N=3.2e7+10,K=60,P=60;
const int MIP=1e5+10;
const ll inf=1ll<<60;
typedef pair<ll,int>PLI;
#define x first
#define y second
ll read()
{
    ll x=0;
    char ch=getchar();
    while(!isdigit(ch))ch=getchar();
    while(isdigit(ch))x=x*10+ch-'0',ch=getchar();
    return x;
}
ll primes[N],max_p[N];//存储质数和每个数的最大质因数
bool st[MIP];
ll p[K][P],k[K],cnt;
void init()
{
    for(int i=2,cnt=0;i<N;i++)//线性筛质数
    {
        if(!max_p[i])primes[++cnt]=max_p[i]=i;
        for(int j=1;j<=cnt&&primes[j]<=max_p[i];j++)
        {
            if(i*primes[j]>=N)break;
            max_p[i*primes[j]]=primes[j];
        }
    }
}
ll tot[N];
void div(ll k)
{
    for(int i=1;primes[i]*primes[i]<=k;i++)
        if(k%primes[i]==0)
        {
            p[cnt][++tot[cnt]]=primes[i];
            while(k%primes[i]==0)k/=primes[i];
        }
    if(k>1)p[cnt][++tot[cnt]]=k;
}
ll dist[K][MIP];
void dijkstra(int cnt)
{
    memset(st,0,sizeof st);
    for(int i=0;i<p[cnt][1];i++)dist[cnt][i]=inf;
    dist[cnt][0]=0;
    priority_queue<PLI>q;
    q.push({0,0});
    while(q.size())
    {
        auto t=q.top();
        q.pop();
        int ver=t.y;
        if(st[ver])continue;
        ll distance=t.x;
        for(int i=2;i<=tot[cnt];i++)
        {
            ll d=(ver+p[cnt][i])%p[cnt][1];
            if(dist[cnt][d]>distance+p[cnt][i])
            {
                dist[cnt][d]=distance+p[cnt][i];
                q.push({dist[cnt][d],d});
            }
        }
    }
}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
    if(!b)
    {
        x=1,y=0;
        return a;
    }
    ll d=exgcd(b,a%b,y,x);
    y-=a/b*x;
    return d;
}
int main()
{
    init();
    ll NUM=read(),T=read();
    while(T--)
    {
        ll n=read(),kk=read();
        int id=0;
        for(int i=1;i<=cnt;i++)
            if(k[i]==kk)id=i;
        if(!id)
        {
            id=++cnt;
            k[id]=kk;
            div(kk);
            ll tmp=kk;
            if(tot[id]>2)dijkstra(id);
        }
        bool flag=0;
        for(int i=1;i<=tot[id];i++)
            if(n%p[id][i]==0)
            {
                flag=1;
                puts("YES");
                break;
            }
        if(flag)continue;
        if(tot[id]<=1)
        {
            puts("NO");
            continue;
        }
        if(tot[id]==2)
        {
            ll x,y;
            exgcd(p[id][1],p[id][2],x,y);
            y=(y%p[id][1]+p[id][1])%p[id][1];
            ll tmp=y*(n%p[id][1])%p[id][1]*p[id][2];
            puts(tmp<=n?"YES":"NO");
            continue;
        }
        int tmp=n%p[id][1];
        puts(dist[id][tmp]<=n?"YES":"NO");
    }
    return 0;
}

本博客立的 flag

  1. 贪心和二位偏序一类问题
  2. dfs 补坑
  3. 欧拉定理与扩展欧拉定理
  4. 最短路的更多应用(同余最短路与分层图最短路)

我怎么写了这么多呀(逃

posted @ 2023-07-20 20:46  week_end  阅读(26)  评论(0)    收藏  举报