首页 > 其他分享 >241118 noip 数数模拟赛

241118 noip 数数模拟赛

时间:2024-11-18 16:22:57浏览次数:1  
标签:数数 noip int times leq state ans 241118 dp

省流:\(100 + 100 + 100 + 10\)。四道数数太好玩了。绿蓝紫黑。

T1

题意:

如下是一个不完全正确的归并排序算法代码。

//此函数表示将S[1,mid],S[mid+1,r]两个有序序列合并成为一个大的有序序列S[l,r],如果原序列无序则合并后的序列也无序
void merge_arr(int l,int mid,int r)
{
    //merge_array
}
void merge_sort(int l,int r,int deep)
{
    if(l==r)	return;
    if(deep>=K)	return;
	int mid=(l+r)>>1;
    merge_sort(l,mid,deep+1);
    merge_sort(mid+1,r,deep+1);
    merge_arr(l,mid,r);
}

其中,主函数内将使用如下方法调用排序:

merge_sort(1,n,0);

初始给定一个 \(n\) 表示你要给一个长度为 \(n\) 的排列进行排序,多次询问,每次给定一个 \(k\),你需要求出有多少种排列能够用上面的代码排序成单调递增的,对 \(998244353\) 取模。

\(T \leq 100,n \leq 5 \times 10^6,k \leq 10^9\)。

容易发现,对于一个 \(k\) 和一个排列,第 \(k\) 层的每个区间内排列中的数字都单调递增,则这个排列是可以排序好的。设第 \(k\) 层的区间为 \([l_1,r_1],[l_2,r_2],\cdots,[l_m,r_m]\)(这些区间并为 \([1,n]\),交为空),那么合法的排列个数即为 \(\frac{n}{\prod_{i = 1}^m r_i - l_i + 1}\)。容易发现同一层中区间不同长度至多为 \(2\),所以可以递推得出第 \(k\) 层中长度为 \(x\) 的有多少个区间,计算上述柿子即可。对于 \(k > \log n\),那么不管什么排列都合法,输出 \(n!\) 即可。

时间复杂度 \(\Theta(n + T)\)。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=24,p=998244353;
int t,n,a[N],dp[N][2],ans[N],fac[1<<N],inv[1<<N];
int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
void init(int lim) {
	fac[0]=inv[0]=1;
	for(int i=1; i<=lim; i++) fac[i]=fac[i-1]*i%p;
	inv[lim]=qpow(fac[lim],p-2);
	for(int i=lim-1; i>=1; i--) inv[i]=inv[i+1]*(i+1)%p;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>t>>n;
	init(n);
	int tmp=n,tot=0;
	while(tmp) {
		a[++tot]=tmp;
		tmp/=2;
	}
	dp[1][0]=1;
	for(int i=2; i<=tot; i++) {
		if(a[i-1]&1) dp[i][0]=(dp[i][0]+dp[i-1][0])%p,dp[i][1]=(dp[i][1]+dp[i-1][0]%p+2*dp[i-1][1]%p)%p;
		else dp[i][0]=(dp[i][0]+2*dp[i-1][0]%p+dp[i-1][1])%p,dp[i][1]=(dp[i][1]+dp[i-1][1])%p;
	}
	for(int i=1; i<=tot; i++) ans[i]=fac[n]*qpow(inv[a[i]],dp[i][0])%p*qpow(inv[a[i]+1],dp[i][1])%p;
	while(t--) {
		int k;
		cin>>k;
		if(k>=tot) cout<<fac[n]<<'\n';
		else cout<<ans[k+1]<<'\n';
	}
	return 0;
}

T2

题意:给定正整数 \(n,m\),以及长度为 \(m\),值域为 \([0,n−1]\) 的整数序列 \(a_1,a_2,\cdots,a_m\)​。保证 \(a\) 中元素互不相同。

