【考試總結】2022-05-19
調兵遣將
先二分 \(+ \rm ST\) 表求 \(\rm GCD\) 得到每個點為左/右端點的區間 \(\rm GCD\) 不同的 \(\Theta(n\log n)\) 個區間
列舉可能的 \(\rm GCD\) 並求出來 \(f_i\) 表示前 \(i\) 個元素最後一個選的軍團位置 \(\le i\) 的方案數,\(g_i\) 表示倒序處理中第一個軍團起始點 \(\ge i\) 的方案數
那麼不經過某個點 \(i\) 的方案數就是 \(f_{i-1}\times g_{i+1}\) ,乍一看仍然需要 \(\Theta(n^2\log n)\) 的複雜度
但是不難發現 \(f_i,g_i\)
不離散化也過了
Code Display
const int N=50010; int w[N],n; map<int,vector<tuple<int,int,int> > > dir,inv; int pre[N],c[N],suf[N],spre[N],ssuf[N]; int ans[N]; int bz[17][N],lg[N]; inline int get_gcd(int l,int r){ int t=lg[r-l+1]; return __gcd(bz[t][l],bz[t][r-(1<<t)+1]); } signed main(){ n=read(); rep(i,1,n) w[i]=read(),bz[0][i]=w[i]; for(int j=1;j<=16;++j){ for(int i=1;i+(1<<j)-1<=n;++i){ bz[j][i]=__gcd(bz[j-1][i],bz[j-1][i+(1<<(j-1))]); } } rep(i,2,n) lg[i]=lg[i>>1]+1; for(int i=1;i<=n;++i){ int pos=i; while(pos<=n){ int val=get_gcd(i,pos),ans=pos,l=pos+1,r=n; while(l<=r){ int mid=(l+r)>>1; if(get_gcd(i,mid)==val) ans=mid,l=mid+1; else r=mid-1; } dir[val].emplace_back(i,pos,ans); pos=ans+1; } } for(int i=n;i>=1;--i){ int pos=i; while(pos>=1){ int val=get_gcd(pos,i),ans=pos,l=1,r=pos-1; while(l<=r){ int mid=(l+r)>>1; if(get_gcd(mid,i)==val) ans=mid,r=mid-1; else l=mid+1; } inv[val].emplace_back(i,ans,pos); pos=ans-1; } } int all=0; for(auto eee:dir){ int gcd=eee.fir; vector<tuple<int,int,int> > Z=eee.sec,F=inv[gcd]; pre[0]=suf[n+1]=spre[0]=ssuf[n+1]=1; rep(i,1,n) pre[i]=suf[i]=spre[i]=ssuf[i]=0; pre[1]=mod-1; suf[n]=mod-1; int indic=0; int a,b,c; tie(a,b,c)=Z[indic]; for(int i=1;i<=n;++i){ if(a==i){ ckadd(pre[b],spre[i-1]); ckdel(pre[c+1],spre[i-1]); ++indic; if(indic<Z.size()) tie(a,b,c)=Z[indic]; } ckadd(pre[i],pre[i-1]); spre[i]=add(spre[i-1],pre[i]); } indic=0; tie(a,b,c)=F[indic]; for(int i=n;i>=1;--i){ if(a==i){ ckadd(suf[c],ssuf[i+1]); ckdel(suf[b-1],ssuf[i+1]); ++indic; if(indic<F.size()) tie(a,b,c)=F[indic]; } ckadd(suf[i],suf[i+1]); ssuf[i]=add(ssuf[i+1],suf[i]); } ckadd(all,spre[n]); for(int i=1;i<=n;++i) ckadd(ans[i],mul(spre[i-1],ssuf[i+1])); } rep(i,1,n) print(del(all,ans[i])); putchar('\n'); return 0; }
一擲千金
注意到原題中給出的讓黑點變成白點和讓一個白點變成黑點在異或意義下效果是一樣的,所以可以將每個每個唯一白點的局面的 \(\rm SG\) 函式值求出再異或得到全域性 \(\rm SG\) 值
發現單個白點構成局面的情況的 \(\rm SG\) 值只和 \((i,j)\to (1,1)\) 之間的切比雪夫距離有關,也就是說考慮最外側這一條鏈上的值即可
先丟擲結論 \(\rm SG((i,j))=lowbit(\max(i,j))\),證明分成 \(\rm [0,lowbit(\max(i,j)))\) 都存在於可達集合中以及 \(\rm lowbit(\max(i,j))\) 不在可達集合裡面,可達集合本質上是 \(\{x|\exists d\in[1,\rm i),x\oplus_{j=1}^{\rm d}\rm lowbit(i-j)\}\)
考察 \(d\in[1,\rm lowbit(i)),\oplus_{j=1}^{\rm d}\rm lowbit(i-j)\) 的結果發現其就是 \(\log_2\rm lowbit(i)\) 位格雷碼反射至十進位制數得到的結果,對此證明不展開,簡單考察進位變化即可
對於 \(\rm lowbit(i)\) 不存在於可達集合的證明,發現根據格雷碼的構造會在 \(\rm j=lowbit(i)\sim j=2lowbit(i)-1\) 反向走一遍 \(j=1\sim j=\rm lowbit(i)-1\) 的序列,這是 \(\rm lowbit(i)\) 異或一個非零的 \(k\) ,在 \(j=2\rm lowbit(i)\) 處字首異或和是 \(0\) 從 \(j=2\rm lowbit(i)+1\) 到 \(j=i-1\) 的討論不再贅述
要求若干矩形並的白點 \(SG\) 值的 \(\oplus\) 之和可以對 \([0,m]\) 這維開線段樹維護掃描線
對於一個被矩形覆蓋的區間那麼 \(\rm lowbit\) 的異或和是 \(\rm lowbit(l)\oplus lowbit(mid)\),也就是說更小的位兩兩抵消掉了;而沒有被覆蓋的區間的 \(\oplus\) 和可以使用子區間得到
那麼線上段樹上額外維護出來每個區間被覆蓋點的個數應對兩維取 \(\max\) 操作即可
Code Display
const int N=1e5+10,U=(1ll<<30)-1;
int rt,tot,sum[N<<6],cnt[N<<6],cov[N<<6];
int K,n,m;
int ls[N<<6],rs[N<<6];
vector<tuple<int,int,int> >squ[N];
inline void push_up(int p){
sum[p]=sum[ls[p]]^sum[rs[p]];
cnt[p]=cnt[ls[p]]^cnt[rs[p]];
}
inline int calc(int L,int R){
return (L&(-L))^((R-L+1)>>1);
}
inline void upd(int &p,int l,int r,int st,int ed,int d){
if(!p) p=++tot;
if(st<=l&&r<=ed){
cov[p]+=d;
if(cov[p]) sum[p]=calc(l,r),cnt[p]=(r-l+1)&1;
else push_up(p);
return ;
}
int mid=(l+r)>>1;
if(st<=mid) upd(ls[p],l,mid,st,ed,d);
if(ed>mid) upd(rs[p],mid+1,r,st,ed,d);
if(!cov[p]) push_up(p);
}
inline int query_sum(int p,int l,int r,int st,int ed,int tag){
tag|=cov[p];
if(st<=l&&r<=ed){
if(tag) return calc(l,r);
else return sum[p];
}
int mid=(l+r)>>1,res=0;
if(st<=mid) res^=query_sum(ls[p],l,mid,st,ed,tag);
if(ed>mid) res^=query_sum(rs[p],mid+1,r,st,ed,tag);
return res;
}
inline int query_cnt(int p,int l,int r,int st,int ed){
if(!p) return 0;
if(cov[p]) return (min(ed,r)-max(st,l)+1)&1;
if(st<=l&&r<=ed) return cnt[p];
int mid=(l+r)>>1,par=0;
if(st<=mid) par^=query_cnt(ls[p],l,mid,st,ed);
if(ed>mid) par^=query_cnt(rs[p],mid+1,r,st,ed);
return par;
}
signed main(){
K=read(); n=read(); m=read();
for(int i=1;i<=K;++i){
int sx=read(),sy=read(),ex=read(),ey=read();
squ[sx].emplace_back(sy,ey,1);
squ[ex+1].emplace_back(sy,ey,-1);
}
int ans=0;
for(int i=1;i<=n;++i){
for(auto s:squ[i]){
int l,r,fl;
tie(l,r,fl)=s;
upd(rt,0,U,l,r,fl);
}
ans^=query_sum(rt,0,U,i+1,U,0);
if(query_cnt(rt,0,U,0,i)&1) ans^=i&(-i);
}
print(ans);
return 0;
}
樹拓撲序
計算 \(f_{u,v}\) 表示 \((u,v)\) 兩個點的子樹合併後逆序對的平均數,列舉整個序列是 \(u\) 在末位還是 \(v\) 在末位
直接的貢獻是 \(geq_{u,v}/leq_{v,u}\) 表示在 \(u/v\) 的子樹中大於等於 \(v/u\) 的數的數量,剩下的問題是原問題的子問題,遞迴求解即可
和 2022-01-19 心理陰影一題是重題
Code Display
const int N=5010;
int ans,n,fa[N],siz[N],inv[N];
vector<int> g[N],sub[N];
int geq[N][N],C[N][N],all=1;
inline void dfs(int x,int fat){
fa[x]=fat; sub[x].pb(x);
rep(i,1,x) geq[x][i]++;
for(int i:g[x]) if(i!=fat){
dfs(i,x);
siz[x]+=siz[i];
rep(j,1,n) geq[x][j]+=geq[i][j];
for(auto t:sub[i]) sub[x].pb(t),ans+=x<t;
ckmul(all,C[siz[x]][siz[i]]);
}
siz[x]++;
return ;
}
int f[N][N];
inline int calc_f(int u,int v){
if(~f[v][u]) return f[v][u];
if(~f[u][v]) return f[u][v];
f[u][v]=add(mul(siz[u],geq[v][u]),mul(siz[v],geq[u][v]));
for(auto t:g[u]) if(t^fa[u]){
ckadd(f[u][v],mul(siz[u],calc_f(t,v)));
}
for(auto t:g[v]) if(t^fa[v]){
ckadd(f[u][v],mul(siz[v],calc_f(t,u)));
}
return f[u][v]=mul(f[u][v],inv[siz[u]+siz[v]]);
}
inline void get_ans(int x){
for(auto t:g[x]) if(t!=fa[x]) get_ans(t);
int siz=g[x].size();
rep(i,0,siz-1) if(g[x][i]^fa[x]) rep(j,i+1,siz-1) if(g[x][j]^fa[x]) ckadd(ans,calc_f(g[x][i],g[x][j]));
return ;
}
signed main(){
n=read();
inv[0]=inv[1]=1;
for(int i=2;i<=n;++i) inv[i]=mod-mul(mod/i,inv[mod%i])%mod;
C[0][0]=1;
rep(i,1,n){
C[i][0]=1;
for(int j=1;j<=i;++j) C[i][j]=add(C[i-1][j],C[i-1][j-1]);
}
rep(i,1,n-1){
int u=read(),v=read();
g[u].pb(v); g[v].pb(u);
}
dfs(1,0);
memset(f,-1,sizeof(f));
get_ans(1);
print(mul(ans,all));
return 0;
}