2022.7.22 模拟赛
前言
第一次和学长们模拟赛切掉 T1,真的不容易啊。。。。。
T1 数正方体
题目描述
众所周知,正方形有 个点, 条边;正方体有 个点, 条边, 个面,定义点为零维基础图形,线段为一维基础图形,正方形为二维基础图形,正方体为三维基础图形...,那么请问,一个 维基础图形包含多少个 维基础图形呢
多次询问,输出对 取模。
第一行输入一个正整数 表示数据组数。
下接 行,每行两个自然数 ,描述一组数据。
输出 行,每行一个数字表示答案。
对于全部数据,
样例:
输入
7
3 0
3 1
3 2
3 3
48545 1
77625 77624
93574 83513
输出
8
12
6
1
223544257
155250
424453971
解析
赛时手搓了一个及其神奇的结论:
然后过了,当时也是没有严谨的证明,由于自己的数学基础太垃圾,根本不知道什么是四维图形,就干脆,拿一二三维乱搞。
定义 维基础图形的顶点为, 维向量中每一维是 的向量集合(比如说正方体上的顶点就是 )
那么把点再往多扩展一下,不难发现, 维基础图形在 维基础图形上的表现为,某些维取值相同,剩下维取值唯一(是个 维基础图形)
答案就是
代码:
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
const int N = 1e5 + 5;
const int M = 1e5 + 2;
const int p = 998244353;
int inv[N],fac[N],power[N];
namespace Fast{
inline int read(){
bool flag=false;int x=0;char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')flag=true;ch=getchar();}
while(ch>='0'&&ch<='9'){x=(x<<1)+(x<<3)+ch-'0';ch=getchar();}
return flag?-x:x;
}
template<typename T> inline void write(T X){
if(X<0){putchar('-');X=~(X-1);}
int s[200],top=0;
while (X){s[++top]=X%10;X/=10;}
if(!top) s[++top]=0;
while(top) putchar(s[top--]+'0');
return;
}
}
using namespace Fast;
int calc(int m,int n){
if(m==0){
return power[n];
}
if(n==m){
return 1;
}
else{
return (long long)fac[n]*inv[n-m]%p*power[n-m]%p*inv[m]%p;
}
}
int main(){
int T=read();
inv[0]=inv[1]=1;
fac[1]=1;
power[0]=1;
power[1]=2;
for(rint i=2;i<=M;i++){
fac[i]=(long long)fac[i-1]*i%p;
inv[i]=(long long)(p-(p/i))*inv[p%i]%p;
power[i]=(long long)(power[i-1]<<1)%p;
}
for(rint i=2;i<=M;i++) inv[i]=(long long)inv[i]*inv[i-1]%p;
while(T--){
int n=read(),m=read();
write(calc(m,n));
puts("");
}
return 0;
}
T2 数连通块
题目描述
给定一个森林,每个点都有一定概率会消失,一条 的边存在的条件是, 存在且 存在
有若干次询问,每次给定 ,然后把下标不在 的点都删掉后,问剩余点和所有边构成的图的联通块个数的期望
第一行输入三个整数 ,分别表示点的个数和边的个数和询问次数
之后 行,第 行有两个整数 ,表示第 个点存在的概率是 之后 行,每行有两个整数 ,表示存在一条连接 和 的边,保证无重边无自环之后 行,每行两个整数 ,表示一次询问
对于每一次询问,输出一行一个整数表示答案,输出对 取模
对于所有数据,保证:
样例
输入
2 1 1
1 1
1 1
1 2
1 2
输出
1
解析
T2 挂的非常惨,一分没有,后来才发现 取 。(要不然有部分分呢)
考虑到森林中连通块个数的 满足:
证明:每添加一条边,就会合并两个连通块,相应的,连通块的个数就会少一个
根据期望的线性性,可得:
于是问题就转化为了计算 和
为了方便起见,设 表示 的存在的概率
于是对 做前缀和数组 后,可得
之后只需要考虑 如何计算,显然有:
于是可以把每一条 的边看成一个点 ,它的权值是
之后相当于询问一个矩形内的点的权值和,这显然就是一个二维数点问题,离线后树状数组统计即可。
代码:
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
const int mod = 1e9 + 7;
int n,m,q,p[N];
ll pw(ll a,ll b){ll r=1;for(;b;b>>=1,a=a*a%mod)if(b&1)r=r*a%mod;return r;}
ll Sp[N],a[N],ans[N];
vector<int> g[N];
vector<pair<int,int>> que[N];
void add(int x,ll y){for(;x;x-=x&-x)a[x]=(a[x]+y)%mod;}
ll ask(int x){ll r=0;for(;x<=n;x+=x&-x)r=(r+a[x])%mod;return r;}
signed main(){
cin>>n>>m>>q;
for(rint i=1,a,b;i<=n;i++){
cin>>a>>b;
p[i]=a*pw(b,mod-2)%mod;
Sp[i]=(Sp[i-1]+p[i])%mod;
}
for(rint i=1,u,v;i<=m;i++){
cin>>u>>v;
if(u>v) swap(u,v);
g[v].push_back(u);
}
for(rint i=1,l,r;i<=q;i++){
cin>>l>>r;
ans[i]=(Sp[r]-Sp[l-1])%mod;
que[r].push_back(make_pair(l,i));
}
for(rint i=1;i<=n;i++){
for(rint j=0;j<g[i].size();j++){
int v=g[i][j];
add(v,(ll)p[v]*p[i]%mod);
}
for(rint j=0;j<que[i].size();j++){
pair<int,int>t=que[i][j];
(ans[t.second]-=ask(t.first))%=mod;
}
}
for(rint i=1;i<=q;i++){
cout<<(ans[i]%mod+mod)%mod<<endl;
}
return 0;
}
T3 数大模拟
题目描述
有 个连续的格子,一开始某些格子上有棋子,假设格子从 到 进行编号。
你需要进行 轮操作,每一轮操作如下:
•选中所有满足“其左侧相邻的是空格子”的棋子,
•将这些棋子向左移动一格。
比如,对序列 进行一轮操作后,会变为 (其中 表示这个格子上有一个棋子, 表示这是一个空格子)。
请输出操作 轮后,每个格子上是否有士兵(即输出一个长度为 的序列,表示方式与上文相同)。
注:如果有两个相邻格子上都有棋子,那么靠右的棋子不会在这一轮操作中移动。
输入第一行两个整数 ,分别表示格子总数与操作轮数。
第二行 个整数
输出 个整数 ,其中 表示操作 轮后,从左往右第 个格子是否有棋子
样例
输入
6 2
0 1 1 0 1 1
输出
1 1 0 1 1 0
对于 的数据,满足
对于额外 的数据,满足
对于 的数据,满足
解析
赛时没有拿到 ,因为第一题浪费了太多了时间,第二题也写挂掉了,这道题就想了个 的做法。
三十分就是直接暴力就行了,重点在于另外二十分
由于此时 必然大于 ,(不大于的话那不还是暴力
所以 ,那么操作完之后 必然在序列前面,这个时候就可以直接把 都输出出来,再把剩下的 输出。
50pts
代码
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
using namespace std;
const int N = 1e6 + 5;
bool b[N];
int a[N];
int n,k;
int main(){
cin>>n>>k;
if(k>1000){
long long cnt1=0;
long long cnt2=0;
for(rint i=1;i<=n;i++){
cin>>a[i];
if(a[i]==1){
cnt1++;
}
if(a[i]==0){
cnt2++;
}
}
for(rint i=1;i<=cnt1;i++){
cout<<1<<" ";
}
for(rint i=1;i<=cnt2;i++){
cout<<0<<" ";
}
return 0;
}//20pts
for(rint i=1;i<=n;i++){
cin>>a[i];
}
while(k--){
for(rint i=2;i<=n;i++){
if(a[i-1]==0&&a[i]==1){
b[i]=1;
}
}
for(rint i=2;i<=n;i++){
if(b[i]==1){
a[i-1]=1;
a[i]=0;
b[i]=0;
}
}
}//30pts
for(rint i=1;i<=n;i++){
cout<<a[i]<<" ";
}
return 0;
}
再来说说正解。
考虑先把已经归位的 删除,也就是把序列一开始的前缀 都删掉
那么对于每一个棋子,都有一个目标距离,即最终序列的位置
显然这个最早的归位时间是最后一个棋子的归位时间
对于一个棋子,它前面每有一个棋子,则会对它产生晚归位 单位时间的影响
反之,它前面每有一个空格,则会对它产生早归位 单位时间的影响
于是就有一个求得最晚归位时间的代码了,当然也同时可以得知每个棋子的归位时间
int getlen(){
int res=0;
for(rint i=1;i<=n;i++){
if(mp[i]==0){
int tag=0,tot=0;
for(rint j=i;j<=n;j++){
if(mp[j]==1){
tot++;
res=max(res,(j-i+1)-tot+tag);
}
if(mp[j]==0){--tag;}
else{++tag;}
if(tag<0){
tag=0;
}
}
break;
}
}
return res;
}
那么可以发现一个性质:一个棋子的晚归位时间是 级别的
回到这个题,利用之前的打表程序再发掘一下性质
不妨让每个棋子互不相同,也就是标上标号:
0 1 2 0 3 4 (初始局面)
1 0 2 3 0 4 (第一轮)
1 2 0 3 4 0 (第二轮)
1 2 3 0 4 0 (第三轮)
1 2 3 4 0 0 (第四轮)
去掉零
1 2 3 4 (初始局面)
1 2 3 4 (第一轮)
1 2 3 4 (第二轮)
1 2 3 4 (第三轮)
1 2 3 4 (第四轮)
然有一个规律,对于数字 的最后位置的这一竖条,考虑转移到 ,那么就相当于先从 往左下角一直走,直到撞上 ,然后把 剩余的一段往下平移一格,然后往右平移一格
显然这个是对的,因为最终的路线一定是先撞上上一个棋子,然后和它错开一个时间格后如此操作
那么也就可以解释一开始的打表的规律了:空格会代替你撞上一次,非空格就会让你撞上一次
虽然说是要平移,但考虑每次只平移一格,而且总的轮数不会超过 ,所以可以用一个线段树来维
护这个东西,同时用一个 维护一下当前区间,于是只需要支持区间赋值上一个等差数列,区间
加一,单点查询
对于查询第一次撞到哪,既可以在线段树上维护最大值,也可以二分后在线段树上跑(虽然是 的,但也能通过)
附上算法:
1. 找到 1 <= j <= len,满足 i - j + 1 == a[offset + 1 + j] + 1
2. 把 a[offset + 1] ~ a[offset + j] 按照 i, i - 1, i - 2, ... 的值填充
3. 把 a[offset + j + 1] ~ a[offset + len] 都 +1
4. ans[a[offset + k + 1]] = 1
实际上是模拟双端队列,手写一个 就行了,时间复杂度
100pts
代码
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
#define int long long
using namespace std;
typedef long long ll;
const int N = 3e6 + 5;
int n,k,mp[N],len,ans[N],offset,lim;
int getlen(){
int res=0;
for(rint i=1;i<=n;i++){
if(mp[i]==0){
int tag=0,tot=0;
for(rint j=i;j<=n;j++){
if(mp[j]==1){
tot++;
res=max(res,(j-i+1)-tot+tag);
}
if(mp[j]==0){--tag;}
else{++tag;}
if(tag<0){
tag=0;
}
}
break;
}
}
return res;
}
namespace SEGTREE {
const int T=N*10;
int nek[T],d[T];
int val[T],tag[T];
void init() {
memset(nek,-1,sizeof nek);
memset(tag,-1,sizeof tag);
}
#define lc (id<<1)
#define rc (id<<1|1)
void inline push(int id,int l,int r){
if(nek[id]!=-1){
int mid=(l+r)>>1;
val[id]=nek[id];
nek[lc]=nek[id],d[lc]=d[id];
nek[rc]=(mid-l+1)*d[id]+nek[id],d[rc]=d[id];
tag[lc]=tag[rc]=-1;
nek[id]=-1,d[id]=0;
}
if(tag[id]!=-1){
val[id]+=tag[id];
if(tag[lc]==-1) tag[lc]=tag[id];
else tag[lc]+=tag[id];
if(tag[rc]==-1) tag[rc]=tag[id];
else tag[rc]+=tag[id];
tag[id]=-1;
}
}
int inline ask(int id,int l,int r,int pos){
int mid=(l+r)>>1;
push(id,l,r);
if(l==r){return val[id];}
else if(pos<=mid){return ask(lc,l,mid,pos);}
else{return ask(rc,mid+1,r,pos);}
}
int inline getj(int val){
int l=1,r=len,res=1;
while(l<=r){
int mid=(l+r)>>1;
int tmp=ask(1,1,lim,offset+1+mid);
if(val-mid==tmp){res=mid;r=mid-1;}
else if(val-mid>tmp){l=mid+1;}
else{r=mid-1;}
}
return res;
}
void inline set_d(int id,int l,int r,int ql,int qr,int shou_xiang,int gong_cha){
int mid=(l+r)>>1;
push(id,l,r);
if(ql<=l&&r<=qr){
if(nek[id]==-1)
nek[id]=shou_xiang,d[id]=gong_cha;
else
nek[id]+=shou_xiang,d[id]+=gong_cha;
tag[id]=-1;
}
else if(qr<=mid){
set_d(lc,l,mid,ql,qr,shou_xiang,gong_cha);
}
else if(ql>=mid+1){
set_d(rc,mid+1,r,ql,qr,shou_xiang,gong_cha);
}
else{
set_d(lc,l,mid,ql,mid,shou_xiang,gong_cha);
int new_shou_xiang=shou_xiang+(mid-ql+1)*gong_cha;
set_d(rc,mid+1,r,mid+1,qr,new_shou_xiang,gong_cha);
}
}
void inline plus_1(int id,int l,int r,int ql,int qr){
int mid=(l+r)>>1;
push(id,l,r);
if(ql<=l&&r<=qr){
if(tag[id]==-1)
tag[id]=1;
else ++tag[id];
}
else if(qr<=mid){
plus_1(lc,l,mid,ql,qr);
}
else if(ql>=mid+1){
plus_1(rc,mid+1,r,ql,qr);
}
else{
plus_1(lc,l,mid,ql,mid);
plus_1(rc,mid+1,r,mid+1,qr);
}
}
}
ll a[N];
int inline getj(int i){return SEGTREE::getj(i);}
void inline set_d(int l,int r,int i,int gc){SEGTREE::set_d(1,1,lim,l,r,i,gc);}
void inline plus_1(int l,int r){SEGTREE::plus_1(1,1,lim,l,r);}
int inline get_val(int pos){return SEGTREE::ask(1,1,lim,pos);}
signed main(){
SEGTREE::init();
cin>>n>>k;
for(rint i=1;i<=n;i++){
cin>>mp[i];
}
len=getlen();
k=min(k,len);
len++;
offset=len+10;
lim=2*(len+20);
for(rint i=1;i<=n;i++){
if(mp[i]==0){
continue;
}
int t=i;
offset--;
// 1. 找到 1 <= j <= len,满足 i - j + 1 == a[offset + 1 + j] + 1
// 2. 把 a[offset + 1] ~ a[offset + j] 按照 i, i - 1, i - 2, ... 的值填充
// 3. 把 a[offset + j + 1] ~ a[offset + len] 都 +1
// 4. ans[a[offset + k + 1]] = 1
int j=getj(i);
set_d(offset+1,offset+j,i,-1);
if(offset+j+1<=offset+len){plus_1(offset+j+1,offset+len);}
ans[get_val(offset+k+1)]=1;
}
SEGTREE::init();
for(rint i=1;i<=n;i++){
putchar(ans[i]+'0');
putchar(' ');
}
}
T4 数字符串
题目描述
有一个长度为 的字符串 ,对于每一个前缀 ,求有多少个 满足:
1.
2.
3.
4.
输出
其中 表示前缀 中满足条件的 的个数
第一行输入一个由小写字母构成的字符串。
第二行一个整数 ,表示题目描述中的常数 ;
输出一行一个整数表示答案。
样例
输入
abababac
2
输出
24
满足 ,并且 仅由小写字母组成。
分析
考虑求完 next
数组后的大暴力:
for(rint i=1;i<=n;i++){
if(i%k==0) ++cnt[i];
for(rint j=nxt[i];j&&j>=i-j+1;j=nxt[j]){
if((2*j-i)%k==0){
++cnt[i];
}
}
}
由于数据范围很小,直接建完 next
树后链上差分即可
代码
#include<bits/stdc++.h>
#define rint register int
#define endl '\n'
#define int long long
using namespace std;
const int mod=998244353;
const int N = 2e6 + 9;
int n,k,z[N],tag[N];
char s[N];
inline void MOD(int &x){x=x>=mod?x-mod:x;}
inline int power(int x,int y){
int ans=1;
while(y){
if(y&1)ans=ans*x%mod;
x=x*x%mod;
y>>=1;
}
return ans;
}
signed main(){
scanf("%s",s+1);
n=strlen(s+1);
scanf("%d",&k);
int l=0;
for(rint i=2;i<=n;++i){
if(l+z[l]>i)z[i]=min(z[i-l+1],l+z[l]-i);
while(i+z[i]<=n&&s[z[i]+1]==s[i+z[i]])z[i]++;
if(i+z[i]>l+z[l])l=i;
}
z[1]=n;
for(rint i=0;i<n;++i){
int len=z[i+1];
if(len>=i+k){
len-=i;
len=(len/k)*k;
tag[2*i+k]++;
if(2*i+len+k<=n)tag[2*i+len+k]--;
}
}
int Ans=1,sum=0;
for(rint i=1;i<=n;i++){
if(i+k<=n)tag[i+k]+=tag[i];
Ans=Ans*(tag[i]+1)%mod;
sum+=tag[i];
}
for(rint i=0;i<=n;i++)
z[i]=tag[i]=0;
cout<<Ans;
return 0;
}
本文作者:PassName
本文链接:https://www.cnblogs.com/spaceswalker/p/16505836.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步