OI 笑传 #21

ワールドエンドガールフレンド

今天是周天 VP 的洛谷的 S 模拟。

结果起晚了。。。十点才开题。打了 3h。

结果是 \(100+100+0+0=200\),T3 暴力没打,T4 啥也不会。大样例挺牛的,都过了就不会挂分,给好评。

下面是赛时。

T1

清新小思维,想了一会知道了后缀选一定是不劣的,于是考虑从后面往前加数,怎么维护前缀和数组中 \(0\) 的个数。

我做这个用了个比较猎奇的东西就是初始时维护一个指针 \(p\) 指向 \(0\),从后往前加数,假设加 \(a_i\),那么就把指针现在指的位置 \(+1\),之后把指针移到 \(p-a_i\) 那个地方,每次移动之后统计移动到的位置的最大值就是答案。

相当于是一个移动参考系的过程,\(p\) 指向的就是原点的位置。

我觉得挺有道理的,想完写完并过了所有大样例用了 20min。

实现用 map 即可。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e6+5;
map<i64,int> cnt;
int arr[N];
void init(){cnt.clear();return ;}
void solve(){
  int n;cin>>n;
  for(int i=1;i<=n;i++){
    arr[i]=rd;
  }
  i64 p=0;
  int ans=0;
  for(int i=n;i>=1;i--){
    cnt[p]++;
    p-=arr[i];
    if(cnt.find(p)==cnt.end())cnt[p]=0;
    ans=max(ans,cnt[p]);
  }
  cout<<ans<<'\n';
}
int main(){
  
  int T;cin>>T;
  while(T--){
    init();
    solve();
  }
  
  return 0;
}

T2.1

又是这么一坨题面糊我脸上,想起来去年超速检测。

观察了一下猜又是个性质计数题,于是先去开了 T3。

T3.1

事数据结构,这东西骗分好说啊,于是先回去看 T2 了。

T4 事博弈+计数,这东西在 T4 就不是什么好东西。

T2

部分分似乎很少。只能去看性质了。

在家里 walkaround 了一会发现选出来的点必须同高。

然后选出来的那些子树我们只关心它的最大深度,这个是显然的,因为树高肯定得连续的。

但是这个大树似乎很烦,我再想想。

然后又出了个实际错误的结论是我们选出来的第一棵子树必须是深度最小的那一棵。

于是好像把每种深度的点及其引出来的子树按照高度统计一下,对于每种高度分别计数,再加起来就做完了?

再想想式子,为了不重复,我们每次都指定一个点作为 \(1\) 位置的点,这样计数一定是好的,不重复,而且由于引出子树的深度相同的点是等价的,计数完之后乘个数量就行了。

考虑现在钦定了一个点,设深度最小的点的数量有 \(cnt_1\) 个,于是枚举从剩下的 \(cnt_1-1\) 个点里面选出来了 \(i\) 个点,当然是必须要选的,所以 \(i\in[1,\min(cnt_1-1,k-1)]\) 这个好说。

再设其它点有 \(cnt_2\) 个,显然 \(cnt_1+cnt_2\) 就是深度为这个的点的数量,然后式子就是:

\[res=\sum_{i=1}^{\min(cnt_1-1,k-1)} \binom{i}{cnt_1-1} \times \binom{k-i-1}{cnt_2} \]

之后累加进答案就是:

\[ans\leftarrow ans+res\times (k-1)! \times cnt_1 \]

注意时刻取模。

做完了??

哦想想从什么深度才能开始计数,显然最深的就是可以的。

哦还有这个 \(cnt_1 \ge 2\),不然罩不住选出来的第一个。

嗯,写写看。

code 1

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e6+100;
const int mod=998244353;
vector<int> e[N];
vector<int> lv[N];
int dep[N];int mdep[N];
i64 fac[N];
i64 x,y;
i64 ksm(i64 a,i64 b){
  i64 res=1,k=a;
  while(b){
    if(b&1){
      res=res*k%mod;
    }
    k=k*k%mod;
    b>>=1;
  }
  return res;
}
i64 inv(i64 c){
  return ksm(c,mod-2);
}
i64 C(i64 n,i64 m){
  return fac[n]*inv(fac[m]*fac[n-m]%mod)%mod;
}
int beg=0x3f3f3f3f;
void dfs(int u,int fa){
  bool leaf=1;
  for(int v:e[u]){
    if(v==fa)continue;
    mdep[v]=dep[v]=dep[u]+1;
    leaf=0;
    dfs(v,u);
    mdep[u]=max(mdep[u],mdep[v]);
  }
  lv[dep[u]].push_back(mdep[u]);
  if(leaf)beg=min(beg,dep[u]);
  return ;
}
int main(){
  
  fac[0]=1;
  for(int i=1;i<=1e6+3;i++)fac[i]=fac[i-1]*i%mod;

  int n;int k;cin>>n>>k;
  for(int i=1;i<n;i++){
    int v;cin>>v;
    e[i+1].push_back(v);
    e[v].push_back(i+1);
  }
  mdep[1]=dep[1]=1;
  dfs(1,0);
  i64 ans=0;
  for(int i=beg;i>=2;i--){
    if(lv[i].size()<=k)continue;
    sort(lv[i].begin(),lv[i].end());
    cout<<ans<<'\n';
    int cnt1=0,cnt2=0;
    int mi=lv[i][0];
    for(int v:lv[i]){if(v==mi)cnt1++;else break;}    
    if(cnt1<=1)continue;
    cnt2=lv[i].size()-cnt1;
    i64 res=0;
    for(int i=1;i<=min(cnt1-1,k-1);i++){
      res=(res+C(cnt1-1,i)*C(cnt2,k-i-1)%mod)%mod;
    }
    ans=(ans+res*fac[k-1]%mod*cnt1%mod)%mod;
    cout<<ans<<'\n';
  }
  cout<<ans;

  
  return 0;
}

