2024牛客暑期多校训练营8 补题 E|J|D|G

E-Haitang and Math_2024牛客暑期多校训练营8 (nowcoder.com)

E题其实都可以想到朴素的对于 [ n − 108 , n ] [n-108,n] [n108,n] 这个区间每个数去暴力分解因子,然后判断因子是否成立。

但是如果这样写复杂度将会是 O ( T × 108 × n × l o g n ) O(T\times 108 \times \sqrt n \times logn) O(T×108×n ×logn) 显然不合法。那么我们想如何去优化呢。

对于枚举因子,其实我们很经常会分解出质因子,然后去质因子去暴力枚举选取的可能。

就可以把复杂度从原本的 O ( n ) → O ( n l o g n + d ( n ) ) O(\sqrt n) \rightarrow O(\frac{\sqrt n}{logn} + d(n)) O(n )O(lognn +d(n)) , d ( n ) d(n) d(n) 表示因子函数。该量级差不多为 l o g n logn logn

然后如果带回去会发现现在变成 O ( T × 108 × ( n l o g n + d ( n ) × l o g n ) ) O(T\times 108 \times( \frac{\sqrt n}{logn} + d(n) \times logn)) O(T×108×(lognn +d(n)×logn))

显然我们很想把 108 108 108 这个常数消去,这个 108 108 108 表示为 [ n − 108 , n ] [n-108,n] [n108,n] 的质因子分解。

那么其实可以想对于一个质因子,他只有在 [ 0 , 108 ] [0,108] [0,108] 的时候,去对 [ n − 108 , n ] [n-108,n] [n108,n] 可能是一对多的映射。

108 108 108 以上的时候,其实至多是一一映射。

为什么呢?

pri[i] >=108 的时候,会有余数,如果 n 减去这个余数,就可以被 pr[i] 整除

显然余数对应于[0,108] 必然只有一个,假设当前余数位 r 那么其实他只能 r + pr[i] 同于,显然> 108

可能会问为什么上面 <= M 的时候不直接减去这个余数,因为其实一个余数其实对应于 [0,108]里面多个数

同样假设当前余数位 r,那么 r+pr[i] 不一定 > 108 ,例如 在 %10 为 2 ,那么对应于 [0,108] 其实有 2,12,22,…

差不多是上面这样。

然后优化后其实我们还可以优化掉 d ( n ) d(n) d(n) 后面的 l o g n logn logn 。这个 l o g n logn logn 复杂度是算 S ( X ) S(X) S(X)的复杂度,我们只要预处理前 1 0 6 10^6 106 后面的只需要 S ( x 1 0 6 + x m o d    1 0 6 ) S(\frac{x}{10^6} + x\mod 10^6) S(106x+xmod106) 即可。

