E-Haitang and Math_2024牛客暑期多校训练营8 (nowcoder.com)
E题其实都可以想到朴素的对于 [ n − 108 , n ] [n-108,n] [n−108,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] [n−108,n] 的质因子分解。
那么其实可以想对于一个质因子,他只有在 [ 0 , 108 ] [0,108] [0,108] 的时候,去对 [ n − 108 , n ] [n-108,n] [n−108,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 a≤b≤1012且b−a≤106 问 [ 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(bloglogb) 如果没算错的话。就是埃氏筛的复杂度。
#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==n−2 的时候,显然无法构造出序列。因为 1 1 1 必然会使得至少一个子区间是不满足的,而且 1 1 1 是放在首位的时候, 1 + x ≤ y 1+x \le y 1+x≤y,如果放在中间显然会影响两个以上使得其不能构造成三角形。
然后对于剩余情况,其实打表前 10 个数会发现,应该是都有的。问题就在于怎么构造。
其实很显然我们的构造肯定是前面一段有 m m m 个合法三角形。后面 n − m n-m n−m 个是构造出 0 个三角形的。
然后如何构造呢。我们会发现其实对于一个三角形。如果任意两个之间差大于等于其中一条边,必然是不行。所以临界条件应该是倍数关系。即 d , 2 d , 3 d d,2d,3d d,2d,3d 然后接下去怎么构造呢 我们发现 2 d , 3 d , d − 1 2d,3d,d-1 2d,3d,d−1 又可以继续下去了。然后就这样一直构造 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,d−1,2d−1,3d−1...1,d+1,2d+1 这样子。然后可能会发现如果我们剩下的数不能完全的分配给这 n − m n-m n−m 个数又应该怎么办?
其实下取整后剩下的数会为 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+1≥3d+1 显然可以组成三角形。
如果有一个余数的时候。我们余数放在前面,因为 d + 2 d ≤ 3 d + 1 d+2d\le3d+1 d+2d≤3d+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 k≥n−3 所以一定有 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;
}