OI 笑传 #33

今天是 bct Day 3,赛时 \(100+20+15+50=185\),rk.11。

没有挂分很舒适。

评价是 ok 场,复习(?)了下期望这一块。写了 T1,T4 的拍子。

T1

考虑从小到大填数,这样这个数的排名就是后面空位的数量,填到对应位置即可。

使用二分+树状数组,有两个 log。

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=2e5+5;
int pin[N];
int c[N];
int n;
void add(int p){
  for(;p<=n;p+=(p&(-p))){
    c[p]++;
  }
  return ;
}
int pf(int p){
  int res=0;
  for(;p>0;p-=(p&(-p))){
    res+=c[p];
  }
  return res;
}
int aac(int p,int r){
  return (n-p)-(r-pf(p));
}
int main(){
  
  freopen("contest.in","r",stdin);
  freopen("contest.out","w",stdout);
  
  cin>>n;
  for(int i=1;i<=n;i++){
    int pos;
		cin>>pos;
    int l=1,r=n;
    while(l<r){
      int mid=l+r>>1;
      if(aac(mid,i-1)>pos)l=mid+1;
      else r=mid;
    }
    add(l);
    pin[l]=i;
  }
  for(int i=1;i<=n;i++){
    cout<<pin[i]<<' ';
  }
  
  return 0;
}

T2

没做过期望题,但是人家出了咱就要想对吧。

考虑期望的一般步骤就是从终态往回推。于是设 \(dp_{i,j}\) 表示选了 \(i\) 个单独的原来成对的袜子和 \(j\) 个原来就是单只的袜子的期望。

对于任意一个 \(dp_{i,j},i\ne 0\) 都可以导出一个终态 \(e_{i+1,j}=i+1+j\),概率是 \(\dfrac{i}{2n+m-i-j}\),也就是:

\[dp_{i,j}\leftarrow dp_{i,j}+e_{i+1,j}\times \frac{i}{2n+m-i-j} \]

接下来是正常的转移:

\[dp_{i-1,j} \leftarrow dp_{i-1,j}+dp_{i,j} \times \frac{2n-2(i-1)}{2n+m-(i-1)-j} ,i\ne 0 \]

\[dp_{i,j-1} \leftarrow dp_{i,j-1}+dp_{i,j} \times \frac{m-(j-1)}{2n+m-i-(j-1)} ,j\ne 0 \]

答案取 \(dp_{0,0}\),时间复杂度 \(O(nmT)\)

关于输出有理数,把除法换成乘法逆即可。

赛时想到这里,20pts。

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;
}
struct que{
  int n,m;
}q[20000];
i64 dp[2000][2000];
i64 e[2000][2000];
i64 poww[200000];
const int mod=1e9+7;
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;
}
int main(){

  int T;cin>>T;
  int T1=T;
  int cnt=0;
  bool le20=1;
  poww[0]=0;
  for(int i=1;i<=100000;i++){
    poww[i]=ksm(i,mod-2)%mod;
  }
  while(T--){
    cnt++;
    cin>>q[cnt].n>>q[cnt].m;
    if(q[cnt].n>20&&q[cnt].m>20)le20=0;
  }
  cnt=0;
  while(T1--){
    cnt++;
    int n,m;
    n=q[cnt].n;m=q[cnt].m;
    for(int i=2;i<=n+1;i++){
      for(int j=0;j<=m;j++){
        e[i][j]=i+j;
      }
    }
    for(int i=n;i>=0;i--){
      for(int j=m;j>=0;j--){
        dp[i][j]=(dp[i][j]%mod+(i)*poww[2*n+m-i-j]%mod*e[i+1][j]%mod)%mod;
        if(i>0){
          dp[i-1][j]=(dp[i-1][j]%mod+(2*n-2*(i-1))*poww[2*n+m-(i-1)-j]%mod*dp[i][j]%mod)%mod;
        }
        if(j>0){
          dp[i][j-1]=(dp[i][j-1]%mod+(m-(j-1))*poww[2*n+m-i-(j-1)]%mod*dp[i][j]%mod)%mod;
        }
      }
    }
    cout<<dp[0][0]<<'\n';
    for(int i=0;i<=n+1;i++){
      for(int j=0;j<=m+1;j++){
        dp[i][j]=0;
      }
    }
  }

  
  return 0;
}

