仮面の街

今天是 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)\)

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\)

後面維護置換環就要用平衡樹了,放了。

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 括號序列