入营の测试

主播菜死了,只拿了120分

T1



注: 1 ≤ t 1 ≤ 1 0 9 1\le t_1\le10^9 1t1109

考虑从空开始加边。这样做是等效的。所以肯定先尽量使得 max ⁡ { T 1 , T 2 } \max\{T_1,T_2\} max{T1,T2} 最小(其中 T 1 , T 2 T_1,T_2 T1,T2 都是已经形成的子树。现在要代表元上连边)。可以发现刚开始的合并事实上每个点都会算一次,之后子树间的合并最大值还会分别再算一次。如果没有算上,则显然会有遗漏。如果算多了,应该先合并合适的子树。发现在最优的情况下,我们应该最后合并最大的 t i t_i ti 的点。故答案为 ∑ t + ∑ ( u , v ) ∈ E max ⁡ { t u , t v } − max ⁡ { t i } \sum t+\sum\limits_{(u,v)\in E}\max\{t_u,t_v\}-\max\{t_i\} t+(u,v)Emax{tu,tv}max{ti}

T2



注:一个人的排名是学习能力大于他的的人数。

对于每个学生,首先考虑若不给该学生书,怎样安排排名不变。一个显而易见的结论:若一个人看书超了另一个人,除非他也看书,否则没法超过,排名下降。所以只有在学习能力 ≤ ⌊ x i t ⌋ , ≥ x i + 1 \le\left\lfloor\frac{x_i}{t}\right\rfloor,\ge x_i+1 txi,xi+1 的人,可以看书。用组合数算即可。

考虑第二种情形: x i → t × x i x_i\to t\times x_i xit×xi,则所有 [ x i + 1 , t × x i ] [x_i+1,t\times x_i] [xi+1,t×xi] 学生都必须看书。(考虑清楚边界)。在 [ t × x i + 1 , m x ] , [ 1 , x i ] [t\times x_i+1,mx],[1,x_i] [t×xi+1,mx],[1,xi] 学生都可以看书。

#include <bits/stdc++.h>
using namespace std;
#define int long long
const int N=1e5+1,P=1e9+7;

struct {
  int fac[N];
  int ifac[N];
  int inv[N];
  int ans[N];
  int binom(int n,int m){
    if(n<m||m<0) return 0;
    return fac[n]*ifac[m]%P*ifac[n-m]%P;
  }
  void init(int n){
    fac[0]=ifac[0]=inv[0]=1;
    fac[1]=ifac[1]=inv[1]=1;
    for(int i=2;i<=n;++i){
      fac[i]=fac[i-1]*i%P;
      inv[i]=(P-P/i)*inv[P%i]%P;
      ifac[i]=ifac[i-1]*inv[i]%P;
    }
    //printf("C(3,2)=%lld\n",binom(3,2));
  }
  struct {
    int id,x;
  } a[N];
  void LOL(){
    //freopen("rank2.in","r",stdin);
    //freopen("rank2.o","w",stdout); 
    int n,k,t;
    cin>>n>>k>>t;
    init(n);
    for(int i=1;i<=n;++i)
      cin>>a[i].x,a[i].id=i;
    sort(a+1,a+n+1,[](auto a,auto b){
      return a.x<b.x;
    });
    auto Find=[&](int x){ //最大的小于等于x的数的位置 
      int l=1,r=n,mid;
      while(l<=r){
        mid=l+r>>1;
        if(a[mid].x<=x) l=mid+1;
        else r=mid-1;
      }
      return l-1;
    };
    auto Find1=[&](int x){ //最小的大于等于x的数的位置 
      int l=1,r=n,mid;
      while(l<=r){
        mid=l+r>>1;
        if(a[mid].x>=x) r=mid-1;
        else l=mid+1;
      }
      return r+1;
    };
    for(int i=1;i<=n;++i){
      int c1=Find(a[i].x/t);//小于等于 x[i]/t 的,不会影响大于x[i]的数的数量 
      int c2=n-Find(a[i].x);
      //printf("考虑 %lld 前面有 %lld 个数可选,后面有 %lld 个数可选\n",a[i].id,c1,c2);
      int ans1=binom(c1+c2,k);
      //printf("在不选当前数的情形下,有 %lld 种方案\n",ans1);
      int c3=Find(a[i].x*t);//等于 a[i].x*t 的,也要乘 
      //printf("若考虑当前数,则 [x[i]+1,t*x[i]] 都要选\n");
      int c4=Find1(a[i].x+1); //+1///
      //printf("x[i]+1 的位置是 %lld, a[i].x*t 的位置是 %lld\n",c4,c3);
      int ch=k-(c3-c4+1)-1;
      //printf("中间所以数都得选上。还有 %lld 没选的\n",ch); 
      int c5=Find(a[i].x);
      //printf("小于等于 x[i] 的或者大于 t*x[i],都可以乘。\n");
      //printf("分别有 %lld %lld 个。\n",c5,n-c3); 
      int ans2=binom(n-c3+c5-1,ch);
      ans[a[i].id]=(ans1+ans2)%P;
      //printf("ans[%lld]=%lld\n",a[i].id,ans1+ans2);
    }
    for(int i=1;i<=n;++i)
      cout<<ans[i]<<'\n';
  }
} DNF;
main() {
  DNF.LOL();
}

T3



记: a a a 为串 S S S b b b 为串 t t t n n n a a a 长度, m m m b b b 长度。

观察到只需要删除操作。增加其实是无用的。又发现将两个串修改相等其实就是求 LCS ⁡ \operatorname{LCS} LCS。最终答案就是 L e n 1 + L e n 2 − 2 ∗ LCS ⁡ Len_1+Len_2-2*\operatorname{LCS} Len1+Len22LCS