一遍写对了,但是过不了第三个样例。

手摸一下发现是让大树挂这相同高度的树,断开其它所有深度比它大的情况。

这个统计也好办啊,判断一下就行。

于是改了改:

code 2

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e6+100;
const int mod=998244353;
vector<int> e[N];
vector<int> lv[N];
int dep[N];int mdep[N];
i64 fac[N];
i64 x,y;
i64 ksm(i64 a,i64 b){
  i64 res=1,k=a;
  while(b){
    if(b&1){
      res=res*k%mod;
    }
    k=k*k%mod;
    b>>=1;
  }
  return res;
}
i64 inv(i64 c){
  return ksm(c,mod-2);
}
i64 C(i64 n,i64 m){
  return fac[n]*inv(fac[m]*fac[n-m]%mod)%mod;
}
int beg=0x3f3f3f3f;
void dfs(int u,int fa){
  bool leaf=1;
  for(int v:e[u]){
    if(v==fa)continue;
    mdep[v]=dep[v]=dep[u]+1;
    leaf=0;
    dfs(v,u);
    mdep[u]=max(mdep[u],mdep[v]);
  }
  lv[dep[u]].push_back(mdep[u]);
  if(leaf)beg=min(beg,dep[u]);
  return ;
}
int main(){
  
  fac[0]=1;
  for(int i=1;i<=1e6+3;i++)fac[i]=fac[i-1]*i%mod;

  int n;int k;cin>>n>>k;
  for(int i=1;i<n;i++){
    int v;cin>>v;
    e[i+1].push_back(v);
    e[v].push_back(i+1);
  }
  mdep[1]=dep[1]=1;
  dfs(1,0);
  i64 ans=0;
  for(int i=beg;i>=2;i--){
    if(lv[i].size()<=k)continue;
    sort(lv[i].begin(),lv[i].end());
    cout<<ans<<'\n';
    int cnt1=0,cnt2=0;
    int mi=lv[i][0];
    for(int v:lv[i]){if(v==mi)cnt1++;else break;}    
    if(cnt1<=1)continue;
    cnt2=lv[i].size()-cnt1;
    i64 res=0;
    if(cnt2==k-1)res=1;
    for(int i=1;i<=min(cnt1-1,k-1);i++){
      res=(res+C(cnt1-1,i)*C(cnt2,k-i-1)%mod)%mod;
    }
    ans=(ans+res*fac[k-1]%mod*cnt1%mod)%mod;
    cout<<ans<<'\n';
  }
  cout<<ans;
  
  return 0;
}

现在样例都过了,此时已经过去 1h20min。

满心欢喜去测大样例,结果只过了满二叉树的 case。

我。。。。这还有啥情况啊。

于是只能写对拍了,写个 \(k=2\) 的就行了,因为大样例就是这个。

写完拍上又是 10min。。。

最后还真拍出来了,就是可以选任意深度的点,只要保证大树和其它树能交出来选的深度就行。

具体我在草纸上分了三种情况,就是选的深度最小的,中间的,最大的,三种情况。

然后每种情况钦定一个点做 \(1\) 号树,考虑选不选和自己相同深度的点的情况。

这里式子和上面其实挺像的,唯一不同的就是选最大深度树的时候。这时候 \(k\) 个点必须选的全是最大深度树,断深度小的一定是不行的。

于是式子就是:

\[\binom{k-1}{cnt_1-1} \times (k-1)! \times cnt_1 \]

这下应该对了,细细实现了一下就过了所有大样例。

赛后知道这东西评了蓝,我??这 DP 都不用应该到不了蓝吧。

写完+调完之后过去了 2h10min。

code