你需要计算满足以下要求的 \(0,1,…,n − 1\) 的排列 \(p\) 的数量:

  • 能够经过任意次以下操作,使 \(p=a\):

    • 选择 \(1 \leq l \leq r\leq \lvert p \rvert\) 的两个数 \(l,r\),如果 \(\text{mex}({p_l,p_{l + 1},…,p_r})\) 存在于序列 \(p\) 中,则将 \(p_l,p_{l + 1},\cdots,p_r\)​ 删除。

输出答案对 \(998244353\) 取模后的结果。

\(1 \leq m \leq n \leq 10^6,\sum m \leq 10^6,0 \leq a_i <n\),\(a\) 中元素互不相同。

注意,\(\sum a\) 没有限制。

将排列 \(p\) 合法的要求转换一下,设 \(a\) 中最小的元素为 \(mn\),设 \(l,r\) 为对于 \(p\) 中所有 \(<mn\) 的元素位置最小和最大的位置是什么,则合法要求为 \(p\) 中 \(a\) 的元素按照顺序且不存在一个 \(a\) 中的元素它的位置 \(>l\) 且 \(<r\)。

我们可以先钦定 \(mn + m\) 个位置表示这些位置用来放 \(<mn\) 的元素和 \(a\) 中的元素,方案数为 \(C_n^{mn + m}\),由于 \(a\) 中的元素不能 \(>l\) 且 \(<r\),所以对于这 \(mn + m\) 个位置分配的方案数为 \(m + 1\),由于 \(<mn\) 的元素可以乱序,方案数为 \(mn!\),最后再把剩下的 \(n - mn - m\) 放进去,方案数为 \((n - mn - m)!\),把以上所有的方案数相乘即是答案。\(mn \leq 1\) 可能需要特判。

时间复杂度 \(\Theta(n + \sum m)\)。

代码:

#include<bits/stdc++.h>
using namespace std;
const int N=1e6+5,p=998244353;
int t,n,m,fac[N],inv[N];
inline int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=1ll*ans*a%p;
		a=1ll*a*a%p;
		b>>=1;
	}
	return ans;
}
inline void init(int lim) {
	fac[0]=inv[0]=1;
	for(int i=1; i<=lim; i++) fac[i]=1ll*fac[i-1]*i%p;
	inv[lim]=qpow(fac[lim],p-2);
	for(int i=lim-1; i>=1; i--) inv[i]=1ll*inv[i+1]*(i+1)%p;
}
inline int C(int n,int m) {
	if(n<m||n<0||m<0) return 0;
	return 1ll*fac[n]*inv[m]%p*inv[n-m]%p;
}
inline int read() {
	int x=0;
	char ch=getchar();
	while(ch<'0'||ch>'9') ch=getchar();
	while(ch>='0'&&ch<='9') x=1ll*x*10+ch-'0',ch=getchar();
	return x;
}
void write(int x) {
	if(x>=10) write(x/10);
	putchar(x%10+'0');
}
int main() {
	init(1e6);
	t=read();
	while(t--) {
		n=read(),m=read();
		int mn=INT_MAX;
		for(int i=1; i<=m; i++) {
			int x;x=read();
			mn=min(mn,x);
		}
		if(mn<=1) {
			write(1ll*fac[n]*inv[m]%p);putchar('\n');
			continue;
		}
		write(1ll*C(n,m+mn)*(m+1)%p*fac[mn]%p*fac[n-m-mn]%p);putchar('\n');
	}
	return 0;
}

T3

题意:给定一张 \(n\) 个点 \(m\) 条边的图和一个定值 \(k\),你需要对所有的 \(0 \leq i \leq k\) 求出只保留 \(n - 1 + i\) 条边且图还连通的选边方案数,对 \(998244353\) 取模。

\(n \leq 17,0 \leq k \leq 2\)。

这个 \(k\) 的范围非常有迷惑性,实际上树和基环树的性质完全没有用。

可以设计状压 dp,设 \(dp_{state,0/1/2}\),表示当前已经连通了 \(state\) 的二进制位中为 \(1\) 的点,且保留了 \(\text{popcount}(state) + 0/1/2\) 条边。转移如下:

\[dp_{state,0} = \sum_{s \in state} cnt_{s,state \oplus s} \times dp_{s,0} \times dp_{state \oplus s,0} \\ dp_{state,1} = dp_{state,0} \times (n - \text{popcount}(state) + 1) + \\ \sum_{s \in state} cnt_{s,state \oplus s} \times (dp_{s,1} \times dp_{state \oplus s,0} + dp_{s,0} \times dp_{state \oplus s,1}) \\ dp_{state,2} = dp_{state,1} \times (n - \text{popcount}(state)) + \\ \sum_{s \in state} cnt_{s,state \oplus s} \times (dp_{s,2} \times dp_{state \oplus s,0} + dp_{s,0} \times dp_{state \oplus s,2} + dp_{s,1} \times dp_{state \oplus s,1}) \]

\(cnt_{s,t}\) 表示一个端点在 \(s\) 中,另一个端点在 \(t\) 中的边的个数。

发现这会算重,但是容易发现上述的 dp 对于一个有 \(i\) 条边的图,对于每一条边都会给答案加上这张图的贡献,所以让 \(dp_{state,0}\) 除以 \(\text{popcount}(state)-1\),\(dp_{state,1/2}\) 同理即可。

这样我们就得到了一个 \(\Theta(3^n n^2)\) 的做法。考虑优化,发现一条边两个端点有一个不在 \(state\) 中,那么这条边无论如何都不会算到,可以暴力算出有可能被算到的边,记数量为 \(sum\),如果一条边两个端点都在 \(state\) 中但却没有计算到当且仅当两个端点都在 \(s\) 或 \(state \oplus s\),于是先预处理高维前缀和,这样我们就可以快速的求出 \(cnt_{s,t} = sum - tot_s - tot_t\)。

时间复杂度 \(\Theta(2^n n^2 + 3^n)\)。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=300,p=998244353;
int n,m,k,u[N],v[N],dp[200005][3],sum[200005];
inline int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=1ll*ans*a%p;
		a=1ll*a*a%p;
		b>>=1;
	}
	return ans;
}
signed main() {
	ios::sync_with_stdio(false);
	cin.tie(nullptr),cout.tie(nullptr);
	cin>>n>>m>>k;
	for(int i=1; i<=m; i++) cin>>u[i]>>v[i],sum[(1<<u[i]-1)|(1<<v[i]-1)]++;
	for(int i=0; i<n; i++) for(int j=0; j<(1<<n); j++) if(j&(1<<i)) sum[j]+=sum[j^(1<<i)];
	for(int i=1; i<(1<<n); i++) {
		if(__builtin_popcount(i)==1) dp[i][0]=1;
		else {
			int cnt=0;
			for(int j=1; j<=m; j++) if((i&(1<<u[j]-1))&&(i&(1<<v[j]-1))) cnt++;
			for(int s=i&(i-1); s; s=(s-1)&i) if(s<(i^s)) dp[i][0]=(dp[i][0]+(cnt-sum[s]-sum[i^s])*(dp[s][0]*dp[i^s][0]%p)%p)%p;
			dp[i][0]=dp[i][0]*qpow(__builtin_popcount(i)-1,p-2)%p;
			for(int s=i&(i-1); s; s=(s-1)&i) if(s<(i^s)) dp[i][1]=(dp[i][1]+(cnt-sum[s]-sum[i^s])*(dp[s][1]*dp[i^s][0]%p+dp[s][0]*dp[i^s][1]%p)%p)%p;
			dp[i][1]=(dp[i][1]+dp[i][0]*(cnt-__builtin_popcount(i)+1)%p)%p;
			dp[i][1]=dp[i][1]*qpow(__builtin_popcount(i),p-2)%p;
			for(int s=i&(i-1); s; s=(s-1)&i) if(s<(i^s)) dp[i][2]=(dp[i][2]+(cnt-sum[s]-sum[i^s])*(dp[s][2]*dp[i^s][0]%p+dp[s][0]*dp[i^s][2]%p+dp[s][1]*dp[i^s][1]%p)%p)%p;
			dp[i][2]=(dp[i][2]+dp[i][1]*(cnt-__builtin_popcount(i))%p)%p;
			dp[i][2]=dp[i][2]*qpow(__builtin_popcount(i)+1,p-2)%p;
		}
	}
	for(int i=0; i<=k; i++) cout<<dp[(1<<n)-1][i]<<'\n';
	return 0;
}

