在这片梦想之地,不堪回首的过去像泡沫一样散去,不愿面对的明天也永远不会到来,人们为何选择沉睡?是因为害怕从梦中醒来。|

PassName

园龄:3年1个月粉丝:32关注:16

2022.7.22 模拟赛

前言

第一次和学长们模拟赛切掉 T1,真的不容易啊。。。。。

T1 数正方体

题目描述

众所周知,正方形有 4 个点,4 条边;正方体有 4 个点,12 条边,6 个面,定义点为零维基础图形,线段为一维基础图形,正方形为二维基础图形,正方体为三维基础图形...,那么请问,一个 n 维基础图形包含多少个 m 维基础图形呢 (mn)

多次询问,输出对 998244353 取模。

第一行输入一个正整数 T 表示数据组数。

下接 T 行,每行两个自然数 n,m,描述一组数据。

输出 T 行,每行一个数字表示答案。

对于全部数据,T105,0mn105

样例:

输入
7
3 0
3 1
3 2
3 3
48545 1
77625 77624
93574 83513

输出
8
12
6
1
223544257
155250
424453971

解析

赛时手搓了一个及其神奇的结论:

facn×invnm×2nm×invm

然后过了,当时也是没有严谨的证明,由于自己的数学基础太垃圾,根本不知道什么是四维图形,就干脆,拿一二三维乱搞。

定义 n 维基础图形的顶点为,n 维向量中每一维是 0/1 的向量集合(比如说正方体上的顶点就是 0/1,0/1,0/1
那么把点再往多扩展一下,不难发现,m 维基础图形在 n 维基础图形上的表现为,某些维取值相同,剩下维取值唯一(是个 m 维基础图形)
答案就是 (nnm)2nm

代码:

#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 数连通块

题目描述

给定一个森林,每个点都有一定概率会消失,一条 (u,v)E 的边存在的条件是,u 存在且 v 存在

有若干次询问,每次给定 [l,r] ,然后把下标不在 [l,r] 的点都删掉后,问剩余点和所有边构成的图的联通块个数的期望

第一行输入三个整数 n,m,q,分别表示点的个数和边的个数和询问次数

之后 n 行,第 i 行有两个整数 ai,bi,表示第 i 个点存在的概率是 aibi 之后 m 行,每行有两个整数 u,v ,表示存在一条连接 uv 的边,保证无重边无自环之后 q 行,每行两个整数 [l,r],表示一次询问

对于每一次询问,输出一行一个整数表示答案,输出对 1e9+7 取模

对于所有数据,保证:1n105,0m<n,1q105,1aibi105

样例

输入
2 1 1
1 1
1 1
1 2
1 2

输出
1

解析

T2 挂的非常惨,一分没有,后来才发现 mod1e9+7。(要不然有部分分呢)

考虑到森林中连通块个数的 T 满足:

T=nm

证明:每添加一条边,就会合并两个连通块,相应的,连通块的个数就会少一个
根据期望的线性性,可得:E(T)=E(n)E(m)
于是问题就转化为了计算 E(n)E(m)
为了方便起见,设 pu 表示 u 的存在的概率
于是对 pu 做前缀和数组 Sp 后,可得

E(n[l,r])=SprSpl1

之后只需要考虑 E(m) 如何计算,显然有:

E(m)=(u,v)Eu[l,r]v[l,r]pu·pv

于是可以把每一条 (u,v)E 的边看成一个点 (u,v),它的权值是 pu·pv

之后相当于询问一个矩形内的点的权值和,这显然就是一个二维数点问题,离线后树状数组统计即可。

代码:

#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 数大模拟

题目描述

n 个连续的格子,一开始某些格子上有棋子,假设格子从 1n 进行编号。

你需要进行 k 轮操作,每一轮操作如下:
•选中所有满足“其左侧相邻的是空格子”的棋子,
•将这些棋子向左移动一格。

比如,对序列 1001101 进行一轮操作后,会变为 1010110(其中 1 表示这个格子上有一个棋子,0 表示这是一个空格子)。

请输出操作 k 轮后,每个格子上是否有士兵(即输出一个长度为 n 的序列,表示方式与上文相同)。

注:如果有两个相邻格子上都有棋子,那么靠右的棋子不会在这一轮操作中移动。

输入第一行两个整数 n,k,分别表示格子总数与操作轮数。

第二行 n个整数 ai

输出 n 个整数 bi,其中 bi 表示操作 k 轮后,从左往右第 i 个格子是否有棋子

样例

输入
6 2
0 1 1 0 1 1

输出
1 1 0 1 1 0

对于 30% 的数据,满足 n,k1000
对于额外 20% 的数据,满足 n1000
对于 100% 的数据,满足 1n,k106

解析

赛时没有拿到 100pts ,因为第一题浪费了太多了时间,第二题也写挂掉了,这道题就想了个 50pts 的做法。
三十分就是直接暴力就行了,重点在于另外二十分
由于此时 k 必然大于 1000,(不大于的话那不还是暴力
所以 k>n ,那么操作完之后 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;
}

再来说说正解。

考虑先把已经归位的 1 删除,也就是把序列一开始的前缀 1 都删掉
那么对于每一个棋子,都有一个目标距离,即最终序列的位置
显然这个最早的归位时间是最后一个棋子的归位时间
对于一个棋子,它前面每有一个棋子,则会对它产生晚归位 1 单位时间的影响
反之,它前面每有一个空格,则会对它产生早归位 1 单位时间的影响
于是就有一个求得最晚归位时间的代码了,当然也同时可以得知每个棋子的归位时间

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;
}

那么可以发现一个性质:一个棋子的晚归位时间是 O(n) 级别的
回到这个题,利用之前的打表程序再发掘一下性质
不妨让每个棋子互不相同,也就是标上标号:

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   (第四轮)

然有一个规律,对于数字 i 的最后位置的这一竖条,考虑转移到 j=I+1 ,那么就相当于先从 (1,j) 往左下角一直走,直到撞上 i ,然后把 i 剩余的一段往下平移一格,然后往右平移一格

显然这个是对的,因为最终的路线一定是先撞上上一个棋子,然后和它错开一个时间格后如此操作
那么也就可以解释一开始的打表的规律了:空格会代替你撞上一次,非空格就会让你撞上一次
虽然说是要平移,但考虑每次只平移一格,而且总的轮数不会超过 O(n) ,所以可以用一个线段树来维
护这个东西,同时用一个 offset 维护一下当前区间,于是只需要支持区间赋值上一个等差数列,区间
加一,单点查询
对于查询第一次撞到哪,既可以在线段树上维护最大值,也可以二分后在线段树上跑(虽然是 O(log2 n) 的,但也能通过)

附上算法:

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

实际上是模拟双端队列,手写一个 deque 就行了,时间复杂度 O(n)

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 数字符串

题目描述

有一个长度为 n 的字符串 S,对于每一个前缀 S[1,i],求有多少个 x 满足:
1.1xi
2.S[1,x]=S[ix+1,i]
3.xix+1
4.x(ix+1)+10(modk)

输出 i=1n(Fi+1)mod998244353

其中 Fi 表示前缀 S[1,i] 中满足条件的 x 的个数

第一行输入一个由小写字母构成的字符串。

第二行一个整数 k,表示题目描述中的常数 k

输出一行一个整数表示答案。

样例

输入
abababac
2

输出
24

满足 1k|S|106,并且 S 仅由小写字母组成。

分析

考虑求完 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 中国大陆许可协议进行许可。

posted @   PassName  阅读(108)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起