Show me the code
#define rd read()
#define mkp make_pair
#define ls p<<1
#define rs p<<1|1
#define rep(i,a,b) for( int i=(a); i<=(b); ++i)
#define per(i,a,b) for( int i=(a); i>=(b); --i)
#include<bits/stdc++.h>
using namespace std;
typedef long long i64;
typedef unsigned long long u64;
typedef unsigned int u32;
typedef __int128 i128;
i64 read(){
  i64 x=0,f=1;
  char c=getchar();
  while(c>'9'||c<'0'){if(c=='-') f=-1;c=getchar();}
  while(c>='0'&&c<='9'){x=(x<<3)+(x<<1)+(c^48);c=getchar();}
  return x*f;
}
const int N=1e6+100;
const int mod=998244353;
vector<int> e[N];
vector<int> lv[N];
int dep[N];int mdep[N];
i64 fac[N];
i64 x,y;
i64 ksm(i64 a,i64 b){
  i64 res=1,k=a;
  while(b){
    if(b&1){
      res=res*k%mod;
    }
    k=k*k%mod;
    b>>=1;
  }
  return res;
}
i64 inv(i64 c){
  return ksm(c,mod-2);
}
i64 C(i64 n,i64 m){
  if(n<m)return 0;
  return fac[n]*inv(fac[m]*fac[n-m]%mod)%mod;
}
int beg=0;
void dfs(int u,int fa){
  bool leaf=1;
  for(int v:e[u]){
    if(v==fa)continue;
    mdep[v]=dep[v]=dep[u]+1;
    leaf=0;
    dfs(v,u);
    mdep[u]=max(mdep[u],mdep[v]);
  }
  lv[dep[u]].push_back(mdep[u]);
  if(leaf)beg=max(beg,dep[u]);
  return ;
}
int main(){
  
  fac[0]=1;
  for(int i=1;i<=1e6+3;i++)fac[i]=fac[i-1]*i%mod;

  int n;int k;cin>>n>>k;
  for(int i=1;i<n;i++){
    int v;v=rd;
    e[i+1].push_back(v);
    e[v].push_back(i+1);
  }
  mdep[1]=dep[1]=1;
  dfs(1,0);
  i64 ans=0;
  for(int i=beg;i>=2;i--){
    if(lv[i].size()<=k)continue;
    sort(lv[i].begin(),lv[i].end());
    int cnt1=0,cnt2=0,cnt3=0;
    int mi=lv[i][0];
    for(int j=0;j<lv[i].size();j++){
      cnt1+=cnt2;cnt2=0;
      // cout<<cnt1<<' '<<cnt2<<' '<<cnt3<<'\n';
      while(j<lv[i].size()&&lv[i][j]==mi)cnt2++,j++;
      if(j<lv[i].size())mi=lv[i][j];
      j--;
      cnt3=lv[i].size()-cnt1-cnt2;
      if(cnt2<=1)continue;
      int res=0;
      if(cnt1==0){
        if(cnt3==k-1)res=1;
        for(int i=1;i<=min(cnt2-1,k-1);i++){
          res=(res+C(cnt2-1,i)*C(cnt3,k-i-1)%mod)%mod;
        }
        ans=(ans+res*fac[k-1]%mod*cnt2%mod)%mod;
      }
      else if(cnt3==0){
        if(cnt2<=k)continue;
        ans=(ans+C(cnt2-1,k-1)%mod*fac[k-1]%mod*cnt2%mod)%mod;
      }
      else{
        if(cnt3+cnt2<=k)continue;
        if(cnt3==k-1)res=1;
        for(int i=1;i<=min(cnt2-1,k-1);i++){
          res=(res+C(cnt2-1,i)*C(cnt3,k-i-1)%mod)%mod;
        }
        ans=(ans+res*fac[k-1]%mod*cnt2%mod)%mod;
      }
    }    
  }
  cout<<ans;

  
  return 0;
}

T3

首先否了一个无脑区间覆盖的贪心做法。

然后想到了可以记录下每个点被停止服务的右端点在哪里。

这个东西可以区间覆盖做。

然后好像这个停止服务的位置是有单调性的!

于是考虑询问 \(l,r\) 车站,我们一定可以在 \(l\) 找到一个位置 \(k\),使得 \(k\) 不能服务到 \(r\),但 \(k-1\) 可以,这个线段树二分做是好说的。

于是 \(k\sim l\) 的答案就是自己停止服务右端点加一位置的车站和自己的距离差。\(1\sim k-1\) 的答案就是自己到 \(r\) 的距离差。

前面那一段当然取 \(k-1\) 是最优的,后面呢?

又想了想发现我们只关心后面这段的最小值。

但是你最小值就要在区间覆盖的时候维护一遍了。。。

但是好像这种做法能过掉 60pts 的部分分?我觉得已经够牛了。

但是还没实现代码。

posted @ 2025-10-21 23:37  hm2ns  阅读(13)  评论(0)    收藏  举报