我们看看能不能扔掉一个 \(n\) 或者 \(m\) 优化复杂度。其实这一步据说可以直接找规律瞪出来的。

我们改变定义,直接正向求解,\(dp_{i,j}\) 表示选了 \(i\) 双袜子,\(j\) 个单袜子时的期望。放入一个单袜子时有:

\[dp_{i,j}=dp_{i,j-1}\times \frac{2i+j+1}{2i+j} \]

证明就是考虑展开直接使用期望的定义,也就是某个选袜子序列乘上这个序列被选到的概率。

假设我们选出的这个有 \(2i+j-1\) 个袜子在 \(k\) 位置出现了一个袜子和前面选过的袜子配上对了。那么现在接着放入一个单袜子。这个袜子可以放在 \(2i+j\) 个位置上。

分袜子放在 \(k\) 前或者 \(k\) 后的情况,放 \(k\) 前有 \(k\) 个位置,也就是有 \(\dfrac{k}{2i+j}\) 的可能,还让这个序列的答案变成了 \(k+1\)。于是期望就变成了 \((k+1)\dfrac{k}{2i+j}\)

如果放在后面,答案不变,期望变成 \(\dfrac{2i+j-k}{2i+j}\)

通个分就可以得到 \(dp_{i,j}=k\times \dfrac{2i+j+1}{2i+j}\),这个 \(k\) 其实就是 \(dp_{i,j-1}\)。既然我们用 \(k\) 这个具体的数举例了,那把这个数换成期望状物当然是对的了。

还有一个性质是:

\[dp_{i,0}=dp_{i-1,1},i\ge 2 \]

这个比较好理解,因为 \(i\ge 2\) 的时候,那个先来的已经贡献完了,你在往后面加袜子都是没用的,把它当成单只的处理就行。

于是,用第一个式子,我们能把 \(dp_{i,j}\) 一路展开到 \(dp_{i,0}\),就是:

\[dp_{i,j}=dp_{i,0}\times \frac{2i+j+1}{2i+1} \]

然后你能用第二个式子把所有的 \(dp_{i,0}\) 求出来。

于是你可以递推并 \(O(1)\) 求出所有 \(dp_{i,j}\)。时间复杂度 \(O(nT)\)

然后接下来再推式子就要科技了。放了。

T3

赛时只写了 \(O(n!m)\) 的暴力,写 T4 去了。

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;
}
string s[10000];
int main(){
  
  freopen("iqtest.in","r",stdin);
	freopen("iqtest.out","w",stdout); 
//  
  int n,m;cin>>n>>m;
  vector<int> pm;
  int es=1;
  for(int i=1;i<=n;i++){
    cin>>s[i];
    pm.push_back(i);
    es=es*i;
  }
  double eps=1.0/es;
  double ans=0;
  do{
    int exs=(1<<m)-1;
    bool fil=0;
    for(int t:pm){
      bool win=0,los=0;
      for(int j=0;j<s[t].size();j++){
        if(!((exs>>j)&1))continue;
        if(s[t][j]=='1')win=1;
        if(s[t][j]=='0')los=1;
      }
      if(win&&los){
        for(int j=0;j<s[t].size();j++){
          if(!((exs>>j)&1))continue;
          if(s[t][j]=='0')exs^=(1<<j);
        }
      }
      if(!(exs&1)){fil=1;break;}
    }
    if(fil)continue;
    else ans+=eps;
  }while(next_permutation(pm.begin(),pm.end()));
  
  cout<<fixed<<setprecision(16)<<ans; 

  
  return 0;
}

发现题目很多人很少,于是复杂度主体不能是题目数量。

我们考虑状压枚举人的存活情况,对于每一种存活情况,可以处理出能改变这种存活情况的题目。

然后有个重要的性质是:存活的人越少,能改变存活情况的题目也越少,且改变后的状态的题目是之前的子集。

这样 DP 就是没有后效性的了。于是设 \(f_s\) 表示存活的人状态为 \(s\),枚举能改变状态的题目集合 \(a_s\),大小为 \(k\)

\[f_s=\frac{1}{k}\sum_{i=1}^{k}f_{s \And a_i} \]

之后又要用高科技了,放。

T4

贪心是对的,于是数据点分治+线段树有 \(50\) pts。

