今天是 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_{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\) 个单袜子时的期望。放入一个单袜子时有:
证明就是考虑展开直接使用期望的定义,也就是某个选袜子序列乘上这个序列被选到的概率。
假设我们选出的这个有 \(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\) 这个具体的数举例了,那把这个数换成期望状物当然是对的了。
还有一个性质是:
这个比较好理解,因为 \(i\ge 2\) 的时候,那个先来的已经贡献完了,你在往后面加袜子都是没用的,把它当成单只的处理就行。
于是,用第一个式子,我们能把 \(dp_{i,j}\) 一路展开到 \(dp_{i,0}\),就是:
然后你能用第二个式子把所有的 \(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\):
之后又要用高科技了,放。
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;
}