【考試總結】2022-03-15
夢批糼
只會 \(\Theta(n^6)\),做法是三維字首和求出來包含每個點的立方體個數
注意總選擇方案並不是合法立方體個數而是 \(\frac 18 n(n+1)m(m+1)k(k+1)\)
Code Display
const int N=70; int sum[N][N][N],a[N][N][N],n,m,o,w,squ[N][N][N],val[N][N][N]; inline int calc(int a,int b,int c,int d,int e,int f){ return sum[d][e][f]-sum[a-1][e][f]-sum[d][b-1][f]-sum[d][e][c-1]+sum[a-1][b-1][f]+sum[a-1][e][c-1]+sum[d][b-1][c-1]-sum[a-1][b-1][c-1]; } signed main(){ freopen("dream.in","r",stdin); freopen("dream.out","w",stdout); n=read(),m=read(),o=read(),w=read(); rep(i,1,n) rep(j,1,m) rep(k,1,o){ a[i][j][k]=!read(); sum[i][j][k]=sum[i-1][j][k]+sum[i][j][k-1]+sum[i][j-1][k]-sum[i-1][j-1][k]-sum[i-1][j][k-1]-sum[i][j-1][k-1]+sum[i-1][j-1][k-1]+a[i][j][k]; } rep(i,1,n) rep(j,1,m) rep(k,1,o) val[i][j][k]=read()*(!a[i][j][k]); int all=n*(n+1)*m*o*(o+1)*(m+1)/8%mod; int invall=ksm(all,w*(mod-2)); int cnt=0; rep(i,1,n) rep(j,1,m) rep(k,1,o){ rep(i1,i,n) rep(j1,j,m) rep(k1,k,o){ if(!calc(i,j,k,i1,j1,k1)){ ++cnt; squ[i][j][k]++; squ[i][j1+1][k]--; squ[i1+1][j][k]--; squ[i][j][k1+1]--; squ[i1+1][j1+1][k]++; squ[i1+1][j][k1+1]++; squ[i][j1+1][k1+1]++; squ[i1+1][j1+1][k1+1]--; }else break; } } int ans=0; rep(i,1,n) rep(j,1,m) rep(k,1,o){ squ[i][j][k]+=squ[i-1][j][k]+squ[i][j][k-1]+squ[i][j-1][k]-squ[i-1][j-1][k]-squ[i-1][j][k-1]-squ[i][j-1][k-1]+squ[i-1][j-1][k-1]; if(val[i][j][k]&&squ[i][j][k]){ int per=del(ksm(cnt,w),ksm(cnt-squ[i][j][k],w)); ckadd(ans,mul(val[i][j][k],per)); } } print(mul(ans,invall)); return 0; }
等你哈蘇德
考慮將黑色白色分別視為 \(\pm1\),那麼題目中的限制就變成了字首和陣列相鄰兩個差的絕對值不過 \(1\)
此時一個線段就變成了兩個端點的操作,如果初始狀態所有線段沒有顏色,那麼離散化後將在座標軸上相鄰奇數度數點連邊跑歐拉回路就能得到一組答案
如果有些線段是有初始顏色的,先將其任意定向,並計算每個點出度減入度的數值 \(d_i\)
那麼最終的調整形式應該為將一些點的正度數還給那些度數為負的邊,方式也就是讓邊反向,所以正權點和負權點通過圖上能改變方向的邊構成了二分圖,使用網路流就能找到最終哪些邊發生了反向
注意這裡奇數度數點 \(i\) 的處理方式應當是新增 \([corr_i,coor_{i+1}-1]\)
Code Display
const int N=5e5+10,inf=0x3f3f3f3f3f3f3f3f; struct Network_Flow{ struct edge{int to,nxt,lim,id;}e[N<<2]; int head[N],dep[N],S,T,ecnt=1,cur[N]; inline void adde(int u,int v,int w,int id){ e[++ecnt]={v,head[u],w,id}; head[u]=ecnt; return ; } inline void add(int u,int v,int w,int id){return adde(u,v,w,id),adde(v,u,0,0);} inline bool bfs(){ queue<int> q; q.push(S); rep(i,1,T) cur[i]=head[i],dep[i]=0; dep[S]=1; while(q.size()){ int fr=q.front(); q.pop(); for(int i=head[fr];i;i=e[i].nxt) if(e[i].lim){ int t=e[i].to; if(dep[t]) continue; dep[t]=dep[fr]+1; q.push(t); } } return dep[T]; } inline int dfs(int x,int in){ if(x==T) return in; int out=0; for(int i=head[x];i;cur[x]=i,i=e[i].nxt) if(e[i].lim){ int t=e[i].to; if(dep[t]!=dep[x]+1) continue; int res=dfs(t,min(in,e[i].lim)); e[i].lim-=res; e[i^1].lim+=res; in-=res; out+=res; if(!in) break; } if(!out) dep[x]=0; return out; } inline int dinic(){ int sum=0; while(bfs()) sum+=dfs(S,inf); return sum; } }F; bool vis[N]; int deg[N],n,m,lsh[N],cnt,l[N],r[N],dir[N]; int val[N]; signed main(){ freopen("wait.in","r",stdin); freopen("wait.out","w",stdout); m=read(); n=read(); rep(i,1,m){ l[i]=read(),r[i]=read()+1; dir[i]=read(); lsh[++cnt]=l[i],lsh[++cnt]=r[i]; } sort(lsh+1,lsh+cnt+1); cnt=unique(lsh+1,lsh+cnt+1)-lsh-1; vector<int> opt(cnt+1); rep(i,1,m){ l[i]=lower_bound(lsh+1,lsh+cnt+1,l[i])-lsh; r[i]=lower_bound(lsh+1,lsh+cnt+1,r[i])-lsh; opt[l[i]]^=1; opt[r[i]]^=1; if(dir[i]<=0){ deg[l[i]]--,deg[r[i]]++; if(dir[i]==-1) F.add(r[i],l[i],1,i); dir[i]=0; }else deg[r[i]]--,deg[l[i]]++; } rep(i,1,cnt) if((opt[i]^=opt[i-1])){ opt[i]^=1; opt[i+1]^=1; F.add(i,i+1,1,0); deg[i]++; deg[i+1]--; } F.S=cnt+1; F.T=cnt+2; int Flow=0; rep(i,1,cnt) if(deg[i]){ deg[i]/=2; if(deg[i]<0) F.add(i,F.T,-deg[i],0); else F.add(F.S,i,deg[i],0),Flow+=deg[i]; } if(F.dinic()<Flow) puts("-1"),exit(0); for(int x=1;x<=cnt;++x){ for(int i=F.head[x];i;i=F.e[i].nxt) if(F.e[i].to!=F.S){ if(!F.e[i].id) continue; if(F.e[i].lim==0) dir[F.e[i].id]=1; } } vector<int> vals(cnt+1); rep(i,1,m) print(dir[i]); putchar('\n'); return 0; }
喜歡最最痛
考慮猜測並推廣 \(m=2\) 的情況:在樹上選擇若干個不相交的鏈使得權值最大,再加上已經給出的新邊的前若干小
使用類似模擬費用流的反悔操作:每次選出當前樹上的帶權直徑,將直徑的邊權取反
不難發現選了 \(i\) 次帶權直徑就一定能找到 \(i\) 個帶權鏈,如果被取反了多次就可以理解成沒有被選擇
根據 【林克卡特樹】 發現選擇的 鏈的數量 與 權值總和 構成的函式是斜率遞減的函式
而給出若干數,選出前 \(k\) 小還是一個斜率單遞增的函式,那麼答案也是關於選擇鏈數量的凸函式
那麼這部分拿一個單調指標掃描即可
使用 \(\text{LCT}\) 找樹上帶權直徑,需要維護的內容是每個點開始向下的鏈的最大字首,子樹裡面最長鏈
那麼為了合併,我們仍然需要維護每條鏈的最大字尾和鏈的邊權和(其實就是最大欄位和的模型)
為了 翻轉/連邊 那麼需要維護從鏈底到鏈頂兩份資訊(和 【定位系統】 一題類似),為了支援鏈權取反,還要維護正權資訊和負權資訊
對於虛子樹資訊,使用兩個 multiset
來分別維護虛兒子字首鏈和子樹裡面最長鏈,記得更新答案也可以使用兩個虛子樹資訊來合併
虛實轉換的時候直接和 【定位系統】 寫一樣的 fetch,lost
即可,這樣子的寫法確實非常簡便了
實現的時候需要過載路徑結構體 和 每個點資訊的結構體,這裡有一個非常神仙的寫法是強制程式碼裡面最大字首,字尾的兩個端點相同,實際上另一個端點就是這個點本身
Code Display
const int N=2e5+10,inf=0x3f3f3f3f3f3f3f3f;
int n,m,ls[N],rs[N],fa[N],val[N];
bool rev[N],sig[N];
struct path{
int u,v,w; path(){}
path(int U,int V,int W){u=U,v=V,w=W;}
bool operator <(const path &a)const{
if(w^a.w) return w<a.w;
if(u^a.u) return u<a.u;
return v<a.v;
}
path operator +(const path &a)const{
path res;
res.u=u; res.v=a.u; res.w=a.w+w;
if(res.u>res.v) swap(res.u,res.v);
return res;
}//attention to the order of the nodes
path operator +(const int &a)const{return path(u,v,w+a);}
path operator -(const int &a)const{return path(u,v,w-a);}
bool operator ==(const path &a)const{return u==a.u&&v==a.v&&w==a.w;}
};
struct data{
path pre,suf,ans;
int sum;
data(){}
data(path a,path b,path c,int d){pre=a,suf=b; ans=c; sum=d;}
data operator +(const data &a)const{
data res;
res.sum=sum+a.sum;
res.pre=max(pre,a.pre+sum);
res.suf=max(suf+a.sum,a.suf);
res.ans=max(max(a.ans,ans),suf+a.pre);
return res;
}
}dp[N],lef[N][2],rig[N][2];
multiset<path> pre[N],chain[N];
// Code Designer! here only data.ans's path may has a different u,v
// "pre" and "suf" has the meaning of u->current x
inline bool isroot(int x){return ls[fa[x]]!=x&&rs[fa[x]]!=x;}
inline void push_rev(int x){
swap(ls[x],rs[x]);
swap(lef[x][0],rig[x][0]);
swap(lef[x][1],rig[x][1]);
rev[x]^=1;
return ;
}
inline void push_up(int x){
lef[x][0]=rig[x][0]=data(dp[x].pre+val[x],dp[x].pre+val[x],dp[x].ans,val[x]);
lef[x][1]=rig[x][1]=data(dp[x].pre-val[x],dp[x].pre-val[x],dp[x].ans,-val[x]);
if(ls[x]){
lef[x][0]=lef[ls[x]][0]+lef[x][0];
lef[x][1]=lef[ls[x]][1]+lef[x][1];
rig[x][0]=rig[x][0]+rig[ls[x]][0];
rig[x][1]=rig[x][1]+rig[ls[x]][1];
}
if(rs[x]){
lef[x][0]=lef[x][0]+lef[rs[x]][0];
lef[x][1]=lef[x][1]+lef[rs[x]][1];
rig[x][0]=rig[rs[x]][0]+rig[x][0];
rig[x][1]=rig[rs[x]][1]+rig[x][1];
}
}
inline void push_sig(int x){
swap(lef[x][0],lef[x][1]);
swap(rig[x][0],rig[x][1]);
sig[x]^=1; val[x]*=-1;
return ;
}
inline void push_down(int x){
if(rev[x]){
if(ls[x]) push_rev(ls[x]);
if(rs[x]) push_rev(rs[x]);
rev[x]=0;
}
if(sig[x]){
if(ls[x]) push_sig(ls[x]);
if(rs[x]) push_sig(rs[x]);
sig[x]=0;
} return ;
}
inline void rotate(int x){
int y=fa[x],z=fa[y];
if(!isroot(y)) if(ls[z]==y) ls[z]=x; else rs[z]=x;
if(ls[y]==x) ls[y]=rs[x],fa[rs[x]]=y,rs[x]=y;
else rs[y]=ls[x],fa[ls[x]]=y,ls[x]=y;
fa[x]=z; fa[y]=x; return push_up(y),push_up(x);
}
int stk[N],top;
inline void splay(int x){
int y=x; stk[top=1]=y;
while(!isroot(y)) stk[++top]=y=fa[y];
while(top) push_down(stk[top--]);
while(!isroot(x)){
int y=fa[x],z=fa[y];
if(!isroot(y)) rotate(((ls[z]==y)^(ls[y]==x))?x:y);
rotate(x);
} return ;
}
inline void recalc(int x){
assert(chain[x].size()&&pre[x].size());
dp[x].pre=*pre[x].rbegin();
dp[x].ans=*chain[x].rbegin();
if(pre[x].size()>=2){
multiset<path>::iterator iter=prev(pre[x].end());
ckmax(dp[x].ans,*prev(iter)+*iter+val[x]);
}
return ;
}
inline void lost(int x,int son){
chain[x].erase(chain[x].find(lef[son][0].ans));
pre[x].erase(pre[x].find(lef[son][0].pre));
assert(chain[x].size()&&pre[x].size());
recalc(x);
}
inline void fetch(int x,int son){
chain[x].insert(lef[son][0].ans);
pre[x].insert(lef[son][0].pre);
assert(chain[x].size()&&pre[x].size());
recalc(x);
}
inline void access(int x){
for(int y=0;x;x=fa[y=x]){
splay(x);
path now=lef[x][0].ans;
if(rs[x]) fetch(x,rs[x]);
if(y) lost(x,y);
rs[x]=y; push_up(x);
}
}
inline void make_root(int x){
access(x); splay(x); push_rev(x);
}
inline void link(int x,int y){
make_root(x);
access(y); splay(y);
int tmp=y; while(ls[tmp]) tmp=ls[tmp]; if(tmp==x) return ;
push_rev(y);
fa[x]=y; fetch(y,x); push_up(y);
return ;
}
int ans,sum[N];
signed main(){
freopen("love.in","r",stdin); freopen("love.out","w",stdout);
n=read(); m=read();
rep(i,1,n){
pre[i].insert({i,i,0});
chain[i].insert({i,i,0});
recalc(i); push_up(i);
}
rep(i,1,n-1){
int u=read(),v=read(),w=read();
ans+=2*w;
val[i+n]=w;
pre[i+n].insert({0,0,-inf}); chain[i+n].insert({0,0,-inf});
recalc(i+n); push_up(i+n);
link(u,i+n);
link(v,i+n);
}
rep(i,1,m){
make_root(1);
path now=lef[1][0].ans;
make_root(now.u);
access(now.v); splay(now.v);
push_sig(now.v);
sum[i]=now.w+sum[i-1];
}
print(ans);
multiset<int> vals,oth;
int cnt=0,nowsum=0,indic=0;
while(m--){
int w=read(); ++cnt;
if(vals.size()){
int vv=*prev(vals.end());
if(w<vv){
vals.erase(prev(vals.end()));
vals.insert(w);
nowsum-=vv-w;
w=vv;
}
} oth.insert(w);
while(indic<=cnt&&oth.size()){
if(sum[indic]-nowsum<sum[indic+1]-(*oth.begin()+nowsum)){
indic++;
vals.insert(*oth.begin());
nowsum+=*oth.begin();
oth.erase(oth.begin());
}else break;
}
print(ans-sum[indic]+nowsum);
} putchar('\n');
return 0;
}
學校訓練已經出現很多次 LCT+DDP 的題目了,不清楚是什麼風向。多項式這個大毒被 ban 之後的新藥了是吧?