OI 笑传 #6

又是很有意思的一场,建议多来这种模拟赛。

发现最后一题是端午时候最后一场的原,这么有缘分但是竟然忘怎么做了。

但因为是最后一场所以 1h 过了前两题的大样例后就去摆烂了。后两题的文件夹都没建。

赛后竟然没有挂分是准确的 200pts,看了看榜发现有许多同志也是打完两题就去摆烂了啊(

直接看题吧。

T2

初见觉得这题很猎奇啊,怎么这样绑测试点这下一挂直接飞起来了。

但是发现这题里的图的最短路有单调性啊,具体来说就是图的形态不会变,每次统一的增大或者减小固定位置和数量的边的边权,显然是有单调性的。

于是二分下就完事了啊,写了写一遍过大样例了。

场上还担心被 hack,尝试了各种样例都叉不掉于是就放了。

code

Show me the code
#define psb push_back
#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)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll 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=300005;
int n,m;
bool mp[1005][1005];
int pp(int x,int y){
  return (x-1)*n+y;
}
int fx[5]={0,0,1,0,-1};
int fy[5]={0,1,0,-1,0};
struct ed{
  int v;
  double w;
  bool operator<(const ed &x)const{
    return x.w<w;
  }
};
vector<ed> e[N];
int s,edd;double tc;
double dis[N];bitset<N> vis;
bool check(double mid){
  fill(dis,dis+1+n*m+66,1e9);
  vis.reset();
  priority_queue<ed> q;
  q.push(ed{s,0});
  while(q.size()){
    int u=q.top().v;
    double wi=q.top().w;
    q.pop();
    if(vis[u])continue;
    vis[u]=1;dis[u]=wi;
    for(ed to:e[u]){
      int v=to.v;
      double w=to.w;
      if(w<0)w=mid;
      if(dis[v]>dis[u]+w){
        dis[v]=dis[u]+w;
        q.push(ed{v,dis[v]});
      }
    }
  }
  if(dis[edd]>=tc)return 1;
  else return 0;

}
int main(){
  
  // freopen("msg.in","r",stdin);
  // freopen("msg.out","w",stdout);

  cin>>n>>m;
  int sx,sy,ex,ey;
  cin>>sx>>sy>>ex>>ey;
  s=pp(sx,sy);edd=pp(ex,ey);
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      cin>>mp[i][j];
    }
  }
  for(int i=1;i<=n;i++){
    for(int j=1;j<=m;j++){
      if(mp[i][j]==0){
        for(int k=1;k<=4;k++){
          int tx=i+fx[k],ty=j+fy[k];
          if(tx>0&&ty>0&&tx<=n&&ty<=n&&mp[tx][ty]==0){
            if(k%2)
              e[pp(i,j)].push_back(ed{pp(tx,ty),1.0});
            if(k%2==0)
              e[pp(i,j)].push_back(ed{pp(tx,ty),-1.0});
          }
        }
      }
    }
  }
  cin>>tc;
  double l=0,r=1e6;
  while(r-l>=1e-6){
    double mid=(l+r)/2;
    if(check(mid))r=mid;
    else l=mid;
  }
  cout<<fixed<<setprecision(3)<<l;

  
  return 0;
}

T3

场上把这题想了个差不多?

任何截出来的字符串都必须是连续子串这个性质是非常好的,但是问题是题目并没有限制对于每个 \(x\) 结完后剩下的那个尾巴要是哪个子串。

考虑先把这个尾巴强制扔到最后面去,设字符串总长为 \(len\),我们可以截出 \(\left \lfloor \frac{len}{x} \right \rfloor\) 个子串,当然这些子串有可能相同。接下来把这些子串重排,计算方案数。

此时计算方案数的方式是比较好想的,考虑现在我们截出的字符串不同的一共有 \(k\) 种,第 \(i\) 种为字符串 \(pat_i\),有 \(p_i\) 个。分种考虑,把每一种字符串填进 \(\left \lfloor \frac{len}{x} \right \rfloor\) 个位置中去,这个组合数即可。每填入一种字符串就会占用 \(p_i\) 个位置,记现在剩余 \(w\) 个位置,则 \(w\) 初始为 \(\left \lfloor \frac{len}{x} \right \rfloor\),每次考虑完一种字符串后 \(w \leftarrow w-p_i\),显然从组合意义的角度考虑填入字符串的种类顺序是不影响答案的。

上述过程的答案可以表示为:

\[\prod_{i=1}^{k} \binom{\left \lfloor \frac{len}{x} \right \rfloor - \sum _{j=1}^{i-1} p_j}{p_i} \]

上式可以化简为:

\[\dfrac{\left \lfloor \frac{len}{x} \right \rfloor!}{\prod_{i=1}^{k} \left(p_i!\right)} \]

其中 \(!\) 表示阶乘。

