2023.7.20模拟赛题解
由于这次模拟赛题目我改完了质量挺高,因此来总结一下讲解题目。
T1
T1貌似是一个学长之前的一场模拟赛题目
题意:
给定一个长度为 \(n(n\le 10^6)\) 的 \(01\) 串,求有多少个长度不小于 \(3\) 的区间 \([l,r]\) 满足:
- 在 \((l,r)\) 的范围内能够选择一个 \(1\),我们称之为 \(mid\)。
- 区间 \([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)\) 的算法才能通过本题。
根据二位偏序的一般套路,我们可以将人和出口一起进行一个双关键字降序排序。然后分成两种情况遍历所有点:
- 当前点是出口坐标,设出口横坐标为 \(x_{exit}\),它后面的所有人的横坐标最大值为 \(x_{pre}\)。由于是降序排列,所以一定满足 \(x_{exit}\ge x_{pre}\),因此可以直接用数据结构维护当前点的纵坐标 \(y_{exit}\)。
- 当前点是人的坐标,查一下数据结构中是否有满足条件的最小的纵坐标。如果有,就统计结果并把满足条件的最小纵坐标删去即可。
以上过程就是本题的正解,数据结构可以用线段树、树状数组等实现。由于 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)\) 的图案,图案全部由大写字母组成,对于这个图案能够进行两种操作:
- 交换其中两行
- 交换其中两列
求能否通过有限次操作(可能为 \(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\) 满足
对 \(k\) 进行分类讨论:
- 只有 \(1\) 个质因子,暴力取模看是否为 \(0\) 即可。
- 有 \(2\) 个质因子,可以考虑用扩展欧拉定理(
暂时没有写博客,也许以后会补) - 不少于 \(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
- 贪心和二位偏序一类问题
- dfs 补坑
- 欧拉定理与扩展欧拉定理
- 最短路的更多应用(同余最短路与分层图最短路)
我怎么写了这么多呀(逃

浙公网安备 33010602011771号