代码

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 1e6+200;
bool isprime[N];
int pri[N],cnt;
void ini()
{
	for(int i=2;i<=1e6+100;i++)
	{
		if(!isprime[i]) pri[++cnt] = i;
		for(int j=1;j<=cnt;j++)
		{
			if(i*pri[j] > 1e6+100) break;
			isprime[i*pri[j]] = 1;
			if(i%pri[j] == 0) break;
		}
	}
}
const int M = 108;
int val[M+10]; // 装数质因数分解
vector<pair<int,int> > e[M+10]; // 装质因子
int mid = 1e6;
int s[N];
int S(int x)
{
	return s[x/mid] + s[x%mid];
}
int cal1(int x)
{
	int res = 0;
	while(x)
	{
		res += x%10;
		x/=10;
	}
	return res;
}
int ans = 0;
#define ptr vector<pair<int,int> > ::iterator
void dfs(int res,ptr s,ptr t,int val)
{
	if(s==t)
	{	// 当前的值 == 我们预先估计的 m 的余数,但是此时要特判是否相等,相等的话显然取余为0
		if(S(val) == res) ans += (res !=val);
		return ;
	}
	auto [w,cnt] = *s;
	s++,dfs(res,s,t,val);
	for(int i=1;i<=cnt;i++)
	{
		val *=w;
		dfs(res,s,t,val);
	}
	
}
void solve()
{
	int n;
	cin>>n;
	ans = 0;
	// 如果 <=128 我们可以直接处理,否则下面 n-i 可能出现负数。
	if(n<=M)
	{
		for(int i=1;i<=n;i++)
		{
			if(n%i == S(i))
				ans ++;
		}
		cout<<ans<<endl;
		return ;
	}
	// val[i] 表示 n - i。即 m 的数位和为 i 在模 m 下 (n-i)%m = 0 这样 存储 n-i
	//	a[i] 表示 n-i 的质因数有多少
	for(int i=0;i<=M;i++)	val[i] = n-i,e[i].clear();
	for(int i=1;1ll*pri[i]*pri[i] <= n;i++)
	{	// 当<=m 时候,可能对应于多个(n-i)的因子 所以要暴力M 位
		if(pri[i] <= M)
		{
			for(int j=0;j<=M;j++)
			{
				int cnt = 0;
				while(val[j] % pri[i] == 0)
					val[j]/=pri[i],cnt++;
				if(cnt)
					e[j].push_back({pri[i],cnt});
			}
		}
		// >=m 的时候,会有余数,如果 n 减去这个余数,就可以被 pr[i] 整除
		// 显然余数对应于[0,108] 必然只有一个,假设当前余数位 r 那么其实他只能 r + pr[i] 同于,显然> M
		// 可能会问为什么上面 <= m 的时候不直接减去这个余数,因为其实一个余数其实对应于 [0,108]里面多个数
		// 同样假设当前余数位 r,那么 r+pr[i] 不一定 > M ,例如 在 %10 为 2 ,那么对应于 [0,108] 其实有 2,12,22,.... 
		else if(n%pri[i] <=M)
		{
			int j = n%pri[i];
			int cnt = 0;
			while(val[j]% pri[i] == 0)
				val[j]/=pri[i],cnt++;
			if(cnt)
				e[j].push_back({pri[i],cnt});
		}
	}
	//以上筛子的复杂度为 \sqrt(n) / log sqrt(n) 。
	/*
	为什么是这个时间复杂度,因为质数密度,在一个 n 的随机序列中,质数密度为 log n。
	 */
	// 一定别忘记了还有可能质因子是 >= 1e6 的
	for(int i=0;i<=M;i++)
	{
		if(val[i]!=1)
			e[i].push_back({val[i],1});
	}
	// 然后dfs找因子,这个复杂度为 d(n)logn ,d(n)表示因子函数,logn 为S(x)的复杂度 这一维S(x) 可以通过类似__builtin_popcount()优化掉。 
	/*
	d(n) 的上界:https://www.zhihu.com/question/451354587
	平均阶为log n
	 */
	for(int i=0;i<=M;i++)
	{	//cout<<e[i].size()<<endl;
	
		dfs(i,e[i].begin(),e[i].end(),1);//最终要的余数,起始,结尾,当前的值。
	}
	cout<<ans<<endl;
	
}  
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	ini();
	// 埃氏筛筛出1e6以内的质数 一定要多大100个左右的表,否则如果遇到 1e12 筛不掉会报错。
	for(int i=1;i<=1e6;i++)
		s[i] = cal1(i);
	//cout<<cnt<<endl;
	
	int t;
	cin>>t;
	while(t--)
		solve();
} 

然后题解说的区间筛,我也去学习了一下。

区间筛解决的是 a ≤ b ≤ 1 0 12 且 b − a ≤ 1 0 6 a\le b \le 10^{12} 且 b-a\le 10^6 ab1012ba106 [ a , b ) [a,b) [a,b) 之间的质数个数。

然后答案就是用 [ 1 , 1 0 6 ] [1,10^6] [1,106] 去筛这个区间。因为我们可以从数论知识知道 n n n 如果是合数必定有一个 n \sqrt n n 的质因子。

代码(抄抄 csdn 代码),该代码的时间复杂度为 O ( b l o g l o g b ) O(\sqrt b loglog\sqrt b ) O(b loglogb ) 如果没算错的话。就是埃氏筛的复杂度。

#define maxn 10000000
using namespace std;
typedef long long ll;//对长整型起别名
bool is_prime_small[maxn];
bool is_prime[maxn];
ll a, b;//定义全局变量
//对区间[a,)内的整数执行筛法。is_prime[i-a]=true->i是素数
void solve(ll a, ll b)
{
	for (ll i = 0; (ll)i * i < b; i++)
		is_prime_small[i] = true;//对0到根号b进行初始化
	for (ll i = 0; i < b - a; i++)
		is_prime[i] = true;//将[a,b)问题转化为[0,b-a)
	
	for (ll i = 2; (ll)i * i < b; i++)
	{
		if (is_prime_small[i])
		{
			for (ll j = 2 * i; (ll)j * j < b; j += i)
				is_prime_small[i] = false;
			for (ll j = max(2LL, (a + i - 1) / i) * i; j < b; j += i)
				is_prime[j - a] = false;
			//max(2LL,(a+i-1)/i)*i是求距离a最近,但比a大的数
			//2LL是2的长整型
		}
	}
}