化简完后的式子对我们处理尾巴的位置其实是十分有用的。

考虑截出的字符串必须是连续子串这个性质,这导致了能留下尾巴的位置也是 \(\left \lfloor \frac{len}{x} \right \rfloor\),而且我们改变尾巴的过程其实只影响了两个字符串,也就是会删除一个和添加一个,对应回这个式子里你会发现我们可以很快的处理 \(p\) 数组了,这个式子的值也可以很快计算了。

还没完,如果添加的字符串和删除的字符串相同,此时是一个重复的统计,不应被算进答案里去。因此还要支持判定之前是否出现过对应的 \((pat_i,p_i)\) 二元组集合。(字符串的类型也是要考虑的而不是只关注 \(p_i\))。

对于每个 \(x\),处理 \((pat_i,p_i)\) 和判定相同集合的过程都可以用 hash 实现,后者需要手写 hash,时间复杂度记作 \(O(1)\)。统计答案时处理逆元,计算组合数总计是 \(O(\log n)\) 的,所有可能段的总数是一个调和级数的样子为 \(O(n\ln n)\),总的时间复杂度也就是 \(O(n \ln n \log n)\)

你说得对我不想写这个题的代码 qwq。

T4

想想怎么刻画路径上的最大值和最小值都在端点上这个性质。

这里我们先从小到大加入点,每次加入点时合并这个点的邻接点所在的已经被加入的点的集合。更形式化的说,是建立了点权意义下的 Kruskal 最小重构树。

这个上每个子树的根有什么性质?我们只考虑以这个根 \(k\) 为路径端点的路径,这意味着从他到子树内任意一点在原树上的路径中经过的最大的点就是 \(k\)。换言之,这些路径上的最大值在端点上。

我们再构建一个点权意义下的 Kruskal 最大重构树。此时子树根的意义就是到子树内所有点的路径的最小值在端点上。

于是符合要求的路径 \((u,v)\) 的要求就是在一棵重构树上 \(u\)\(v\) 的祖先,在另一棵树上 \(v\)\(u\) 的祖先。我们要做的是统计这些点对的数量。

这个用线段树是好说的,具体来说就是我们在其中一棵重构树上 DFS,在另一棵树上标记经过的点。对于 DFS 到的点,在另一棵树上子树查询即可。

代码非常好写,一遍写对。

code

Show me the code
#define psb push_back
#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)
#define rd read()
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
ll read(){
  ll 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=5e5+5;
vector<int> e[N];
vector<int> mie[N];
vector<int> mae[N];
bitset<N> vis;
int fa[N];
int _find(int u){return fa[u]==u?u:fa[u]=_find(fa[u]);}
pair<int,int> rg[N];
int dfn=0;
int dfr[N];
int c[N];
int a[N]; 
int n,m;
ll lb(ll x){return x&(-x);}
void add(int p,int k){
	a[p]+=k;
	while(p<=dfn){
		c[p]+=k;
		p=p+lb(p);
	}
}
ll prefix(int r){
	ll ans=0;
	while(r>0){
		ans+=c[r];
		r=r-lb(r);
	}
	return ans;
}
ll q(int l,int r){
	return prefix(r)-prefix(l-1);
}
void dfs1(int u,int fa){
  dfn++;
  dfr[u]=dfn;
  rg[u].first=dfn;
  for(int v:mae[u]){
    if(v==fa)continue;
    dfs1(v,u);
  }
  rg[u].second=dfn;
  return ;
}
ll ans=0;
void dfs2(int u,int fa){
  ans+=q(rg[u].first,rg[u].second);
  add(dfr[u],1);
  for(int v:mie[u]){
    if(v==fa)continue;
    dfs2(v,u);
  }
  add(dfr[u],-1);
  return ;
}
int main(){
  
  cin.tie(0);
  ios::sync_with_stdio(0);

  int n;cin>>n;
  for(int i=1;i<n;i++){
    int u,v;cin>>u>>v;
    e[u].push_back(v);
    e[v].push_back(u);
  }
  rep(i,1,n)fa[i]=i;
  for(int i=1;i<=n;i++){
    vis[i]=1;
    for(int v:e[i]){
      if(!vis[v])continue;
      int t=_find(v);
      fa[t]=i;
      mie[t].push_back(i);
      mie[i].push_back(t);
    }
  }
  vis.reset();
  rep(i,1,n)fa[i]=i;
  for(int i=n;i>=1;i--){
    vis[i]=1;
    for(int v:e[i]){
      if(!vis[v])continue;
      int t=_find(v);
      fa[t]=i;
      mae[t].push_back(i);
      mae[i].push_back(t);
    }
  }
  dfs1(1,0);
  dfs2(n,0);
  cout<<ans;
 
  return 0;
}
posted @ 2025-08-05 22:58  hm2ns  阅读(20)  评论(0)    收藏  举报