2022.7.22 模拟赛

前言

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

T1 数正方体

题目描述

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

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

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

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

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

对于全部数据,\(T≤10^5,0≤m≤n≤10^5\)

样例:

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

输出
8
12
6
1
223544257
155250
424453971

解析

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

\[fac_n×inv_{n-m}×2^{n-m}×inv_m \]

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

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

代码:

#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\) 行有两个整数 \(a_i,b_i\),表示第 \(i\) 个点存在的概率是 \(\frac{a_i}{b_i}\) 之后 \(m\) 行,每行有两个整数 \(u,v\) ,表示存在一条连接 \(u\)\(v\) 的边,保证无重边无自环之后 \(q\) 行,每行两个整数 \([l,r]\),表示一次询问

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

对于所有数据,保证:\(1≤n≤10^5,0≤m<n,1≤q≤10^5,1≤a_i≤b_i≤10^5\)

样例

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

输出
1

解析

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

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

\[T=n-m \]

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

\[E(n_{[l,r]})=S_{p_r}-S_{p_{l-1}} \]

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

\[E(m)=\sum_{(u,v)∈E∧u∈[l,r]∧v∈[l,r]}^{}p_u·p_v \]

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

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

代码:

#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\) 个连续的格子,一开始某些格子上有棋子,假设格子从 \(1\)\(n\) 进行编号。

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

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

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

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

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

第二行 \(n\)个整数 \(a_i\)

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

样例

输入
6 2
0 1 1 0 1 1

输出
1 1 0 1 1 0

对于 \(30\%\) 的数据,满足 \(n,k≤1000\)
对于额外 \(20\%\) 的数据,满足 \(n≤1000\)
对于 \(100\%\) 的数据,满足 \(1≤n,k≤10^6\)

解析

赛时没有拿到 \(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(log^2\) \(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.\(1≤x≤i\)
2.\(S[1,x]=S[i−x+1,i]\)
3.\(x≥i-x+1\)
4.\(x−(i−x+1)+1≡0(modk)\)

输出 \(\prod_{i=1}^{n}(F_i+1) mod 998244353\)

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

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

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

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

样例

输入
abababac
2

输出
24

满足 \(1≤k≤|S|≤10^6\),并且 \(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;
}

posted @ 2022-07-22 15:12  PassName  阅读(115)  评论(0)    收藏  举报