数学
1.Minimize Range
来源https://atcoder.jp/contests/abc450/tasks/abc450_d
错误的思路:
我在最开始观察的时候发现如果存在一种特殊情况只有两个数字的时候把小的加上k之后导致小的数字加上k之后变成了大的,而且让两数字之间的差距变大了。在这种情况下两数和k之间的关系是什么数学推导之后是发现最开始两数之差小于k/2。所以我建了一个小顶堆,只要第一个数字和最大数字之间的差距大于k/2就把这个数字加上k然后push到这个小顶堆里面,然后再维护最大值,直到最小的数字和最大值之间的差距小于k/2,这样就能得到最小的极差
错误的原因:
上面的推导在只有两个数的时候确实是正确的,可是在不止两个数的环境下就不一定是正确的。首先除开次数太多导致的T,我们有种特殊的情况,那就是无论多少次更新,极差一直不会变,但是极差始终大于k/2。
正确的思路:
将这个看作在一个首尾相连的数轴上面,我们要找到最小的极差就是找到一段最小的距离可以涵盖所有的数字,而没有被涵盖到的就是一段两个数字之间的距离,那就等价于找到两个数字之间最大的距离,包含首尾的距离
代码:
点击查看代码
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,k;
cin>>n>>k;
vector<int>b(n);
for(int i=0;i<n;i++){
int a;
cin>>a;
b[i]=a%k;
}
sort(b.begin(),b.end());
int mx=-1;
for(int i=0;i<n-1;i++){
if(mx<b[i+1]-b[i])
mx=b[i+1]-b[i];
}
if(k-b[n-1]+b[0]>mx)
mx=k-b[n-1]+b[0];
cout<<k-mx<<endl;
}
2.小红的排列
来源https://ac.nowcoder.com/acm/contest/130107/C
错误的思路:
最开始的想法是从左往右按个排序,每次该排入奇数的时候从奇数里面选一个,偶数的时候偶数里面选一个,然后对应的奇数和偶数的个数减一,而对应?也就是不确定的时候我的想法是从剩余的奇数和偶数里面随机选一个,然后问号的个数减一
错误的原因:
问题出现在处理不确定的数字的时候,我们由于不知道填进去的这个数字到底是奇数还是偶数,然后我们的操作只是对问号的个数减一,可能会导致后面的还需要填入奇数或者偶数的时候,计数器出现问题
正确的思路:
这道题可以有两个解决的思路,一个是我之前想的从左往右挨个排序;一个是因为我们知道这个排列里面总的奇数和偶数的个数,只要有问号出现那么就代表奇数o和j的个数至少有一个是小于完整排列里面对应的个数的,那么我们在问号里面选出一部分问号把偶数或者奇数的缺少的先给补上,用C(m,n)的算法,如果我们补的是偶数,那么奇数的位置就是最开始是j的位置加上?里面没有被偶数占的位置,最终的答案三者相乘
代码(version1)
注意虽然思路这样想是对的,但是实际代码实现的时候这个代码其实是内存爆的,代码由AI生成,优化部分我还没有想过
点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=998244353;
int main(){
int n;
string s;
cin >> n >> s;
int total_j = n - n/2; // 奇数总数
int total_o = n/2; // 偶数总数
// dp[i][j] 表示:处理完前 i 个字符后,还剩下 j 个奇数的方案数
// 此时剩下的偶数个数一定是 (total_o - (i - (total_j - j)))
// 也就是:已用偶数 = 总步数i - 已用奇数(total_j-j)
vector<vector<ll>> dp(n + 1, vector<ll>(total_j + 1, 0));
// 初始状态:处理 0 个字符,剩下 total_j 个奇数,方案数为 1
dp[0][total_j] = 1;
for(int i = 0; i < n; i++){
for(int j = 0; j <= total_j; j++){
if(dp[i][j] == 0) continue;
// 当前状态:还剩 j 个奇数
// 已经用了 used_j = total_j - j 个奇数
// 已经用了 used_o = i - used_j 个偶数
// 还剩 rem_o = total_o - used_o 个偶数
int used_j = total_j - j;
int used_o = i - used_j;
int rem_o = total_o - used_o;
// 尝试在第 i 位(0-indexed)填奇数
if(s[i] == 'j' || s[i] == '?'){
if(j > 0){ // 还有奇数可用
// 方案数 * 剩下的奇数个数
dp[i+1][j-1] = (dp[i+1][j-1] + dp[i][j] * j) % mod;
}
}
// 尝试在第 i 位填偶数
if(s[i] == 'o' || s[i] == '?'){
if(rem_o > 0){ // 还有偶数可用
// 方案数 * 剩下的偶数个数
dp[i+1][j] = (dp[i+1][j] + dp[i][j] * rem_o) % mod;
}
}
}
}
// 最终答案:处理完 n 个字符,剩下 0 个奇数(也就是所有数字都用完了)
cout << dp[n][0] << endl;
}
代码(version2)
代码实现细节写在本周的周报的周六,链接:https://www.cnblogs.com/wtpavssite/p/19756866
点击查看代码
#include<bits/stdc++.h>
using namespace std;
using ll=long long;
const ll mod=998244353;
struct Z {
ll x;
Z(ll x = 0) : x(x % mod) { if (x < 0) x += mod; }
Z operator*(const Z& other) const { return Z(x * other.x); }
// 如果需要加法也可以加上,虽然这段代码主要用乘法
Z operator+(const Z& other) const { return Z(x + other.x); }
};
struct Comb {
vector<ll> fact, inv_fact;
Comb(int n = 0) { init(n); }
void init(int n) {
fact.resize(n + 1);
inv_fact.resize(n + 1);
fact[0] = 1;
for (int i = 1; i <= n; i++) fact[i] = fact[i - 1] * i % mod;
// 快速幂求逆元
inv_fact[n] = pow_mod(fact[n], mod - 2);
for (int i = n - 1; i >= 0; i--) inv_fact[i] = inv_fact[i + 1] * (i + 1) % mod;
}
ll pow_mod(ll a, ll b) {
ll res = 1;
while (b) {
if (b & 1) res = res * a % mod;
a = a * a % mod;
b >>= 1;
}
return res;
}
// 计算 C(n, m)
ll C(int n, int m) {
if (m < 0 || m > n) return 0;
return fact[n] * inv_fact[m] % mod * inv_fact[n - m] % mod;
}
// 获取阶乘
ll fac(int n) {
return fact[n];
}
} C;
int main(){
int n;
string s;
cin>>n>>s;
ll cnt_j,cnt_o,cnt_a;
cnt_o=n/2;
cnt_j=n-cnt_o;
cnt_a=n;
ll ans=1;
C.init(n);
for(int i=0;i<n;i++){
if(s[i]=='j'){
ans=ans*cnt_j%mod;
cnt_j--;
cnt_a--;
}
else if(s[i]=='o'){
ans=ans*cnt_o%mod;
cnt_o--;
cnt_a--;
}
}
if(cnt_o<0||cnt_j<0||cnt_o+cnt_j!=cnt_a){
cout<<0<<endl;
return 0;
}
ans = (ans * C.C(cnt_a, cnt_o) % mod) * C.fac(cnt_j) % mod*C.fac(cnt_o)%mod;
cout<<ans<<endl;
}
3.小苯的凝聚区间
来源:https://ac.nowcoder.com/acm/contest/130843/E
开始的思路:
虽然我观察到了最大值和最小值一定是区间的两个端点,所以我最开始想的直接就是找到最值然后输出,后来发现其实有些问题。
错误的原因:
虽然找到了两个最值之后往外边扩大不会增大这个凝聚值了,但是存在向里面缩小的时候凝聚值变大的可能。
正确的思路:
我们对凝聚值的公式进行变形,(M−m+1)−(r−l+1)=>(M-m)-(r-l)=>(M-r)-(m-l)。我们可以观察到M和r是关于一个位置上的数字的特征,同理m和l也是这样。当然我们的最大值可能在最小值的左边也有可能在右边,所以我们分类讨论两种情况,发现两种情况的计算公式是不一样的,所以我们在输入最开始的数组的时候就把情况分开,计算数组中特征的最值之差
4.Square pair
来源:https://atcoder.jp/contests/abc342/tasks/abc342_d
开始的思路:
因为考虑到O(n^2)的算法一定会超时,所以我想的是把数据范围内的所有平方数全部求出来,先固定pair的前一个,然后查询数组里面有没有能和前一个数字构成平方数的数字。查找这个的过程我想的是做一个后缀的map记录再第i个位置后面的所有数字的存在。
错误的原因:
这样固定一个数字来找另一个数字存在的思路其实是可行的,但是这个思路错在不是用这样来查找,而且在数据范围内平方数的集合大小可能达到1e10,也会超时。
正确的思路:
首先要知道一个数学性质,一个数字n可以分解成唯一的质因数的幂次的多项式的形式,我们对于一个数字观察他的质因数分解的项,当一个数字是平方数的时候他的分解形式的各个项的幂次一定是偶数,那么不是平方数的数字对其的分解的项中是偶数的项除去之后的项我们对其的幂变成1,这些剩下的数字相乘的结果就是这个数字在这个情景下的核。只有两个数字的核是相同的时候,这两个数字相乘的结果才是平方数。那么我们就只需要记录不同的核有几种,并且每种的个数是多少,用组合数Cx,2来计算。此外对于0来说,因为0与任何一个数字组合得到的数字都是0,也是平方数,那么我们对0的计数要特殊处理一部分是一个0一个非零,一部份是两个0,计算相加(最开始就是只计算了两个0的情况还错了很久)。
代码:
点击查看代码
void solve(){
ll all=0;
ll zeros=0;
int n;
cin>>n;
vector<ll>a(n+1);
for(int i=1;i<=n;i++){
cin>>a[i];
if(a[i]==0){
zeros++;
}
}
all+=zeros*(n-zeros)+zeros*(zeros-1)/2;
vector<map<int,int>>mp(n+1);
for(int i=1;i<=n;i++){
for(int j=2;j*j<=a[i];j++){
while(a[i]%j==0){
mp[i][j]++;
a[i]/=j;
}
}
if(a[i]>1)
mp[i][a[i]]++;
}
for(int i=1;i<=n;i++){
if(a[i]==0){
continue;
}
int t=1;
for(auto [p,s]:mp[i]){
if(s%2==1){
t*=p;
}
}
a[i]=t;
}
map<int,int>cnt;
for(int i=1;i<=n;i++){
if(a[i]==0)continue;
cnt[a[i]]++;
}
for(auto [p,s]:cnt){
if(s>1){
all+=1LL*s*(s-1)/2;
}
}
cout<<all<<endl;
}
浙公网安备 33010602011771号