维护每个字符在每个位置之后第一次出现的位置。设 n e i , c ne_{i,c} nei,c 表示位置 i i i 后,字符 c c c 首次出现的位置。用 O ( 26 n ) O(26n) O(26n) 的时间预处理出串 a a a n e ne ne

之后开一个大小 [ 22 ] [ 22 ] [22][22] [22][22] d p dp dp 数组。 d p i , j dp_{i,j} dpi,j 表示考虑到 b b b 的第 i i i 个字符,使得 LCS ⁡ ( a , b ) = j \operatorname{LCS}(a,b)=j LCS(a,b)=j 的最小的 a a a 的右端点。 ∀ i ∈ [ 0 , m ] , d p i , 0 = l − 1 , d p i , ∗ ≠ 0 = inf ⁡ \forall i\in[0,m],dp_{i,0}=l-1,dp_{i,*\ne0}=\inf i[0,m],dpi,0=l1,dpi,=0=inf ∀ i ∈ [ 1 , m ] , ∀ j ∈ [ 1 , i ] , d p i , j = min ⁡ { d p i − 1 , j , n e d p i − 1 , j − 1 + 1 , b i − ′ a ′ } \forall i\in[1,m],\forall j\in[1,i],dp_{i,j}=\min\{dp_{i-1,j},ne_{dp_{i-1,j-1}+1,b_i-'a'}\} i[1,m],j[1,i],dpi,j=min{dpi1,j,nedpi1,j1+1,bia}。含义是要不就单纯考虑前 i − 1 i-1 i1 位,要不就加入最后一位,然后要匹配到对应的点,也就是他下一位匹配到 b i b_i bi。那么 a n s = max ⁡ { d p i , j ∣ d p i , j ≤ r } ans=\max\{dp_{i,j}|dp_{i,j}\le r\} ans=max{dpi,jdpi,jr}

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
int n,m;
char a[N],b[1000];
int dp[300][300];
int ne[N][260];
main(){
  cin.tie(0)->sync_with_stdio(0);
  cin>>(a+1)>>(b+1);
  n=strlen(a+1);
  m=strlen(b+1);
  for(int i=25;~i;--i) 
    ne[n+1][i]=ne[n+2][i]=n+1;
  for(int i=n;i;--i) {
    copy(ne[i+1],ne[i+1]+26,ne[i]);
    ne[i][a[i]-'a']=i;
  }
  int q; cin>>q;
  for(int L,R;q--;) {
    cin>>L>>R;
    //memset(dp,0x3f,sizeof dp);
    for(int i=0;i<=m;++i){
      fill(dp[i]+1,dp[i]+m+1,1e9);
      dp[i][0]=L-1;
    }
    int LCS=0;
    for(int i=1;i<=m;++i)
      for(int j=1;j<=i;++j){
        dp[i][j]=min(dp[i-1][j],ne[dp[i-1][j-1]+1][b[i]-'a']);  //add b[i] or not
        dp[i][j]<=R?LCS=max(LCS,j):0; //update max LCS's ending pos, ends with b[i] (with len of j)
      }
    cout<<R-L+1+m-2*LCS<<'\n';
  }
}

T4



题解:

其实我也看不懂。大体就是离线扫描一遍,配合 n log ⁡ n n\log n nlogn 的并查集(可撤销并查集)搞。(有大佬看懂了吗qwq

#include<bits/stdc++.h>
using namespace std;
const int N=(5e5+1)*2;
#define int long long
#define pb push_back
#define fi first
#define se second
using pii=pair<int,int>;
struct {
  int n,m,t,fa[N],cnt;
  int ans[N],s[N],sz[N],h[N];
  struct{int op,x,y,id;}q[N];
  struct ask{int op,x,id;};
  vector<ask> qq[N];
  pii g[N];
  #define RMX(p,q) (p)<(q)?p=q,0:0; 
  int find(int x){
    if(fa[x]==x) return x;
    int ret=find(fa[x]);
    RMX(g[x],g[fa[x]]);
    return fa[x]=ret;
  }
  int find2(int x){
    if(fa[x]==x) return x;
    return find(fa[x]);
  }
  void merge(int x,int y){
    x=find2(x),y=find2(y);
    if(x==y) return ;
    if(sz[y]>sz[x]) swap(x,y);
    fa[y]=x,h[y]=s[x],sz[x]+=sz[y];
  }
  int query(int x){
    int ans=0;
    while(x^fa[x]) ans+=s[x]-h[x],x=fa[x];
    return ans+s[x]-h[x];
  }
  void LOL(){
    cin.tie(0)->sync_with_stdio(0);
    cin>>n>>m,iota(fa,fa+n+1,0),cnt=n;
    for(int i=1;i<=m;++i) {
      auto&[op,x,y,id]=q[i];
      cin>>op>>x,id=i;
      if(op<5) cin>>y;
      else {
        find(x);
        qq[g[x].fi].pb({-1,x,i});
        qq[i].pb({1,x,i});
        ans[i]+=g[x].se;
      }
      if(op==2)
        fa[find(y)]=fa[find(x)]=++cnt,fa[cnt]=cnt;
      if(op==4) g[find(x)]={i,y};//建立虚点
    }
    iota(fa,fa+cnt+1,0);
    for(int i=1;i<=m;++i){
      auto&[op,x,y,id]=q[i];
      if(op==1) merge(x,y);
      if(op==3) s[find2(x)]+=y;
      for(auto[op,x,id]:qq[i]) 
        ans[id]+=op*query(x);
    }
    for(int i=1;i<=m;++i)
      if(q[i].op==5) cout<<ans[i]<<'\n';
  }
} DNF;
main(){DNF.LOL();}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值