赛前大脑升级(洛谷 NOIP 2023 模拟赛)

P9836

首先注意到我们不需要考虑所有因数,因为任何操作都可以拆成多次质因数操作:而质因数个数是很少的。

最后我们希望的是所有的因数个数之积,众所周知 \(n=\prod_{i=1}^m p_i^{a_i}\) 的因数个数是 \(\prod_{i=1}^m(a_i+1)\),那么其实就是把所有质因数在每个位置出现的次数分别加一再乘起来。

这样我们就可以从质因数的角度考虑,由于顺序无关,先排一手序。

考虑从 \(k\) 中拿出的一个质因数应该放到哪里。如果我们把一个质因数放到了一个已经有 \(x\) 个她的位置,那么对答案的贡献是 \(\frac{x+1}{x}\),显然 \(x\) 越小越优。所以可以暴力模拟,塞到该质因数最少的里面去。

#include<bits/stdc++.h>
#define eps 1e-10
#define INF 0x3f3f3f3f
#define rep(i, l, r) for(int i=(l); i<=(r); i++)
#define per(i, r, l) for(int i=(r); i>=(l); i--)
#define ls k<<1
#define rs k<<1|1
#define tmid ((tr[k].l+tr[k].r)>>1)
#define nmid ((l+r)>>1)
#define pub push_back
#define all(v) v.begin(), v.end()
#define pii pair<int, int>
#define mkp make_pair
#define x first
#define y second
using namespace std;
#define int long long
inline void Read(int &x){
  int f=1; x=0;
  char c=getchar();
  while(c<'0' || c>'9'){
    if(c=='-') f=-1;
    c=getchar();
  }
  while(c>='0' && c<='9'){
    x=(x<<3)+(x<<1)+c-'0', c=getchar();
  }
  x*=f;
}
const int N=1e4+10, p=998244353;
int n, w, a[N];
vector<int> cnt[N];
bool np[N]; int pri[N];
void Init(){
  np[1]=1;
  rep(i, 2, 10000){
    if(!np[i]) pri[++pri[0]]=i;
    for(int j=1; j<=pri[0] and i*pri[j]<=10000; j++){
      np[i*pri[j]]=1;
      if(i%pri[j]==0) break;
    }
  }
}
signed main(){
  Read(n), Read(w);
  Init();
  rep(i, 1, n){
    Read(a[i]);
    rep(j, 1, pri[0]){
      int tmp=0;
      while(a[i]%pri[j]==0) a[i]/=pri[j], tmp++;
      cnt[j].pub(tmp);
    }
  }
  rep(j, 1, pri[0]) sort(all(cnt[j]));
  rep(j, 1, pri[0]) if(w%pri[j]==0){
    int tmp=0;
    while(w%pri[j]==0) w/=pri[j], tmp++;
    // tmp Vga pri[j]
    while(tmp){
      int pos=0, now=cnt[j][0];
      while(pos<=(int)cnt[j].size()-1){
        if(cnt[j][pos]==now and tmp) cnt[j][pos]++, pos++, tmp--;
        else break;
      }
    }
  }
  int ans=1;
  rep(j, 1, pri[0]) for(int i: cnt[j]){
    ans=ans*(i+1)%p;
  }
  printf("%lld\n", ans);
  return 0;
}

P9837

申必构造,想不出是黑,想出来是橙。

注意到二元组总共的对数和表格里的对数恰好是相等的,即需要考虑如何分配他们。

分配一定有某种规律,我们可以尝试一下按照不同的标准来分类这些二元组,依次填入。

最后发现,可以根据两数之差分组,\(1\ 2\) 列之间是差 \(1\) 的二元组,\(2\ 3\) 列之间是差 \(2\) 的二元组……以此类推,我们按照列依次填入。在手模时会发现,如果要保证都能填,需要加减交替填。(具体见代码)

但是填的时候会碰到一个问题,有时候前面的数不一定想要。例如在第一列填上 \(1\sim n\) 后,第二列我们希望填 \(2\sim n\),这样才能组成所有差 \(1\) 的二元组。但是此时要求第一列对应位置应该是 \(1\sim n-1\)。注意到目前填数个数相同的行之间是可以交换的,所以我们可以直接把 \(n\) 换到最顶上,这样就可以填上第二行。然后如果你继续手模,会发现填第三列不用换、填第四列需要换、填第五列不用换……