过几天还会专门出一下 Pollard Rho 算法和 Miller rabin 算法,Min_25筛,杜教筛,洲阁筛。以及别的一些奇奇怪怪的数论小知识。(希望过几天不会成为懒b)

J-Haitang and Triangle_2024牛客暑期多校训练营8 (nowcoder.com)

很显然,如果当 m = = n − 2 m == n-2 m==n2 的时候,显然无法构造出序列。因为 1 1 1 必然会使得至少一个子区间是不满足的,而且 1 1 1 是放在首位的时候, 1 + x ≤ y 1+x \le y 1+xy,如果放在中间显然会影响两个以上使得其不能构造成三角形。

然后对于剩余情况,其实打表前 10 个数会发现,应该是都有的。问题就在于怎么构造。

其实很显然我们的构造肯定是前面一段有 m m m 个合法三角形。后面 n − m n-m nm 个是构造出 0 个三角形的。

然后如何构造呢。我们会发现其实对于一个三角形。如果任意两个之间差大于等于其中一条边,必然是不行。所以临界条件应该是倍数关系。即 d , 2 d , 3 d d,2d,3d d,2d,3d 然后接下去怎么构造呢 我们发现 2 d , 3 d , d − 1 2d,3d,d-1 2d,3d,d1 又可以继续下去了。然后就这样一直构造 d , 2 d , 3 d , d − 1 , 2 d − 1 , 3 d − 1...1 , d + 1 , 2 d + 1 d,2d,3d,d-1,2d-1,3d-1...1,d+1,2d+1 d,2d,3d,d1,2d1,3d1...1,d+1,2d+1 这样子。然后可能会发现如果我们剩下的数不能完全的分配给这 n − m n-m nm 个数又应该怎么办?

其实下取整后剩下的数会为 3 d + 1 , 3 d + 2 3d+1,3d+2 3d+1,3d+2 其实发现放在首尾是最合适的。放完后我们再去看我们剩下 m 个数放在哪里。

如果没有余数的时候。显然我们 m 个数可以放在最后面。因为 d + 1 + 2 d + 1 ≥ 3 d + 1 d+1 + 2d+1 \ge 3d+1 d+1+2d+13d+1 显然可以组成三角形。

如果有一个余数的时候。我们余数放在前面,因为 d + 2 d ≤ 3 d + 1 d+2d\le3d+1 d+2d3d+1 放在后面不行。然后剩下 m m m 个数放在前面。

如果剩余三个数。其实就是把 3 d + 1 3d+1 3d+1 放在前面, 3 d + 2 3d+2 3d+2 放在后面就合法。然后我们的剩下 m m m 前后随便插入。

但是其实会发现,这样构造样例过不去。因为如果此时 d = 1 d=1 d=1 的 时候,会发现其实 6 , 2 6,2 6,2 的样例我们会构造出 4 , 1 , 2 , 3 , 5 , 6 4,1,2,3,5,6 4,1,2,3,5,6 显然不符合条件。然后我们就会想其实我们此时把 4 和 6 交换一下就好,即当我们 d = 1 d = 1 d=1 多出来一个数的时候,我们把这个数与 n n n 互换后插入即可。如果此时余数为 2 2 2 一个插前面一个插入后面也不,然后剩余的 m 个插入后面,显然也是答案。为什么呢 因为插入前最后一个数为 3 d + 2 3d +2 3d+2 那么我们插入的最小的数为 3 d + 3 3d+3 3d+3显然就肯定倒二个数不为 1 (因为我们 k ≥ n − 3 k\ge n-3 kn3 所以一定有 3 个数以上在前面).那就符合条件。