闲话:这题其实没有 \(100\),被卡常了,但是通过死缠烂打让教练把时限开到了 2s 于是过了/kx

T4

原题:qoj8143。

首先暴力求出 \(\sum_{i=1}^b f(a,b,i)\),接下来讨论 \(i > b\) 的情况。

若 \(a \mid b\),则 \(i > b\) 时答案为 \(\frac{b}{a}\),接下来不做讨论(即不讨论下文方程中 \(y = 0\) 的情况)。

显然 \(f(a,b,i)\) 为方程 \(ax + iy = b\) 的解中最小的非负整数解 \(x\),将方程变形为 \(x = \frac{b − iy}{a}(x \geq 0)\)。由于 \(i>b\),有 \(\lvert iy \rvert > b\),又因为 \(x \geq 0\),所以 \(y < 0\)。设 \(z = −y(z > 0)\),则 \(x=\frac{b + iz}{a}\)​,由于最小化 \(x\),则 \(z\) 取 \(f(i,−b,a)\)。发现若 \((i ~\text{mod}~ a)\) 相同时,取得的 \(z\) 也相同。枚举 \((i ~\text{mod}~ a)\),等差数列求和即可。

设 \(l = a + b\),单组数据总时间复杂度 \(\Theta(l \log l)\)。

代码:

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int p=998244353;
int t,n,a,b;
int qpow(int a,int b) {
	int ans=1;
	while(b) {
		if(b&1) ans=ans*a%p;
		a=a*a%p;
		b>>=1;
	}
	return ans;
}
void exgcd(int a,int b,int &x,int &y) {
	if(!b) {
		x=1,y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
int solve(int a,int c,int b) {
	int d=__gcd(a,b);
	if(abs(c)%d) return -1;
	a/=d,b/=d,c/=d;
	int x,y;
	exgcd(a,b,x,y);
	return (x*c%b+b)%b;
}
int calc(int fir,int lst,int len) {return (fir+lst)%p*(len%p)%p*499122177%p;}
signed main() {
	cin>>t;
	while(t--) {
		int ans=0;
		cin>>n>>a>>b;
		for(int i=1; i<=min(b,n); i++) ans=(ans+(solve(a,b,i)==-1?0:solve(a,b,i)))%p;
		if(b%a==0) {
			cout<<(ans+(b/a)*((n-min(b,n))%p)%p)%p<<endl;
			continue;
		}
		if(n>b) {
			for(int i=0; i<a; i++) {
				int fir=(b/a*a+i<=b?b/a*a+i+a:b/a*a+i),lst=(n/a*a+i>n?n/a*a+i-a:n/a*a+i),len=(lst-fir)/a+1;
				if(len<=0) continue;
				int z=solve(i,-b,a);
				if(z==-1) continue;
				ans=(ans+(len*b%p+calc(fir,lst,len)*z%p)%p*qpow(a,p-2)%p)%p;
			}
		}
		cout<<ans<<endl;
	}
	return 0;
}

标签:数数,noip,int,times,leq,state,ans,241118,dp
From: https://www.cnblogs.com/System-Error/p/18552910

相关文章

  • [2024.11.18]NOIP2024模拟赛#23
    赛时T1题面实在太奇怪,结合样例看了好久才看懂。看懂以后发现应该就是简单神秘结论题。简单写了一会就过了样例,发现没给大样例,就扔了。T2第一眼感觉还是结论题,但是如果发现每个点能保证只覆盖一次的话就能做到\(\mathcal{O}(nm)\)。然后开始写,写完不过样例,发现题目让先输入......
  • [赛记] 多校A层冲刺NOIP2024模拟赛23
    字符串构造机100pts原题,见[赛记]多校A层冲刺NOIP2024模拟赛01【衡中】T1;忍者小队60pts赛时最后想出来个$\Theta(n^2\logn)$的DP,所以60pts;对于这个DP,直接用map维护一下所有lcm的状态转移即可;点击查看代码#include<iostream>#include<cstdio>#include<vect......
  • NOIP 模拟赛:2024-11-16
    全体栽在T1?T1:二分一下内存大小然后模拟判断。关键点在于意识到"解码"和"播放"这两种事件是分开的。用一个while循环,每次循环从"完成某帧的解码"、"开始某帧的解码"、"播放某帧"、"删除某帧"之间选时间最早的时间执行。T2:板题,并查集额外记录个vector然后启发式合......
  • noip模拟15
    A暴力操作(opt)B异或连通(xor)C诡异键盘(keyboard)D民主投票(election)这道题很简单。。。首先,对于一个节点\(u\),如果\(siz[u]-1\)大于了其他所有节点能得到的最大值,那么它一定能胜利。那考虑怎么找到一个值,满足所有节点能得到的最大值最小?用二分答案即可。对于一次......
  • [考试记录] 2024.11.16 noip模拟赛14
    T1字符串构造机考虑将一个LCP条件拆分成两个,一个是相等的部分,使用并查集维护,另一个是不等的部分,两个串末尾的字符一定不相等,随便那啥维护。对于非法情况就是在同一个相等联通块内有不相等的条件。然后考虑从前往后贪心即可。#include<bits/stdc++.h>usingnamespacestd;#d......
  • NOIP 模拟赛总结
    NOIP模拟赛总结DAY5T1:二分答案,贪心T2:二分答案,猜结论,单调栈T3:阶,分治T4:set,线段树,找性质(颜色段均摊总分:70总结:郑楠则反。DP优化经典套路不能忘。注意子任务的提示DAY6T1:暴力T2:乱搞,爆搜T3:分治,(亿点点)化式子T4:曼哈顿转切比雪夫,二分答案,KDtree(最近点),可持久化线段树(二维数......
  • NOIP 数据结构
    线段树标记看成序列而不是数权值对权值、标记对标记、标记对权值P1471区间加,区间平均值,区间方差区间平均值等同于区间和将方差式子拆解:$\frac{1}{n}\sum(A_i-\overline{A})^2=\frac{1}{n}(\sum{A_i}^2-2\sumA_i\overline{A}+n{\overline{A}}^2)$把\(\overline......
  • NOIP 模拟 9
    A送信卒直接二分。B共轭树图看了好多篇题解都说的不太清楚,随便观察一下得知子树间互不影响,且没有边相交,在不连直接父亲的情况下,孩子的父亲一定比祖先的父亲靠上,所以这道题考虑的是和祖先的关系,而不是与孩子的关系,然后这个时候可简单地设计出一种状态,\(f_{u,i}\)表示\(u\)......
  • NOIP 模拟 8
    搬的【MX-S5】梦熊NOIP2024模拟赛1(同步赛)A王国边缘倍增写脸上了。B买东西题反悔贪心写脸上了,首先按物品价格从小到大排序,这样之前用的优惠券一定可以给现在的优惠券用,如果给价格为\(a\),折扣价为\(w\)的物品用了优惠为\(x\)的优惠券,现在拿过来给\(b\)用后的贡献是......
  • NOIP 模拟 11
    T1暴力操作(opt)类似背包的处理出来除以每个数的最小代价,然后直接二分check即可,细节就是处理前后要做后缀min,然后求出\(\lfloor\frac{a}{x}\rfloor\lemid\)的最小\(x\),可以通过整除分块的套路,\(x=\lfloor\frac{a}{mid+1}\rfloor+1\)。T2异或连通(xor)trie树上的一个子树......