这时候你可能已经开始写了,但是发现不好写。再回来找规律,发现第一列是有规律的,而按照我们的规则,后面的也都可以确定。

如何证明这样也满足第二关的条件呢?注意到如果一行第一个数是 \(x\),那么这一行填的数就是 \(x,x+1,x-1,x+2,x-2,\dots\) 一直到不能填,所以一定是不重复的。

#include<bits/stdc++.h>
#define eps 1e-10
#define INF 0x3f3f3f3f
#define rep(i, l, r) for(int i=(l); i<=(r); i++)
#define per(i, r, l) for(int i=(r); i>=(l); i--)
#define ls k<<1
#define rs k<<1|1
#define tmid ((tr[k].l+tr[k].r)>>1)
#define nmid ((l+r)>>1)
#define pub push_back
#define all(v) v.begin(), v.end()
#define pii pair<int, int>
#define mkp make_pair
#define x first
#define y second
using namespace std;
inline void Read(int &x){
  int f=1; x=0;
  char c=getchar();
  while(c<'0' || c>'9'){
    if(c=='-') f=-1;
    c=getchar();
  }
  while(c>='0' && c<='9'){
    x=(x<<3)+(x<<1)+c-'0', c=getchar();
  }
  x*=f;
}
const int N=4010;
int n, t;
vector<int> ans[N];
int main(){
  Read(n), Read(t);
  ans[1].pub(n);
  for(int i=2; i+1<=n; i+=2){
    int o=i/2;
    ans[i].pub(o), ans[i+1].pub(n-o);
  }
  if(n%2==0) ans[n].pub(n/2);
  rep(l, 1, n-1){
    rep(i, l+1, n){
      if(l&1) ans[i].pub(ans[i].back()+l);
      else ans[i].pub(ans[i].back()-l);
    }
  }
  rep(i, 1, n){
    for(int x: ans[i]) printf("%d ", x);
    puts("");
  }
  return 0;
}

P9838

首先考虑一个排列怎么算答案:\(\sum_{i=1}^ni(n-i+1)f(p_i)\)

而看到 \(\text{lowbit}\) 应该有一个 \(\log\) 的直觉,即 \(\text{lowbit}\) 不同的最多 \(\log n\) 个。而相同 \(\text{lowbit}\) 的排列一下会贡献非常多的某一个好感度。

而看到阶乘应该有另一个直觉,就是他非常巨大,相比于数据范围的 \(k\le 10^{18}\) 也非常巨大。

综合这两个直觉,我们发现,\(n\) 比较大的时候这个题目等价于查询最小好感度,多大呢?题解说\(n>28\),所以需要查询 \(k\) 小值的范围实际上很小,可以考虑暴力一点的做法。

对于 \(n\le 28\) 的情况,我们首先预处理出 \(n\) 以内每种 \(\text{lowbit}\) 的有多少个,然后去一下重,通过除以个数的阶乘达到。这样我们就可以不管填的数具体是什么,只管某种数填了几个。

此时只有五种 \(\text{lowbit}\),而且个数不多,根据经验,这时应当利用他们做 DP 状态。然后我们希望计数,那么就肯定要把当前的好感度放到状态里,即 \(f_{i,c_1,\dots,c_5}\) 表示 \(\text{lowbit}\)\(k\) 的已经填了 \(c_k\) 个、当前好感度为 \(i\) 时的方案数。显然可以通过记忆化搜索完成这个 DP。

DP 完了,毛估估一下总共的好感度也就几千,所以我们可以枚举好感度,看什么时候到 \(k\)

对于 \(n>28\) 的情况,我们考虑什么时候最小。由于和同近积大,我们直观上看应该把越小的 \(\text{lowbit}\) 越放到中间。可以从中间开始直接模拟这个过程,但不能一个个填,发现可以一次填掉所有 \(\text{lowbit}\) 相同的,只要稍微推导一下就能 \(O(1)\) 计算。(假设 \(1\sim m\) 都填了 \(i\),那么这些数的贡献就是 \(\sum_{j=1}^mj(n+1-j)i=i((n+1)\sum_{j=1}^mj-\sum_{j=1}^mj^2)\),接下来就可以套公式计算了)

这道题结合了几种典型性质的应用,也提醒了我自己找不到小 H 的残酷事实,是一道好题。