#include<bits/stdc++.h>
using namespace std;
#define ll long long
void solve()
{
	int n,m;
	cin>>n>>m;
	if(m==n-2)
	{
		cout<<"-1"<<endl;
		return ;
	}
	else 
	{
		
		int d = (n-m)/3;
		int res = (n-m)%3;
		deque<int> q;
		
		for(int i=0;i<d;i++)
		{
			q.push_back(d-i);
			q.push_back(2*d-i);
			q.push_back(3*d-i);
		}
		if(q[0]==1 && res == 1)
		{	
			if(res == 1)
			{	q.push_front(n);
				int now = 3*d+1;
			for(int i=now;i<n;i++)
				q.push_back(i);
			}

		}
		else {
		if(res >= 1)
			q.push_front(3*d+1);
		if(res >=2)
			q.push_back(3*d+2);
		
		int now = 3*d + res +1;
		if(q[0]+q[1] > now)
			for(int i=now;i<=n;i++)
				q.push_front(i);
		else 
		{	
			for(int i=now;i<=n;i++)
				q.push_back(i);
		}
		
		}
		for(auto c:q)
		{
			cout<<c<<' ';
		}
		cout<<endl;
		
	}
}  
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	int t;
	cin>>t;
	while(t--)
		solve();
} 

D-Haitang and Uma Musume_2024牛客暑期多校训练营8 (nowcoder.com)

就是纯纯模拟题,请添加图片描述

但是这一句话要理解一下,这个type 是必须要outside 才计算,然后虽然说是结束后计算,但是这个结束后对应的此时,即此时要加上。

然后虽然没有卡精度,下一次还是的要用整数模拟吧。

#include<bits/stdc++.h>
using namespace std;
#define int long long  
#define x first
#define y second
//#define i128 __int128

const int N = 7;
array<int,12> Uma;
/*
speed0	sta0	power0	guts0	wis0	
0		1		2		3		4				
speedx	stax	powerx	gutsx	wisx
5		6		7		8		9
*/
array<int,15> card[N];
/*
friend	drive+	train	speed0	sta0	power0	guts0	wis0
0		1		2		3		4		5		6		7
speed+	sta+	power+	guts+	wis+
8		9		10		11		12
*/
vector<vector<vector<int> > >  base(6,vector<vector<int> >(5,vector<int>(6,0)));
/*
speed	sta		power	guts	wis 	skill
0		1		2		3		4		5
*/
int lv;
int skill = 120;
double coef[]={-0.2,-0.1,0,0.1,0.2};

void inibas()
{
	base[1][0][0] = 10;
	base[1][3][0] = 4;
	base[1][4][0] = 2;
	base[1][1][1] = 9;
	base[1][2][1] = 5;
	base[1][0][2] = 5;
	base[1][2][2] = 8;
	base[1][3][2] = 4;
	base[1][1][3] = 4;
	base[1][3][3] = 8;
	base[1][4][4] = 9;
	base[1][0][5] = 2;
	base[1][1][5] = 2;
	base[1][2][5] = 2;
	base[1][3][5] = 2;
	base[1][4][5] = 4;
	base[3] = base[4] = base[5]= base[2] = base[1];
	for(int i=2;i<=5;i++)
		for(int j=0;j<=4;j++)
			base[i][j][j] +=i-1;
	base[5][3][0] = 5;
	base[3][4][0] = 3;
	base[4][4][0] = 3;
	base[5][4][0] = 4;
	base[3][2][1] = 6;
	base[4][2][1] = 6;
	base[5][2][1] = 7;	
	base[5][3][2] = 5;
}
int timtype[9];

signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	inibas();
	for(int i=0;i<10;i++)
		cin>>Uma[i];
	//cout<<Uma[2]<<endl;
	for(int i=1;i<=6;i++)
	{	for(int j=0;j<13;j++)
			cin>>card[i][j];
		for(int j=0;j<5;j++)
		{
			Uma[j] += card[i][j+3];
		}
	}
	//cout<<Uma[2]<<endl;
	
	int n;
	cin>>n;
	lv = 1;
	//int ls = 0;
	
	while(n--)
	{
		int summer,weight,drive,type,s;

		cin>>summer>>weight>>drive>>type>>s;
		
		vector<pair<int,int> > S(s+1);
		for(int i=1;i<=s;i++)
			cin>>S[i].x>>S[i].y; // y=1 表示 frind train
		
		if(summer)
			lv = 5;
		else 
			lv = min(1+timtype[type]++/4,5ll);
		

		for(int j = 0;j<6;j++) //
		{	array<double,6> multi;
			int presentX = 0;
			// \sumX_+
			for(int i=1;i<=s;i++)
				presentX += card[S[i].x][j+8];
			
			multi[0] = presentX + base[lv][type][j];
			multi[1] = 1;
			//\proi friend
			for(int i=1;i<=s;i++)
			{	if(S[i].y){
				double mul = 1 + 0.01*card[S[i].x][0];
				multi[1] *=mul;
				}
			}
			
			//train
			multi[2] = 1;
			for(int i=1;i<=s;i++)
				multi[2] += 0.01*card[S[i].x][2];
			//drive
			multi[3]= 1;
			int presentdrive = 0;
			for(int i=1;i<=s;i++)
				presentdrive +=card[S[i].x][1];
			
			multi[3] += coef[drive]*(1+0.01*presentdrive);
			//Uma rate
			multi[4] = 1 + 0.01*Uma[j+5];
			//support card 
			
			multi[5] = 1 + 0.05*s;
			double add = 1;
			// if(j==2)
			// {
				// for(int i=0;i<6;i++)
					// cout<<multi[i]<<' ';
				// cout<<endl;
// 				
			// }
			for(int i=0;i<6;i++)
			{
				add*=multi[i];
			}
			//cout<<j<<' '<<add<<endl;
			
			if(weight == 1 && j==0 )
				continue ;
			if(j == 5)
				skill += (int)(add);
			else 
				Uma[j] = min(1200ll,Uma[j] + (int)(add));
			
		}
	
		
		for(int i=0;i<5;i++)
			cout<<Uma[i]<<' ';
		cout<<skill<<endl;
	

	}
} 

https://ac.nowcoder.com/acm/contest/81603/G

https://ac.nowcoder.com/acm/discuss/blogs?tagId=270086 看了这个博客补的

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N = 2000+20;

int dp[N][N*2][3];
// dp[i][j][k] 表示到当前i,选择 j 是失败,然后胜利得到的最大分数 - 平局得到的最大分数 = k 的方案数
 
const int mid = N;
int b[N];
int pre[N];
int pow_3[N];
const int mod = 998244353;
 
signed main (){
	std::ios::sync_with_stdio(false);  
	cin.tie(NULL); 
	cout.tie(NULL);
	int n;
	cin>>n;
	pow_3[0] = 1;
	for(int i=1;i<=n;i++)
		pow_3[i] = pow_3[i-1]*3%mod;
	
	for(int i=1;i<=n;i++)
		cin>>b[i];
	for(int i=1;i<=n;i++)
		pre[i] = pre[i-1] + (b[i] == -1);
	int ans = 0;
	// 为什么初始化只要初始化一个?
	/*
	这个mid 这个地方可以转移到每一个地方。
	*/ 
	dp[0][mid][0]= 1;
	
	for(int i=1;i<=n;i++)
	{	
		for(int j = 0;j<3;j++)
		// j 表示上一次选择失败的
			for(int l=0;l<3;l++)
			{	// l 这一次对手出的
				if(b[i] == l || b[i] == -1)
				{	
					
					int g= (l - j +3)%3;
					//cout<<j<<' '<<l<<" "<<g<<endl;
					
					if(g==0)
					{
						for(int k = -i;k<=i;k++)
						{	
							if(k >=0) ans = (ans + dp[i-1][mid+k][j]*pow_3[pre[n] - pre[i]]%mod)%mod ; 
							dp[i][mid + min(k +1 ,1ll) ][(l+2) % 3] = (dp[i][mid +min( k +1 ,1ll) ][(l+2) % 3]+ dp[i-1][mid + k][j]) %mod ;
						}
					}
					else if(g==1)
					{
						for(int k= -i;k<=i;k++)
						{
							if(k<=0) ans = (ans + dp[i-1][mid+k][j]*pow_3[pre[n] - pre[i]]%mod)%mod ;
							dp[i][mid + (1-k)][(l+2)%3] = (dp[i][mid + (1-k)][(l+2)%3] + dp[i-1][mid+k][j])%mod ;
						}
					}
					else 
					{
						for(int k=-i;k<=i;k++)
						{
							ans = (ans + dp[i-1][mid+k][j]*pow_3[pre[n] - pre[i]]%mod)%mod ;
							dp[i][mid + max(k+1,1ll)][(l+2)%3] = (dp[i][mid + max(k+1,1ll)][(l+2)%3] + dp[i-1][mid+k][j])%mod ;
						}
					}
					
				}
				
			}
	}
	cout<<ans<<endl;
	
		
	
} 


  • 10
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值