后面维护置换环就要用平衡树了,放了。

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=5e5+5555;
i64 c[N];
int p[N];
int cpy[N];
int pos[N];
int n,m;
i64 endcst;
i64 calc(){
  for(int i=1;i<=n;i++)p[i]=cpy[i],pos[p[i]]=i;
  i64 ans=c[n];
  i64 cst=0;
  for(int i=n;i>=1;i--){
    if(pos[i]!=i){
      int l=pos[i];
      pos[p[i]]=pos[i];
      pos[i]=i;
      swap(p[l],p[i]);
      cst++;
    }
    ans=min(ans,cst+c[i-1]);
  }
  ans=min(ans,cst);
  return ans;
}
i64 bpm[N];
i64 calc1(){
  for(int i=1;i<=n;i++)p[i]=cpy[i],pos[p[i]]=i;
  i64 ans=c[n];
  bpm[n]=c[n];
  i64 cst=0;
  for(int i=n;i>=1;i--){
    if(pos[i]!=i){
      int l=pos[i];
      pos[p[i]]=pos[i];
      pos[i]=i;
      swap(p[l],p[i]);
      cst++;
    }
    ans=min(ans,cst+c[i-1]);
    bpm[i-1]=cst+c[i-1];
  }
  ans=min(ans,cst);
  endcst=cst;
  return ans;
}
struct qur{
  int op;
  int x;int y;
  i64 c1;
}q[N];
struct seg{
  int l;int r;
  i64 v;
  i64 lzt;
}t[N<<2];
void b(int p,int l,int r){
  t[p].l=l;t[p].r=r;
  t[p].v=bpm[l];
  t[p].lzt=0;
  if(l==r){
    return ;
  }
  int mid=l+r>>1;
  b(ls,l,mid);b(rs,mid+1,r);
  t[p].v=min(t[ls].v,t[rs].v);
  return ;
}
void pushdown(int p){
  if(!t[p].lzt)return ;
  t[ls].v+=t[p].lzt;
  t[rs].v+=t[p].lzt;
  t[ls].lzt+=t[p].lzt;
  t[rs].lzt+=t[p].lzt;
  t[p].lzt=0;
  return ;
}
void add(int p,int l,int r,int c1){
  if(l<=t[p].l&&t[p].r<=r){
    t[p].v+=c1;
    t[p].lzt+=c1;
    return ;
  }
  pushdown(p);
  int mid=t[p].l+t[p].r>>1;
  if(l<=mid)add(ls,l,r,c1);
  if(mid<r) add(rs,l,r,c1);
  t[p].v=min(t[ls].v,t[rs].v);
  return ;
}
i64 qu(){return t[1].v;}
int main(){
//  
  freopen("perm.in","r",stdin);
  freopen("perm.out","w",stdout);

  cin>>n>>m;
  for(int i=1;i<=n;i++)cin>>c[i];
  for(int i=1;i<=n;i++){cin>>p[i];cpy[i]=p[i];}
  if(n<=2000&&m<=2000){
    cout<<calc()<<'\n';
  for(int i=1;i<=m;i++){
  	int op;cin>>op;
    if(op==1){
      int x,y;cin>>x>>y;
      swap(cpy[x],cpy[y]);
    }
    if(op==2){
      int l,r,ci;cin>>l>>r>>ci;
      for(int i=l;i<=r;i++){
        c[i]+=ci;
      }
    }
    cout<<calc()<<'\n';
	} 
  return 0; 
  }
  bool no1=1,no2=1;
  for(int i=1;i<=m;i++){
    int op;cin>>op;
    if(op==1){
      no1=0;
      int x,y;cin>>x>>y;
      q[i].op=op;
      q[i].x=x;q[i].y=y;
    }
    if(op==2){
      int l,r,c1;cin>>l>>r>>c1;
      no2=0;
      q[i].op=op;
      q[i].x=l;q[i].y=r;
      q[i].c1=c1;
    }
  }
  if(no1){
    cout<<calc1()<<'\n';
    b(1,1,n);
    for(int i=1;i<=n;i++){
      int op=q[i].op;
      if(op==1){

      }
      else{
        int l=q[i].x,r=q[i].y,c1=q[i].c1;
        add(1,l,r,c1);
      }
      cout<<min(qu(),endcst)<<'\n';
    }
  }
  
  return 0;
}

DP 选讲

NOIP2021 方差

NOIP2021 数列

CSP-S 2021 括号序列

posted @ 2025-11-22 21:37  hm2ns  阅读(0)  评论(0)    收藏  举报