#include<bits/stdc++.h>
#define eps 1e-10
#define INF 0x3f3f3f3f
#define rep(i, l, r) for(int i=(l); i<=(r); i++)
#define per(i, r, l) for(int i=(r); i>=(l); i--)
#define ls k<<1
#define rs k<<1|1
#define tmid ((tr[k].l+tr[k].r)>>1)
#define nmid ((l+r)>>1)
#define pub push_back
#define all(v) v.begin(), v.end()
#define pii pair<int, int>
#define mkp make_pair
#define x first
#define y second
using namespace std;
#define int long long
inline void Read(int &x){
  int f=1; x=0;
  char c=getchar();
  while(c<'0' || c>'9'){
    if(c=='-') f=-1;
    c=getchar();
  }
  while(c>='0' && c<='9'){
    x=(x<<3)+(x<<1)+c-'0', c=getchar();
  }
  x*=f;
}
const int p=998244353;
int T, n, k, cnt[100], fac[100];
inline int Pow(int a, int b=p-2){
  int res=1;
  for(; b; a=a*a%p, b>>=1) if(b&1) res=res*a%p;
  return res;
}
inline int F(int n, int m){
  n%=p, m%=p;
  return (n-m+1+p)%p*m%p;
}
namespace Big {
  // 1*n+2*(n-1)+...+m*(n-m+1)
  inline int Calc(int n, int m){
    n%=p, m%=p;
    int res1=(n+1)*m%p*(m+1)%p*Pow(2)%p;
    int res2=m*(m+1)%p*(2*m+1)%p*Pow(6)%p;
    return (res1+p-res2)%p;
  }
  int tcnt[100];
  int Solve(int n){
    int ans=0, pos=n/2;
    rep(i, 1, 60) tcnt[i]=cnt[i];
    if(n&1){ // fill in the mannaca
      (ans+=F(n, n/2+1))%=p, tcnt[1]--;
    }
    rep(i, 1, 60){
      int tmp=tcnt[i]/2;
      (ans+=(Calc(n, pos)-Calc(n, pos-tmp)+p)%p*i%p*2%p)%=p;
      pos-=tmp;
      if(tcnt[i]&1){
        assert(tcnt[i+1]>0);
        (ans+=F(n, pos)*(2*i+1)%p)%=p, pos--, tcnt[i+1]--;
      }
    }
    return (ans%p+p)%p;
  }
}
namespace Small {
  int f[18][10][5][3][2][10000];
  int DFS(int n, int c1, int c2, int c3, int c4, int c5, int sum){
    if(sum<0 or c1<0 or c2<0 or c3<0 or c4<0 or c5<0) return 0;
    if(sum==0){
      if(c1 or c2 or c3 or c4 or c5) return 0;
      return 1;
    }
    int &res=f[c1][c2][c3][c4][c5][sum], pos=(c1+c2+c3+c4+c5)%p;
    if(~res) return res;
    res=0, pos=F(n, pos);
    res+=DFS(n, c1-1, c2, c3, c4, c5, sum-pos);
    res+=DFS(n, c1, c2-1, c3, c4, c5, sum-pos*2);
    res+=DFS(n, c1, c2, c3-1, c4, c5, sum-pos*3);
    res+=DFS(n, c1, c2, c3, c4-1, c5, sum-pos*4);
    res+=DFS(n, c1, c2, c3, c4, c5-1, sum-pos*5);
    return res;
  }
  int Solve(int n, int k){
    int sum=Big::Solve(n);
    rep(i, 1, 5) k=(k-1)/fac[cnt[i]]+1; // real rank
    memset(f, -1, sizeof f);
    while(1){
      int tmp=DFS(n, cnt[1], cnt[2], cnt[3], cnt[4], cnt[5], sum);
      if(tmp>=k) return sum;
      k-=tmp, sum++;
    }
  }
}
signed main(){
  Read(T);
  while(T--){
    Read(n), Read(k), fac[0]=1;
    rep(i, 1, 15) fac[i]=fac[i-1]*i;
    rep(i, 1, 60) cnt[i]=(n>>i-1)-(n>>i);
    if(n>=30) printf("%lld\n", Big::Solve(n));
    else printf("%lld\n", Small::Solve(n, k));
  }
  return 0;
}

P9839

有点申必,改天再搞。

posted @ 2023-11-13 08:39  ajthreac  阅读(51)  评论(0编辑  收藏  举报