NOI2021山東省隊二輪集訓
模擬賽
Day 1
第一天,狀態奇差,T1算錯複雜度,\(O(n\log n)\) 被卡到 \(50pts\),本來可以胡亂寫一個 \(O(n\log \log n)\) 的;T2和T3暴力都不會打,T2暴力調了很久,雖然過了樣例但是WA掉了;T3衝了一發倍增套線段樹合併,複雜度不知道,空間似乎也炸,也沒調出來。直接墊底了……
T1
答案 \(\leq \log n+1\),因為總共只有 \(O(n)\) 個子串。二分答案可以做到 \(O(n\log \log n)\)。考慮先把 \(\log n+1\) 層的算出來,相當於是去求一個 \(mex\);然後把所有數除以二再求 \(mex\),……。每一次去重之後複雜度就是 \(O(n)\)
點選檢視程式碼
#include<iostream> #include<cstdio> #define re register #define rint re int using namespace std; const int N=16777216+13; int a[N],n,ans[N]; bool b[N<<1]; inline void rd(){ re char c=getchar(); while(c!='0'&&c!='1') c=getchar(); while(c=='0'||c=='1') a[++n]=c-'0',c=getchar(); } int main(){ rd(); rint lim=0,limit=1; while(limit<n) limit<<=1,lim++; rint tmp=0; for(rint i=1;i<=lim;++i) tmp=tmp<<1|a[i]; b[tmp]=1; for(rint i=lim+1;i<=n;++i){ if(tmp&(1<<(lim-1))) tmp^=(1<<(lim-1)); tmp=tmp<<1|a[i]; b[tmp]=1; } rint las=0; while(lim){ rint res=-1; for(rint i=0;i<(1<<lim);++i) if(!b[i]){res=i;break;} if(res==-1) break;las=res; for(rint i=0;i<(1<<lim);++i){ b[i>>1]|=b[i]; if(i) b[i]=0; } rint tmp=0; --lim; for(rint i=n-lim+1;i<=n;++i) tmp=(tmp<<1|a[i]); b[tmp]|=1; } rint j=0; while(las) ans[++j]=(las&1),las>>=1; for(rint i=lim+1;i>=1;--i) putchar(ans[i]?'1':'0'); return 0; }
T2 P6106
答案滿足可減,首先把一個 4-side 矩形化成四個 2-side 矩形。先考慮 \(k>0\) 的線段,可以分成四類:在矩形內、與上邊界有交、與右邊界有交、完全在外面。通過維護左右端點可以快速分類。對於與邊界有交的兩類,由於線段不交,所以可以掃描線,維護一個類似於導數的東西,極小增量的時候每個線段對答案的貢獻和。斜率為負的直線就反過來跑一遍,複雜度是常數比較大的 \(O(n\log n)\)。
T3
建完虛樹之後,可以把答案拆成 \(O(k)\) 個段的總貢獻。可以去列舉兩個段的貢獻,是 \(O(k^2)\) 的。然後可以把這個東西搞一個樹上差分,就可以四維莫隊變二維莫隊了。還有一個做法是兩次根號分治?
Day 2
自閉了,T1被卡常,T2最後一個小時寫了300行線段樹,維護了二十多個資訊,沒調出來,T3沒怎麼看……直接墊底。
T1
環套樹森林,考慮二分答案。把所有點權都減一個 \(ans\),環縮成一個點一樣處理,拓撲排序時跑dp就可以了。注意可以先把拓撲序求出來,然後直接在上面dp。被卡常了。
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<stack>
#include<queue>
#define re register
#define rint re int
#define rll re ll
using namespace std;
inline int rd(){
rint res=0,flag=1;re char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')flag=-1;
for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
return res*flag;
}
inline int min(const int &a,const int &b){return a<b?a:b;}
typedef long long ll;
const int N=1e6+13;
struct Edge{int v,nxt;}e[N];
int n,m,tot,h[N],fa[N],a[N],c[N],siz[N],ind[N],tmpind[N],cnt_bcc;
ll val[N],b[N];
inline void add(rint u,rint v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
namespace Tarjan{
struct Edge{int v,nxt;}e[N];
int tot,h[N],dfn[N],low[N],dfs_clock;
inline void add(const int &u,const int &v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
bool vis[N];
int st[N],top;
inline void tarjan(rint u){
dfn[u]=low[u]=++dfs_clock;
st[++top]=u;vis[u]=1;
for(rint i=h[u];i;i=e[i].nxt){
rint v=e[i].v;
if(!dfn[v]){
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(vis[v]) low[u]=min(low[u],dfn[v]);
}
if(dfn[u]==low[u]){
++cnt_bcc;
rint x=st[top];--top;
c[x]=cnt_bcc,siz[cnt_bcc]=1,val[cnt_bcc]+=a[x],vis[x]=0;
while(u!=x){
x=st[top];--top;
c[x]=cnt_bcc,siz[cnt_bcc]++,val[cnt_bcc]+=a[x],vis[x]=0;
}
}
}
}using Tarjan::tarjan;
int q[N];
inline bool check(const ll &k){
for(rint i=1;i<=cnt_bcc;++i) b[i]=val[i],ind[i]=tmpind[i];
int head=1,tail=0;q[1]=0;
for(rint i=1;i<=cnt_bcc;++i)
if(!ind[i]) q[++tail]=i;
rll sum=0;
while(head<=tail){
rint u=q[head];++head;
for(rint i=h[u];i;i=e[i].nxt){
rint v=e[i].v;
if(b[u]<k*siz[u]) b[v]-=k*siz[u]-b[u],b[u]=k*siz[u];
ind[v]--;
if(!ind[v]) q[++tail]=v;
}
if(b[u]<k*siz[u]) sum+=k*siz[u]-b[u];
if(sum>m) return 0;
}
return sum<=m;
}
inline void solve(){
rll l=1e8,r=0;
for(rint i=1;i<=n;++i) l=min(l,a[i]),r+=a[i];
r=(r+m)/n;
while(l<r){
rll mid=(l+r+1)>>1;
if(check(mid)) l=mid;
else r=mid-1;
}
printf("%lld\n",l);
}
int main(){
n=rd(),m=rd();
for(rint i=1,x;i<=n;++i){
fa[i]=rd();
if(fa[i]!=-1&&fa[i]!=i) Tarjan::add(i,fa[i]);
}
for(rint i=1;i<=n;++i) a[i]=rd();
for(rint i=1;i<=n;++i)
if(!Tarjan::dfn[i]) tarjan(i);
for(rint i=1;i<=n;++i)
if(fa[i]!=-1&&fa[i]!=i) add(c[i],c[fa[i]]),tmpind[c[fa[i]]]++;
solve();
return 0;
}
T2
列舉右端點,線段樹維護左端點的 \(max-min\),加上中間變數需要維護 \(7\) 個值。每個點的區間相當於是一個矩形,然後從左到右掃描線做這個東西?單調棧可以維護一下 \(max\) 和 \(min\),複雜度是大常數的 \(O(n\log n)\)。沒調出來!!
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<stack>
using namespace std;
inline int max(const int &a,const int &b){return a>b?a:b;}
inline int min(const int &a,const int &b){return a<b?a:b;}
typedef unsigned int uint;
inline int rd(){
int res=0,flag=1;char c=getchar();
for(;!isdigit(c);c=getchar())if(c=='-')flag=-1;
for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
return res*flag;
}
const int N=1e5+13;
int n,a[N],b[N],c[N];
namespace Sub1{
void main(){
uint sum=0;
for(int l=1;l<=n;++l){
int maxa=a[l],maxb=b[l],maxc=c[l],mina=a[l],minb=b[l],minc=c[l];
for(int r=l+1;r<=n;++r){
maxa=max(maxa,a[r]),maxb=max(maxb,b[r]),maxc=max(maxc,c[r]);
mina=min(mina,a[r]),minb=min(minb,b[r]),minc=min(minc,c[r]);
sum+=(uint)(maxa-mina)*(uint)(maxb-minb)*(uint)(maxc-minc);
}
}
cout<<sum<<endl;
}
}
namespace Sub2{
struct data{
uint sum_a,sum_b,sum_c,sum_ab,sum_ac,sum_bc,sum_abc;
data operator +(const data &x)const{
data ans;
ans.sum_a=sum_a+x.sum_a;
ans.sum_b=sum_b+x.sum_b;
ans.sum_c=sum_c+x.sum_c;
ans.sum_ab=sum_ab+x.sum_ab;
ans.sum_ac=sum_ac+x.sum_ac;
ans.sum_bc=sum_bc+x.sum_bc;
ans.sum_abc=sum_abc+x.sum_abc;
return ans;
}
};
struct SegTree{int l,r;data x;}t[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
inline void refresh(int p){t[p].x=t[ls].x+t[rs].x;}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return t[p].x=(data){(uint)a[l],(uint)b[l],(uint)c[l],(uint)a[l]*b[l],(uint)a[l]*c[l],(uint)b[l]*c[l],(uint)a[l]*b[l]*c[l]},void();
build(ls,l,mid),build(rs,mid+1,r);
refresh(p);
}
data query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].x;
if(r<=mid) return query(ls,l,r);
if(l>mid) return query(rs,l,r);
return query(ls,l,r)+query(rs,l,r);
}
#undef mid
void main(){
build(1,1,n);
uint sum=0;
for(int i=2;i<=n;++i){
data tmp=query(1,1,i-1);
sum+=(uint)a[i]*b[i]*c[i]*(i-1)-(uint)a[i]*b[i]*tmp.sum_c-(uint)a[i]*c[i]*tmp.sum_b-(uint)b[i]*c[i]*tmp.sum_a+(uint)a[i]*tmp.sum_bc+(uint)b[i]*tmp.sum_ac+(uint)c[i]*tmp.sum_ab-tmp.sum_abc;
}
cout<<sum<<endl;
}
}
namespace Sub4{
struct data{
uint a1_b1_c1,a1_b1_c2,a1_b2_c1,a1_b2_c2,a2_b1_c1,a2_b1_c2,a2_b2_c1,a2_b2_c2;
data operator +(const data &x){
data ans;
ans.a1_b1_c1=a1_b1_c1+x.a1_b1_c1;
ans.a1_b1_c2=a1_b1_c2+x.a1_b1_c2;
ans.a1_b2_c1=a1_b2_c1+x.a1_b2_c1;
ans.a1_b2_c2=a1_b2_c2+x.a1_b2_c2;
ans.a2_b1_c1=a2_b1_c1+x.a2_b1_c1;
ans.a2_b1_c2=a2_b1_c2+x.a2_b1_c2;
ans.a2_b2_c1=a2_b2_c1+x.a2_b2_c1;
ans.a2_b2_c2=a2_b2_c2+x.a2_b2_c2;
return ans;
}
};
struct SegTree{int l,r;uint a1,a2,b1,b2,c1,c2,a1b1,a1b2,a1c1,a1c2,a2b1,a2b2,a2c1,a2c2,b1c1,b1c2,b2c1,b2c2;int seta1,seta2,setb1,setb2,setc1,setc2;data x;}t[N<<2];//1-max,2-min
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
inline void refresh(int p){
t[p].x=t[ls].x+t[rs].x;
t[p].a1=t[ls].a1+t[rs].a1;
t[p].a2=t[ls].a2+t[rs].a2;
t[p].b1=t[ls].b1+t[rs].b1;
t[p].b2=t[ls].b2+t[rs].b2;
t[p].c1=t[ls].c1+t[rs].c1;
t[p].c2=t[ls].c2+t[rs].c2;
t[p].a1b1=t[ls].a1b1+t[rs].a1b1;
t[p].a1b2=t[ls].a1b2+t[rs].a1b2;
t[p].a1c1=t[ls].a1c1+t[rs].a1c1;
t[p].a1c2=t[ls].a1c2+t[rs].a1c2;
t[p].a2b1=t[ls].a2b1+t[rs].a2b1;
t[p].a2b2=t[ls].a2b2+t[rs].a2b2;
t[p].a2c1=t[ls].a2c1+t[rs].a2c1;
t[p].a2c2=t[ls].a2c2+t[rs].a2c2;
t[p].b1c1=t[ls].b1c1+t[rs].b1c1;
t[p].b1c2=t[ls].b1c2+t[rs].b1c2;
t[p].b2c1=t[ls].b2c1+t[rs].b2c1;
t[p].b2c2=t[ls].b2c2+t[rs].b2c2;
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return;
build(ls,l,mid),build(rs,mid+1,r);
}
inline void pushup_maxa(int p,int x){
t[p].seta1=x;t[p].a1=(uint)x*(t[p].r-t[p].l+1);
t[p].a1b1=(uint)x*t[p].b1;
t[p].a1b2=(uint)x*t[p].b2;
t[p].a1c1=(uint)x*t[p].c1;
t[p].a1c2=(uint)x*t[p].c2;
t[p].x.a1_b1_c1=(uint)x*t[p].b1c1;
t[p].x.a1_b1_c2=(uint)x*t[p].b1c2;
t[p].x.a1_b2_c1=(uint)x*t[p].b2c1;
t[p].x.a1_b2_c2=(uint)x*t[p].b2c2;
}
inline void pushup_mina(int p,int x){
t[p].seta2=x;t[p].a2=(uint)x*(t[p].r-t[p].l+1);
t[p].a2b1=(uint)x*t[p].b1;
t[p].a2b2=(uint)x*t[p].b2;
t[p].a2c1=(uint)x*t[p].c1;
t[p].a2c2=(uint)x*t[p].c2;
t[p].x.a2_b1_c1=(uint)x*t[p].b1c1;
t[p].x.a2_b1_c2=(uint)x*t[p].b1c2;
t[p].x.a2_b2_c1=(uint)x*t[p].b2c1;
t[p].x.a2_b2_c2=(uint)x*t[p].b2c2;
}
inline void pushup_maxb(int p,int x){
t[p].setb1=x;t[p].b1=(uint)x*(t[p].r-t[p].l+1);
t[p].a1b1=(uint)x*t[p].a1;
t[p].a2b1=(uint)x*t[p].a2;
t[p].b1c1=(uint)x*t[p].c1;
t[p].b1c2=(uint)x*t[p].c2;
t[p].x.a1_b1_c1=(uint)x*t[p].a1c1;
t[p].x.a1_b1_c2=(uint)x*t[p].a1c2;
t[p].x.a2_b1_c1=(uint)x*t[p].a2c1;
t[p].x.a2_b1_c2=(uint)x*t[p].a2c2;
}
inline void pushup_minb(int p,int x){
t[p].setb2=x;t[p].b2=(uint)x*(t[p].r-t[p].l+1);
t[p].a1b2=(uint)x*t[p].a1;
t[p].a2b2=(uint)x*t[p].a2;
t[p].b2c1=(uint)x*t[p].c1;
t[p].b2c2=(uint)x*t[p].c2;
t[p].x.a1_b2_c1=(uint)x*t[p].a1c1;
t[p].x.a1_b2_c2=(uint)x*t[p].a1c2;
t[p].x.a2_b2_c1=(uint)x*t[p].a2c1;
t[p].x.a2_b2_c2=(uint)x*t[p].a2c2;
}
inline void pushup_maxc(int p,int x){
t[p].setc1=x;t[p].c1=(uint)x*(t[p].r-t[p].l+1);
t[p].a1c1=(uint)x*t[p].a1;
t[p].a2c1=(uint)x*t[p].a2;
t[p].b1c1=(uint)x*t[p].b1;
t[p].b2c1=(uint)x*t[p].b2;
t[p].x.a1_b1_c1=(uint)x*t[p].a1b1;
t[p].x.a1_b2_c1=(uint)x*t[p].a1b2;
t[p].x.a2_b1_c1=(uint)x*t[p].a2b1;
t[p].x.a2_b2_c1=(uint)x*t[p].a2b2;
}
inline void pushup_minc(int p,int x){
t[p].setc2=x;t[p].c2=(uint)x*(t[p].r-t[p].l+1);
t[p].a1c2=(uint)x*t[p].a1;
t[p].a2c2=(uint)x*t[p].a2;
t[p].b1c2=(uint)x*t[p].b1;
t[p].b2c2=(uint)x*t[p].b2;
t[p].x.a1_b1_c2=(uint)x*t[p].a1b1;
t[p].x.a1_b2_c2=(uint)x*t[p].a1b2;
t[p].x.a2_b1_c2=(uint)x*t[p].a2b1;
t[p].x.a2_b2_c2=(uint)x*t[p].a2b2;
}
inline void pushdown(int p){
if(t[p].seta1) pushup_maxa(ls,t[p].seta1),pushup_maxa(rs,t[p].seta1),t[p].seta1=0;
if(t[p].seta2) pushup_mina(ls,t[p].seta2),pushup_mina(rs,t[p].seta2),t[p].seta2=0;
if(t[p].setb1) pushup_maxb(ls,t[p].setb1),pushup_maxb(rs,t[p].setb1),t[p].setb1=0;
if(t[p].setb2) pushup_minb(ls,t[p].setb2),pushup_minb(rs,t[p].setb2),t[p].setb2=0;
if(t[p].setc1) pushup_maxc(ls,t[p].setc1),pushup_maxc(rs,t[p].setc1),t[p].setc1=0;
if(t[p].setc2) pushup_minc(ls,t[p].setc2),pushup_minc(rs,t[p].setc2),t[p].setc2=0;
}
void setmaxa(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_maxa(p,x);
pushdown(p);
if(l<=mid) setmaxa(ls,l,r,x);
if(r>mid) setmaxa(rs,l,r,x);
refresh(p);
}
void setmina(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_mina(p,x);
pushdown(p);
if(l<=mid) setmina(ls,l,r,x);
if(r>mid) setmina(rs,l,r,x);
refresh(p);
}
void setmaxb(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_maxb(p,x);
pushdown(p);
if(l<=mid) setmaxb(ls,l,r,x);
if(r>mid) setmaxb(rs,l,r,x);
refresh(p);
}
void setminb(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_minb(p,x);
pushdown(p);
if(l<=mid) setminb(ls,l,r,x);
if(r>mid) setminb(rs,l,r,x);
refresh(p);
}
void setmaxc(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_maxc(p,x);
pushdown(p);
if(l<=mid) setmaxc(ls,l,r,x);
if(r>mid) setmaxc(rs,l,r,x);
refresh(p);
}
void setminc(int p,int l,int r,int x){
if(l<=t[p].l&&t[p].r<=r) return pushup_minc(p,x);
pushdown(p);
if(l<=mid) setminc(ls,l,r,x);
if(r>mid) setminc(rs,l,r,x);
refresh(p);
}
data query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].x;
pushdown(p);
if(r<=mid) return query(ls,l,r);
if(l>mid) return query(rs,l,r);
return query(ls,l,r)+query(rs,l,r);
}
#undef mid
void main(){
build(1,1,n);
stack<int> upa,dna,upb,dnb,upc,dnc;
uint sum=0;
for(int i=1,tmp;i<=n;++i){
while(!upa.empty()&&a[upa.top()]<a[i]) upa.pop();
tmp=1;if(!upa.empty()) tmp=upa.top()+1;
setmaxa(1,tmp,i,a[i]);upa.push(i);
while(!dna.empty()&&a[dna.top()]>a[i]) dna.pop();
tmp=1;if(!dna.empty()) tmp=dna.top()+1;
setmina(1,tmp,i,a[i]);dna.push(i);
while(!upb.empty()&&b[upb.top()]<b[i]) upb.pop();
tmp=1;if(!upb.empty()) tmp=upb.top()+1;
setmaxb(1,tmp,i,b[i]);upb.push(i);
while(!dnb.empty()&&b[dnb.top()]>b[i]) dnb.pop();
tmp=1;if(!dnb.empty()) tmp=dnb.top()+1;
setminb(1,tmp,i,b[i]);dnb.push(i);
while(!upc.empty()&&c[upc.top()]<c[i]) upc.pop();
tmp=1;if(!upc.empty()) tmp=upc.top()+1;
setmaxc(1,tmp,i,c[i]);upc.push(i);
while(!dnc.empty()&&c[dnc.top()]>c[i]) dnc.pop();
tmp=1;if(!dnc.empty()) tmp=dnc.top()+1;
setminc(1,tmp,i,c[i]);dnc.push(i);
data res=query(1,1,i);
sum+=res.a1_b1_c1-res.a1_b1_c2-res.a1_b2_c1-res.a2_b1_c1+res.a1_b2_c2+res.a2_b1_c2+res.a2_b2_c1-res.a2_b2_c2;
}
cout<<sum<<endl;
}
}
int main(){
//freopen("math.in","r",stdin);
//freopen("math.out","w",stdout);
n=rd();bool r1=1,r2=1;
for(int i=1;i<=n;++i){
a[i]=rd();
if(a[i]<a[i-1]) r1=0;
if(a[i]!=1) r2=0;
}
for(int i=1;i<=n;++i){
b[i]=rd();
if(b[i]<b[i-1]) r1=0;
}
for(int i=1;i<=n;++i){
c[i]=rd();
if(c[i]<c[i-1]) r1=0;
}
if(n<=2000) Sub1::main();
else if(r1) Sub2::main();
else if(r2) puts("0");
else Sub4::main();
return 0;
}
T3
可以用 \(\log v\) 段的分段函式維護這個 \(O(v)\) 的東西。這是個指數級的東西,還是考慮使用可持久化平衡樹維護區間複製和區間翻轉,這樣就可以做 \(l=1,r=n\) 了。更普遍的情況,可以開一棵線段樹,預處理出每個節點從右往左合併的可持久化平衡樹,直接把 \(O(\log n)\) 個區間的可持久化平衡樹合併即可。
還有dsq的第二分塊做法?好像是用 \(O(v)\) 複雜度合併一些值域然後值域分塊。
Day 3
T1降智題寫錯,T2T3打暴力,再次墊底……
T1
sb題,降智題,nt題(指我自己)
\[\begin{aligned} &\oplus_{i=1}^n \oplus_{j=1}^i j[j|i]\\ =&\oplus_{j=1}^n j\oplus_{i=j}^n [j|i]\\ =&\oplus_{j=1}^n j(\lfloor\frac{n}{j}\rfloor \operatorname{and} 1) \end{aligned} \]直接整除分塊搞就行了。
一定要注意,因為 \(2k \operatorname{xor} 2k+1=1\),把左右的沒有配對的處理掉之後,還需要看這個 \(1\) 的個數!
點選檢視程式碼
#include<iostream>
#include<cstdio>
#define rll register ll
using namespace std;
typedef long long ll;
ll n,ans;
int main(){
scanf("%lld",&n);
for(rll l=1,r;l<=n;l=r+1){
r=n/(n/l);
if(!((n/l)&1)) continue;
int len=r-l+1;
if(l&1) ans^=l,--len;
if(!(r&1)) ans^=r,--len;
if((len>>1)&1) ans^=1;
}
printf("%lld\n",ans);
return 0;
}
T2
兩種做法,一種是平常的主席樹或樹套樹優化建圖跑tarjan,跑tarjan的時候就在主席樹或者樹套樹上模擬那個過程就可以了。還有一種做法是考慮掃描的過程中有一個什麼性質,然後可以直接做線段樹分治。複雜度 \(1\) 到 \(2\) 個 \(\log\)。
T3
把點上的資訊放到邊上,然後對邊維護連通塊?具體好像是要開平衡樹維護??
Day 4
毒瘤場,T1讀錯題(雖然讀對了也不會)所以爆零啦!
T1
\(O(n^3)\) 做法:把 \(a\) 和 \(b\) 都從小到大排序,考慮設 \(f_i\) 表示左邊有 \(i\) 條邊連上的方案數,\(g_i\) 表示右邊有 \(i\) 條邊連上的方案數,然後考慮列舉一個 \(i\) 作為中間點,左右兩邊各拿出 \(i\) 個點來匹配,貢獻為 \(f_{L-i}\times g_{R-i}\times i!\)。求 \(f_i\) 和 \(g_i\) 大概就是設個 \(dp_{i,j}\),由於限制越來越鬆很好轉移。
code:(\(O(n^3)\))
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;
const int N=300+13,mod=1e9+7;
int n,lena,lenb,a[N],b[N],A[N],B[N],L[N],R[N],f[N][N],mul[N];
inline void dpL(){
memset(f,0,sizeof f);
f[lena+1][0]=1;
for(int i=lena;i;--i){
int cnt=0;
for(int j=lenb;j;--j)
if(A[i]<B[j]) ++cnt;
else break;
for(int j=0;j<=lena;++j){
if(!f[i+1][j]) continue;
f[i][j]=(f[i][j]+f[i+1][j])%mod;
f[i][j+1]=(f[i][j+1]+1ll*f[i+1][j]*(cnt-j)%mod)%mod;
}
}
for(int i=0;i<=lena;++i) L[i]=f[1][i];
}
inline void dpR(){
memset(f,0,sizeof f);
f[0][0]=1;
for(int i=1;i<=lenb;++i){
int cnt=0;
for(int j=1;j<=lena;++j)
if(A[j]<B[i]) ++cnt;
else break;
for(int j=0;j<=lenb;++j){
if(!f[i-1][j]) continue;
f[i][j]=(f[i][j]+f[i-1][j])%mod;
f[i][j+1]=(f[i][j+1]+1ll*f[i-1][j]*(cnt-j)%mod)%mod;
}
}
for(int i=0;i<=lenb;++i) R[i]=f[lenb][i];
}
int main(){
scanf("%d",&n);
mul[0]=1;
for(int i=1;i<=n;++i) mul[i]=1ll*mul[i-1]*i%mod;
for(int i=1;i<=n;++i) scanf("%d%d",&a[i],&b[i]);
sort(a+1,a+n+1),sort(b+1,b+n+1);
int ans=0;
for(int i=1;i<=n;++i){
lena=0,lenb=0;
for(int j=1;j<i;++j) A[++lena]=a[j];
for(int j=1;j<=n;++j)
if(b[j]<=a[i]) B[++lenb]=b[j];
else break;
dpL();int l=lena;
lena=0,lenb=0;
for(int j=i+1;j<=n;++j) A[++lena]=a[j];
for(int j=1;j<=n;++j)
if(b[j]>a[i]) B[++lenb]=b[j];
dpR();int r=lenb;int lim=min(l,r);
for(int j=0;j<=lim;++j) ans=(ans+1ll*L[l-j]*R[r-j]%mod*mul[j]%mod)%mod;
}
printf("%d\n",ans);
return 0;
}
\(O(n^2)\) 做法(srf) 考慮把 \(b\) 看成白點,\(a\) 看成黑點,都放到數軸上,題目中的限制條件就是前面的黑點和後面的白點不能都沒匹配。設 \(dp_{i,j,0/1}\) 表示匹配到第 \(i\) 個點,後邊的白點是不是必須匹配的方案數,不能套娃實際上就是合併線段之後不能有一段完全在另一段的前面。這個用 \(0/1\) 這一維就可以維護了。
T2
題意可以轉化為找最多的斜率遞增的向量 \((x_i,y_i)\) 首尾相連,使得滿足 \(\sum x_i\leq n,\sum y_i\leq m\) 且 \(\frac{y_i}{x_i}\) 不同。
考慮直接跑一個暴力的揹包,因為有 \(O(n,m)\) 個向量,所以複雜度是 \(O((nm)^2)\) 的。考慮剪枝,先把無用的向量去掉,比如 \(x_i\) 和 \(y_i\) 不互質和 \((x_i,y_i)\) 左下沒有還沒使用的線段。求一下大概發現有用的向量有 \(O(n^{\frac{2}{3}})\),總複雜度 \(O(n^{\frac{11}{3}})\)。
然後繼續剪枝揹包的另外幾維,最後可以優化到 \(O(n^{\frac{7}{3}})\)。
T3:P6107
建笛卡爾樹之後考慮每一次操作相當於旋轉??六元環個數相當於是數四個三角形??
Day 5
T1寫了樹套樹套樹……爆零了
T1
維護每個位置前面顏色和它相同的最近的位置 \(pre\),修改操作只會修改 \(O(1)\) 個位置的 \(pre\),查詢操作直接線段樹二分找 \(k\) 個 \(pre>s\) 的位置,由於最多隻有 \(k\) 個顏色相同的,所以直接對於每個相同顏色找一下 \(\max\) 即可。
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
typedef long long ll;
inline ll max(const ll&a,const ll&b){return a>b?a:b;}
inline int rd(){
int res=0;char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
return res;
}
void wt(ll x){if(x>9)wt(x/10);putchar(x%10+'0');}
const int N=2e5+13,logN=20;
set<int> t[N];
set<int>::iterator it;
int n,m,c[N],v[N],a[20],ppre[N];
namespace tree1{
struct SegTree{int l,r,maxpre;ll sum;}t[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
inline void refresh(int p){
t[p].sum=t[ls].sum+t[rs].sum;
t[p].maxpre=max(t[ls].maxpre,t[rs].maxpre);
}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return t[p].sum=v[l],t[p].maxpre=ppre[l],void();
build(ls,l,mid),build(rs,mid+1,r);
refresh(p);
}
void update(int p,int x,int y,int z){
if(t[p].l==t[p].r) return t[p].sum=y,t[p].maxpre=z,void();
x<=mid?update(ls,x,y,z):update(rs,x,y,z);
refresh(p);
}
ll query_sum(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].sum;
ll res=0;
if(l<=mid) res+=query_sum(ls,l,r);
if(r>mid) res+=query_sum(rs,l,r);
return res;
}
int getans(int p,int lim){
if(t[p].l==t[p].r) return t[p].l;
return t[ls].maxpre>=lim?getans(ls,lim):getans(rs,lim);
}
int find_right(int p,int l,int lim){
if(l<=t[p].l) return t[p].maxpre>=lim?getans(p,lim):0;
int res=0;
if(l<=mid) res=find_right(ls,l,lim);
if(res) return res;
return find_right(rs,l,lim);
}
#undef ls
#undef rs
#undef mid
}
namespace tree2{
int rt[N],tot;
struct SegTree{int ls,rs,maxx;}t[(N<<1)*logN];
#define ls t[p].ls
#define rs t[p].rs
#define mid ((l+r)>>1)
inline void refresh(int p){t[p].maxx=max(t[ls].maxx,t[rs].maxx);}
void update(int &p,int l,int r,int x,int v){
if(!p) p=++tot;
if(l==r) return t[p].maxx=v,void();
x<=mid?update(ls,l,mid,x,v):update(rs,mid+1,r,x,v);
refresh(p);
}
int query(int p,int l,int r,int x,int y){
if(!p) return 0;
if(x<=l&&r<=y) return t[p].maxx;
if(y<=mid) return query(ls,l,mid,x,y);
if(x>mid) return query(rs,mid+1,r,x,y);
return max(query(ls,l,mid,x,y),query(rs,mid+1,r,x,y));
}
#undef ls
#undef rs
#undef mid
}using tree2::rt;
int main(){
//freopen("gp.in","r",stdin);
//freopen("gp.out","w",stdout);
n=rd(),m=rd();
for(int i=1;i<=n;++i){
c[i]=rd(),v[i]=rd();
it=t[c[i]].end();
if(it!=t[c[i]].begin()) ppre[i]=*(--it);
tree2::update(rt[c[i]],1,n,i,v[i]);
t[c[i]].insert(i);
}
tree1::build(1,1,n);
while(m--){
int op,x,y,z,k;
op=rd(),x=rd();
if(op==1){
y=rd(),z=rd();
it=t[c[x]].upper_bound(x);
if(it!=t[c[x]].end()){
int nxt=*it,pre=0;
it=t[c[x]].lower_bound(x);
if(it!=t[c[x]].begin()) pre=*(--it);
tree1::update(1,nxt,v[nxt],ppre[nxt]=pre);
}
t[c[x]].erase(x);
tree2::update(rt[c[x]],1,n,x,0);
c[x]=y;
t[c[x]].insert(x);
it=t[c[x]].upper_bound(x);
if(it!=t[c[x]].end()){
int nxt=*it;
tree1::update(1,nxt,v[nxt],ppre[nxt]=x);
}
it=t[c[x]].lower_bound(x);int pre=0;
if(it!=t[c[x]].begin()) pre=*(--it);
tree1::update(1,x,v[x]=z,ppre[x]=pre);
tree2::update(rt[c[x]],1,n,x,v[x]);
}
else{
k=rd(),++k;int L=x,R=0;ll ans=0;
for(int i=1;i<=k;++i){
if(x<=n) a[i]=tree1::find_right(1,x,L);
if(!a[i]) a[i]=n+1;
if(x<a[i]) ans+=tree1::query_sum(1,x,a[i]-1);
x=a[i]+1;
if(a[i]==n+1){k=i,R=n;break;}
}
if(!R) R=a[k]-1;--k;
for(int i=1;i<=k;++i)
if(ppre[ppre[a[i]]]<L) ans+=tree2::query(rt[c[a[i]]],1,n,L,R)-v[ppre[a[i]]];
wt(ans);putchar('\n');
}
}
return 0;
}
T2
先建出AC自動機,如果直接在上面跑消元,複雜度 \(O((nm)^3)\)。一種做法是由於它很稀疏,所以可以做稀疏矩陣高斯消元 \(O((nm)^2)\)。正解做法是考慮減少未知元的數量,因為 \(n\) 很小,所以trie樹上的鏈很少,只有 \(n\) 條,對於鏈交的位置,設下面 \(|son|-1\) 條鏈為未知數,這樣未知數的個數是 \(O(n)\),可以通過。
還有一種做法是類似 [SDOI2017]硬幣遊戲的概率生成函式做法……
T3
壓位高精直接做可以獲得 \(30-50pts\)。正解做法是先把後面幾步操作是什麼預測出來,然後可以一起做。複雜度很玄學但是能過。
Day 6
T1想到了正解,最後一步本來可以二分或者三分的,結果 \(O(1)\) 求求錯了……墊底了。
T1
考慮列舉中間交的那一部分,兩邊都是走最短路,這樣有 \(O(n^2)\) 條路徑。考慮當相交的距離相同的時候,最短的那個一定更優,所以只有 \(O(m)\) 條路徑,每條路徑相當於是一個二元組 \((a,b)\),有 \(a\) 長度的相交路徑和 \(b\) 長度的不相交路徑。要分配 \(k\) 的話只需要三分一個最優值或者二分導數即可。
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<cstring>
#include<queue>
#define re register
#define rint re int
#define rld re ld
using namespace std;
inline int rd(){
rint res=0;re char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
return res;
}
typedef long double ld;
inline int min(const int &a,const int &b){return a<b?a:b;}
inline ld min(const ld &a,const ld &b){return a<b?a:b;}
const int N=5000+13,INF=0x3f3f3f3f;
struct Edge{int v,nxt;}e[N<<1];
int n,m,K,tot,s1,t1,s2,t2,h[N],dist[N][N],f[N];
bool vis[N];
inline void add(const int &u,const int &v){e[++tot]=(Edge){v,h[u]};h[u]=tot;}
inline void init(){
memset(dist,0x3f,sizeof dist);
queue<int>q;while(!q.empty())q.pop();
for(rint bgn=1;bgn<=n;++bgn){
memset(vis,0,sizeof vis);
q.push(bgn);dist[bgn][bgn]=0,vis[bgn]=1;
while(!q.empty()){
rint u=q.front();q.pop();
for(rint i=h[u];i;i=e[i].nxt){
rint v=e[i].v;if(vis[v]) continue;
q.push(v);vis[v]=1,dist[bgn][v]=dist[bgn][u]+1;
}
}
}
}
inline ld calc1(const int &k,const int &x){
if(!k) return x;
int p=k/x;int rest=k-p*x;++p;
return rest*((ld)1.0/(p+1))+(x-rest)*((ld)1.0/p);
}
inline ld calc2(const int &k,const int &x){
if(!k) return x*2;
int p=k/x;int rest=k-p*x;++p;
return rest*((ld)2.0/(p+1))+(x-rest)*((ld)2.0/p);
}
inline ld solve(const int &k,const int &x,const int &y){//y條重的,x條不重的
if(!y){
if(!x) return 0;
return calc1(k,x);
}
if(!x) return calc2(k,y);
int l=0,r=k;
while(l<r){
int mid=(l+r)>>1;
if(calc1(mid,x)+calc2(k-mid,y)>calc1(mid+1,x)+calc2(k-mid-1,y)) l=mid+1;
else r=mid;
}
return calc1(l,x)+calc2(k-l,y);
}
int main(){
/*freopen("20.in","r",stdin);
freopen("city.out","w",stdout);*/
n=rd(),m=rd(),K=rd();
for(rint i=1,u,v;i<=m;++i) u=rd(),v=rd(),add(u,v),add(v,u);
init();
s1=rd(),t1=rd(),s2=rd(),t2=rd();
memset(f,0x3f,sizeof f);
f[0]=dist[s1][t1]+dist[s2][t2];
for(rint i=1;i<=n;++i){
for(rint j=1;j<=n;++j){
if(dist[i][j]==INF||dist[i][s1]==INF||dist[j][t1]==INF) continue;
if(dist[i][s2]!=INF&&dist[j][t2]!=INF){
rint cnt=dist[i][j]+dist[i][s1]+dist[i][s2]+dist[j][t1]+dist[j][t2];
f[dist[i][j]]=min(f[dist[i][j]],cnt);
}
if(dist[i][t2]!=INF&&dist[j][s2]!=INF){
rint cnt=dist[i][j]+dist[i][s1]+dist[i][t2]+dist[j][t1]+dist[j][s2];
f[dist[i][j]]=min(f[dist[i][j]],cnt);
}
}
}
rld ans=0x3f3f3f3f;
for(int i=0;i<=m;++i)
if(f[i]!=INF){
rld res=solve(K,f[i]-i,i);
ans=min(ans,res);
}
printf("%.12Lf\n",ans);
return 0;
}
T2
不會啊……有一個做法好像是區間dp的把兩維互換(因為答案顯然比較小)。
T3
掃描線掃 \(x\) 維,相當於是每一次加入或刪除一個“井”字形,維護這個圖形的補集,也就是 \(4\) 個 2-side 矩形。後面的不會啦……
Day 7
T3口胡出做法完全寫不出……T1沒有仔細想……然後十點半心態崩了,最後暴力都沒打墊底了。
T1
T2
T3
Day 8
T1
\(n+k-1-a(k-1)\choose k-1\)
T2
T3
Day 9
T1
T2
T3
Day 10
T1
T2
T3
講課
Day 1
講題:
T1 EC-Final G
給一個序列,每次查詢一個區間的子區間中顏色數是奇數的個數。
相當於是顏色數 \(\mod 2\)。考慮右端點掃描線,左端點使用資料結構維護,大概是說進來之後要對一些東西異或 \(1\),最後查一個區間歷史和。線段樹可以直接維護,複雜度 \(O((n+m)\log n)\)。
T2 Uoj神祕題
給一個序列,每次查詢區間中出現偶數次的數的異或和。
異或的神祕性質,偶數次的數異或和等於區間異或和異或上出現奇數次的數的異或和。和上面那個題同樣統計即可。
T3 JOISC2021 飲食區
\(n\) 個佇列 \(m\) 次操作,每次加入 \(k\) 個 \(type=c\) 的人或者刪掉 \(k\) 個人或者查詢第 \(k\) 個人的 \(type\)。
離線,掃描線掃序列,資料結構維護時間,每次操作拆成兩次單點。注意到 \(<0\) 時變成 \(=0\) 的操作不好維護,那就可以先去線上段樹上二分找到離某一次操作最近的那次 \(<0\) 的位置,需要維護一個字首和的 \(\min\)。
注意這題線上段樹上二分是從右到左,先向上再向下。
T4 某經典問題
DAG上,每個點出發邊權不同,每次查詢從某個點出發字典序 \(kth\) 的路徑到哪個點。\(n,m\leq 10^5,k\leq 10^{18}\)。
可持久化平衡樹可以做到區間複製的指數級增長,DAG的路徑數也是指數及增長。大概就是從後往前拓撲排序的過程中做區間複製然後把多餘的刪去即可。
T5 Luogu6617
記錄一下每個點前面最近的加起來等於 \(w\) 的點,如果沒有修改,相當於是區間取 \(\max\),看看這個值是否 \(\geq l\)。帶修改的話,如果很多個點的前驅都是一個點,改這個點的複雜度會炸。考慮如果有一個 \(x\) 指向 \(y(y<x)\),若有另外一個 \(z>x\) 也指向 \(y\),那麼它一定不是最優,不會被取到。所以只維護最近的那個指向就可以了。
點選檢視程式碼
#include<iostream>
#include<cstdio>
#include<set>
using namespace std;
inline int max(const int &a,const int &b){return a>b?a:b;}
inline int rd(){
int res=0;char c=getchar();
for(;!isdigit(c);c=getchar());
for(;isdigit(c);c=getchar())res=(res<<1)+(res<<3)+(c-'0');
return res;
}
const int N=5e5+13;
int n,m,w,a[N],b[N],c[N];
set<int> p[N];
set<int>::iterator it;
struct SegTree{int l,r,maxx;}t[N<<2];
#define ls p<<1
#define rs p<<1|1
#define mid ((t[p].l+t[p].r)>>1)
inline void refresh(int p){t[p].maxx=max(t[ls].maxx,t[rs].maxx);}
void build(int p,int l,int r){
t[p].l=l,t[p].r=r;
if(l==r) return t[p].maxx=b[l],void();
build(ls,l,mid),build(rs,mid+1,r);
refresh(p);
}
void update(int p,int x,int k){
if(t[p].l==t[p].r) return t[p].maxx=k,void();
x<=mid?update(ls,x,k):update(rs,x,k);
refresh(p);
}
int query(int p,int l,int r){
if(l<=t[p].l&&t[p].r<=r) return t[p].maxx;
int res=0;
if(l<=mid) res=max(res,query(ls,l,r));
if(r>mid) res=max(res,query(rs,l,r));
return res;
}
#undef mid
int main(){
// freopen("data.in","r",stdin);
// freopen("data.out","w",stdout);
n=rd(),m=rd(),w=rd();
for(int i=1;i<=n;++i){
a[i]=rd();
it=p[w-a[i]].end();
if(it!=p[w-a[i]].begin()){
int pos=*(--it);
if(!c[pos]) b[i]=pos,c[pos]=i;
}
p[a[i]].insert(i);
}
build(1,1,n);
int las=0;
while(m--){
int op,x,y;
op=rd(),x=rd(),y=rd();
if(op==1){
if(c[x]){//處理指向x的那個位置 c[x]
it=p[a[x]].lower_bound(x);
if(it!=p[a[x]].begin()){//前面還有
int pre=*(--it);
if(!c[pre]) b[c[x]]=pre,c[pre]=c[x],update(1,c[x],pre);
else b[c[x]]=0,update(1,c[x],0);
}
else{//前面沒了
b[c[x]]=0;
update(1,c[x],0);
}
c[x]=0;
}
it=p[a[x]].lower_bound(x);++it;
if(b[x]){//處理後面的位置連到x指到的位置
if(it!=p[a[x]].end()){
int nxt=*it;
if(b[nxt]<b[x]) c[b[x]]=nxt,b[nxt]=b[x],update(1,nxt,b[x]);
}
else c[b[x]]=0;
b[x]=0;update(1,x,0);
}
p[a[x]].erase(x);
a[x]=y;
p[a[x]].insert(x);
it=p[w-a[x]].upper_bound(x);
if(it!=p[w-a[x]].end()){//指向x
int nxt=*it;
if(b[nxt]<x){
c[b[nxt]]=0;
b[nxt]=x,c[x]=nxt;
update(1,nxt,x);
}
}
it=p[w-a[x]].lower_bound(x);
if(it!=p[w-a[x]].begin()){
int pre=*(--it);
if(!c[pre]||c[pre]>x){
b[c[pre]]=0;
update(1,c[pre],0);
c[pre]=x,b[x]=pre;
update(1,x,pre);
}
}
}
else{
x^=las,y^=las;
int res=query(1,x,y);
if(res>=x) putchar('Y'),putchar('e'),putchar('s'),++las;
else putchar('N'),putchar('o');
putchar('\n');
}
}
return 0;
}
P.S.這題細節太多了!set
這東西真的全是細節!!建議改成【模板】set!!
T6 bzoj3489
可持久化樹套堆???
T7 [Ynoi2007] rgxsxrs
值域分塊?爬了爬了……
T8 Loj6276
一棵樹,點有顏色,求有多少條鏈上的點顏色互不相同。每種顏色出現次數 \(\leq 20\),\(n\leq 10^5,4s\)。
T9 Luogu7126
太困了,爬了爬了
T10 Uoj207
每一對點賦一個隨機大整數,LCT維護子樹異或和。一條邊滿足條件,相當於是這條邊的某個端點子樹內包含了所有點對中的某個點。
Day 2
CF464E
首先可以使用01-trie維護最短路陣列,每一次三角不等式轉移相當於都是做了一個複製再修改,可以使用可持久化資料結構維護。判斷大小可以直接區間雜湊LCP找到第一個不一樣的位置,比大小即可。複雜度 \(O((n+m\log n)\log v)\)。
CF1446D2
加強:\(n\leq 5\times 10^7\)。