[40](CSP 集训)CSP 联训模拟 2
A.挤压
经典二进制拆位
好像也不是那么经典,CL-22 有提到
那么这个题因为维护的不是贡献和而是贡献平方和,所以考虑怎么计算
假设我们得到了一个异或后的答案 \(x\),不动脑子的写成 \(x=(k_12^{1}+k_22^{2}\cdots)^2\),二项式定理可以拆开,变成 \(\sum_{i\operatorname{and}j}2^{i}\times 2^{j}\) (当 \(i\) 有值时,其贡献为 \(2^i\),\(j\) 同理,拆分后贡献为二者相乘,可以发现,这两者中一旦有一个为 \(0\),则其贡献也为 \(0\),乘起来也就是 \(0\),所以不做统计,关于如何判断 \(i,j\) 有值,CL-22 里有说,就是先拆位开桶,然后如果是奇数就有值,偶数就没有),发现是只有两项的形式,所以直接枚举即可
UPD: 码的谁问的,显然这里不是枚举 \(\sum_{i\operatorname{and}j}2^{i}\times 2^{j}\) 再相加,你好歹得套个期望吧
设 \(f_{i,j,k,l,m}\) 表示考虑到第 \(i\) 位数字,考虑其 \(j,k\) 两位,\(l,m\in\{0,1\}\) 表示其有值/无值(其实也就是出现的 \(1\) 的个数是奇数还是偶数)的概率,注意这里求的概率是关于 “从前 \(i\) 个里选出若干个,最后的结果的第 \(i,j\) 位实现上述状态的概率”,然后你从 \(i\) 转移到 \(i+1\) 的时候,直接去判断当前选还是不选,如果选的话,判断一下新加入的值的第 \(i,j\) 位是否有值,有的话就将对应奇偶性取反
最后统计的时候直接从 \(i=n,l=1,m=1\) 的状态里加和即可,要注意乘以 \(2^i\times 2^j\),统计的时候要注意 \(f_{n,j,j,1,1}\) 这样的结果也要算进去,因为平方拆开是有自平方项的
提前预处理逆元和真实概率,否则跑的贼慢
跟 int_R 学到了炫酷写法
#include<bits/stdc++.h>
using namespace std;
#define int long long
const int p=1e9+7;
int n;
int a[100001],p0[100001];
int f[2][33][33][2][2];
int power(int a,int t){
int base=a,ans=1;
while(t){
if(t&1){
ans=ans*base%p;
}
base=base*base%p;
t>>=1;
}
return ans;
}
const int inv=power(1000000000,p-2);
signed main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%lld",&n);
for(int i=1;i<=n;++i){
scanf("%lld",&a[i]);
}
for(int i=1;i<=n;++i){
scanf("%lld",&p0[i]);
p0[i]=p0[i]*inv%p;
}
for(int i=0;i<=32;++i){
for(int j=0;j<=32;++j){
f[0][i][j][0][0]=1;
}
}
for(int i=1;i<=n;++i){
for(int j=0;j<=32;++j){
for(int k=0;k<=32;++k){
for(int l:{0,1}){
for(int m:{0,1}){
f[i&1][j][k][l][m]=(f[1-(i&1)][j][k][l][m]*(1-p0[i])+f[1-(i&1)][j][k][l^((a[i]>>j)&1)][m^((a[i]>>k)&1)]*p0[i])%p;
}
}
}
}
}
int ans=0;
for(int i=0;i<=32;++i){
for(int j=0;j<=32;++j){
ans+=(1ll<<(i+j))%p*f[n&1][i][j][1][1]%p;
ans%=p;
}
}
cout<<(ans%p+p)%p;
}
B.工地难题
\(Update\) \(S\infty n\)
C.星空遗迹
维护一个胜利者栈,在这个栈里保证前一个元素能赢后一个,只要维护出这个栈,则栈底元素即为胜利者
- 栈空,插入
- 栈顶元素输了或平局,弹出
- 栈顶元素赢了,插入新元素
这样做的依据:
- 两个可以胜中间元素的元素,将其中间元素清除后结果不变(弹掉失败者的依据)
- 一个相同元素的连通块,缩成一个后不影响结果(弹掉平局者的依据)
转化成式子大概就是
你无视这个对 \(1\) 取 \(\max\),发现在整个过程中的 \(f_i\) 的最小值即为要求的字符所在地
感性理解
所以如果你无视掉这个对 \(1\) 取 \(\max\),那么我们就可以通过维护原数组的最小值来求解
然后上线段树
可以维护一个原数组的差分数组,这个差分数组只有 \(1,0,-1\) 三种取值,然后我们对查分数组的前缀和维护一颗线段树,因为我们单点修改时,相当于对差分数组做出最多一项修改,在前缀和上体现就是将 \([i,n]\) 的所有值整体平移,那么我们就需要一个支持区间修改,查询最小值所在位置的线段树,查询位置也很简单,你只需要在树节点里记录一下 \(pos\) 信息
然后你做修改的时候(比如把差分数组 \(dx_i\) 变成 \(dx_i'\)),直接对 \([i,n]\) 增加一个 \(dx_i'-dx_i\) 就行了,非常好写
然后需要注意的就是,如果遇到两个都是最小值,那么应该取下标靠后的那个
#include<bits/stdc++.h>
using namespace std;
int n,q;
inline int ton(char c){
if(c=='R') return 0;
if(c=='P') return 2;
return 1;
}
inline char toc(int i){
if(i==0) return 'R';
if(i==2) return 'P';
return 'S';
}
inline bool win2(int x,int y){
if(x==0 and y==1) return true;
if(x==1 and y==2) return true;
if(x==2 and y==0) return true;
return false;
}
inline int win(int s,int x){
return win2(s,x);
// -1 front win
// if(s==x) return 0;
// if(s==1 and x==2) return 1;
// if(s==1 and x==3) return -1;
// if(s==2 and x==3) return 1;
// if(s==2 and x==1) return -1;
// if(s==3 and x==1) return 1;
// if(s==3 and x==2) return -1;
}
const int inf=0x3f3f3f3f;
int sum[200001];
namespace stree{
struct tree{
int l,r;
int val,pos;
int lazy;
}t[800001];
#define tol (id*2)
#define tor (id*2+1)
#define mid(l,r) mid=((l)+(r))/2
void pushup(int id){
if(t[tol].val<=t[tor].val){
t[id].val=t[tol].val;
t[id].pos=t[tol].pos;
}
else{
t[id].val=t[tor].val;
t[id].pos=t[tor].pos;
}
}
void build(int id,int l,int r){
t[id].l=l;t[id].r=r;
if(l==r){
t[id].val=sum[l];
t[id].pos=l;
return;
}
int mid(l,r);
build(tol,l,mid);
build(tor,mid+1,r);
pushup(id);
}
void pushdown(int id){
if(t[id].lazy){
t[tol].val+=t[id].lazy;
t[tol].lazy+=t[id].lazy;
t[tor].val+=t[id].lazy;
t[tor].lazy+=t[id].lazy;
t[id].lazy=0;
}
}
void change(int id,int l,int r,int val){
// cout<<"change "<<id<<" "<<l<<" "<<r<<" "<<val<<endl;
if(l<=t[id].l and t[id].r<=r){
t[id].val+=val;
t[id].lazy+=val;
return;
}
pushdown(id);
int mid(t[id].l,t[id].r);
if(mid>=l) change(tol,l,r,val);
if(mid<r) change(tor,l,r,val);
pushup(id);
}
tree ask(int id,int l,int r){
if(l<=t[id].l and t[id].r<=r) return t[id];
pushdown(id);
int mid(t[id].l,t[id].r);
tree res={0,0,inf,0,0};
if(mid>=l) res=ask(tol,l,r);
if(mid<r){
if(res.val>=inf) res=ask(tor,l,r);
else{
tree res2=ask(tor,l,r);
if(res2.val<res.val) res=res2;
}
}
return res;
}
}
int a[200001];
stack<int>st;
int solve(){
// for(int i=1;i<=n;++i){
// cout<<a[i]<<" ";
// }
// cout<<endl;
while(!st.empty()) st.pop();
int lastans=0;
for(int i=1;i<=n;++i){
while(!st.empty() and win2(st.top(),a[i])==false) st.pop();
st.push(a[i]);
if(st.size()==1) lastans=st.top();
}
return lastans;
}
int d[200001];
int main(){
freopen("a.in","r",stdin);
freopen("a.out","w",stdout);
scanf("%d %d",&n,&q);getchar();
for(int i=1;i<=n;++i){
a[i]=ton(getchar());
}
d[1]=1;
for(int i=2;i<=n;++i){
if(win(a[i-1],a[i])) d[i]=1;
else if(win(a[i],a[i-1])) d[i]=-1;
else d[i]=0;
}
for(int i=1;i<=n;++i){
// cout<<d[i]<<" ";
sum[i]=sum[i-1]+d[i];
}
// cout<<endl;
stree::build(1,1,n);
while(q--){
int op;scanf("%d",&op);
int k,l,r;char x;
if(op==1){
scanf("%d %c",&k,&x);
a[k]=ton(x);
int bef,aft,now=ton(x);
if(k!=1){
bef=d[k];aft=0;
if(win(a[k-1],now)) aft=1;
if(win(now,a[k-1])) aft=-1;
d[k]=aft;
stree::change(1,k,n,aft-bef);
// cout<<aft<<" "<<bef<<" 1"<<aft-bef<<endl;
}
if(k!=n){
bef=d[k+1];aft=0;
if(win(now,a[k+1])) aft=1;
if(win(a[k+1],now)) aft=-1;
d[k+1]=aft;
stree::change(1,k+1,n,aft-bef);
// cout<<aft<<" "<<bef<<" 2"<<aft-bef<<endl;
}
a[k]=now;
}
else{
scanf("%d %d",&l,&r);
printf("%c\n",toc(a[stree::ask(1,l,r).pos]));
}
}
}