20230529 模拟赛订正
A. xor on tree
在一棵 个点的树上,第 个点初始点权 ,有 次操作:
0 u v
:1 x
:查询 的最大值,其中 是 的祖先(包括 ), TL=2s, ML=128MB.
在考场上先是绞尽脑汁想到一个时间复杂度 的动态开点先段树 + 01 trie 的做法,在注释里写道 bravo! is it correct? I bet it is.
。写完 debug 时突然意识到空间有点大,一看 ML,128MB,愤怒地在注释里写道 Oh no, Memory limit 128M! You must be kidding!
。随后稳定产出不能实际优化空间复杂度的做法(包括把 01trie 上动态开点线段树改成线段树分治+动态开点 01trie 这种奇怪的算法)。无奈只能先去看后两题,发现 T2 送了 60 分,T3 暴力都不会。又回来想 T1,冥思苦想之后,我发现空间复杂度大的原因其实是线段树和动态开点线段树同时存在,这样至少一个要动态开点,而只要其中一个不需要用,另一个的复杂度就可以保证了。我又想到,xor 问题一般要么想 01trie,要么想按位考虑,咦,那按位考虑能不能行呢?发现确实可以逐位确定答案,做一个类似基数排序或者数位 DP 的事情,这样时间复杂度不变,01trie 却被完全解构了,只需要用普通线段树维护区间加、单点查即可,而用树状数组就更快了。赛后发现我这个做法居然是空间复杂度最小的!跟大家都不一样!我只用了 27MB,但是其他人都是 40MB+。
从最开始的方法说起吧。首先考虑能树剖吗?想想发现不太行。尝试离线,并考虑到形如“从根到 ”的查询的常见离线方法是按照 dfs 序 dfs,进入一个点时加入一些东西,回溯时撤销,到点时统计此时的答案给到询问。
那么我们所需要支持的,就是将一个在某一段时间之内存在的数,插入 01trie,以及在线查询 01trie 的一个子树目前是否为空。
稍加思考便可发现,我们可以对这棵 01trie 的每个点开一棵线段树,插入时对在 trie 上经过的 的点的线段树执行区间 (插入)/(删除) 操作。而询问时,设询问的时间是 ,就只需要单点查询想去的儿子的线段树的 位置是否为 ,来判断是否能往那边走。
但是正如上文所说,这是一个空间复杂度过高的算法。考虑到 xor 问题的另一种解决思路——逐位确定——我们尝试先来确定各询问答案的最高位。
对于一个询问 , 的按位取反(~W
)一定是最优的。我们现在称一个“事件”为以下两种:
- :在 处有一个 内有效的数
- :在 处有一个 时的查询数
将所有 的最高位 的事件一起考虑,其他事件暂时不考虑,用栈做一个类似虚树上 dfs 的过程,一个事件被降维为 区间加或 单点查。查得答案为 表示“理想与现实不匹配”。同理处理最高位 及同理从高到低确定每一位(例如确定第 2 高位时就是把前两位为 00,01,10,11
的拉出来分别一起考虑),与基数排序非常相似。当然,如果在这一位发现“理想与现实不匹配”,就要修改理想的这一位为现实,以便后面的位能正确地排序。
点击查看代码
复制#include <bits/stdc++.h> using namespace std; typedef long long ll; namespace IO { const int buflen=1<<21; int x; bool f; char ch,buf[buflen],*p1=buf,*p2=buf; inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} inline int read(){ x=0,f=1,ch=gc(); while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); return f?x:-x; } } using IO::read; void print(int x){ if(x/10)print(x/10); putchar(x%10+48); } const int N=1e5+5,U=(1<<30)-1; int n,q,num,dfc,w[N],ww[N],op[N],ans[N],nod[N],stk[N*2],fa[N][18],dep[N]; vector<int>G[N]; vector<pair<int,int> >qu[N]; struct thing { int type,x,l,r,u; }a[N*2],d[N*2]; struct BIT { int c[N]; inline void add(int x,int y){ for(;x<=q+1;x+=x&-x)c[x]+=y; } inline int ask(int x){ int s=0;for(;x;x-=x&-x)s+=c[x]; return s; } }T; inline void chg(int l,int r,int v){ T.add(l+1,v),T.add(r+2,-v); } inline int ask(int p){ return T.ask(p+1); } void init(int x,int p){ fa[x][0]=p,dep[x]=dep[p]+1; for(int i=1;i<=17;i++)fa[x][i]=fa[fa[x][i-1]][i-1]; nod[++dfc]=x; reverse(qu[x].begin(),qu[x].end()); int las=q+1; for(auto o:qu[x])if(o.second>=0){ a[++num]=thing{0,o.second,o.first,las-1,dfc}; las=o.first; } else a[++num]=thing{1,U^(-o.second),o.first,0,dfc}; for(int y:G[x])if(y^p)init(y,x); } inline int glca(int u,int v){ if(u==v)return u; if(dep[u]>dep[v])swap(u,v); for(int i=17;~i;i--)if(dep[fa[v][i]]>=dep[u])v=fa[v][i]; if(u==v)return u; for(int i=17;~i;i--)if(fa[u][i]!=fa[v][i])u=fa[u][i],v=fa[v][i]; return fa[u][0]; } int main(){ freopen("tree.in","r",stdin);freopen("tree.out","w",stdout); n=read(),q=read(); for(int i=1;i<=n;i++)w[i]=read(),ww[i]=w[i]; for(int i=2,p;i<=n;i++)p=read(),G[p].emplace_back(i); for(int i=1;i<=n;i++)qu[i].emplace_back(0,w[i]); for(int i=1,u,v;i<=q;i++){ op[i]=read(),u=read(); if(op[i]==0){ v=read(); ww[u]=v; qu[u].emplace_back(i,v); } else qu[u].emplace_back(i,-ww[u]); } init(1,0); for(int b=29,B=0;~b;b--){ B|=1<<b; sort(a+1,a+num+1,[&](thing a,thing b){return (a.x&B)==(b.x&B)?a.u==b.u?a.type<b.type:a.u<b.u:(a.x&B)<(b.x&B);}); for(int i=1,j=0;i<=num;i++){ while(j<num&&(a[j+1].x&B)==(a[i].x&B))j++; int tp=0; for(int k=i;k<=j;k++){ while(tp&&glca(nod[a[stk[tp]].u],nod[a[k].u])!=nod[a[stk[tp]].u]){ chg(a[stk[tp]].l,a[stk[tp]].r,-1); tp--; } if(!a[k].type)stk[++tp]=k,chg(a[k].l,a[k].r,1); else ans[a[k].l]|=(!!ask(a[k].l))<<b; } while(tp)chg(a[stk[tp]].l,a[stk[tp]].r,-1),tp--; i=j; } for(int i=1;i<=num;i++)if(a[i].type&&!(ans[a[i].l]>>b&1))a[i].x^=1<<b; } for(int i=1;i<=q;i++)if(op[i])print(ans[i]),putchar('\n'); return 0; } /* g++ -o tree.exe tree.cpp -O2 -lm -std=c++14 -Wall -Wextra time ./tree.exe<in>out Queries turn into "How many dfn\in [l,r] are there in u's subtree on trie" and there is already O(loansg^2) This is online query using 2d BIT it is possible to do this in O(log^2),but too slow and memory too large we can do this through dfs(first offline-ize) add elements(queries and modifications)relevant to node x when passing node x and erase on departure with timl, timr so we can change the segment tree of all its ancestors on trie by range [timl,timr] complexity: O(nlognlogV) bravo! is this correct? I bet it is. Oh no! Memory limit 128M! You must be kidding! Specificity:we are only querying w[x], instead of an arbitrary number. Therefore, w[y] can predict w[x] */
B. calc on lowbit
定义 为通过每次等概率将 加上或减去 来将 变为 的期望步数。
求 。
第一个观察是 形如 时 ,但是它用处不大。
考虑如何按位 DP 计算 (因为用 map 记忆化算对正解没什么启发)。
观察到每次改变的位是最低的一些位,而改变之后会有一个更长的后缀变成全 0。变成了 0 的就不会再被操作了。于是这种侵略的顺序就提示我们进行按位 DP。
设 表示 都已变成 0,并且对 产生了 的进位,期望步数;同理定义辅助 dp 数组 为概率。
由于对于不同 ,计算过程是雷同的,因此我们就可以修改 的含义,统一地 dp。
首先把 差分为 ,接下来要用数位 dp 解决 的 。
由于是从低位到高位,套路地设 为当前到了第 位,把 都变成 0,并且第 位进了 到 来,对于所有 的期望步数之和。同理定义辅助 dp 数组 为概率。
转移枚举 为 :
- 若 ,
- 若 ,,
- 若 ,
点击查看代码
// ubsan: undefined // accoders // #include <bits/stdc++.h> // using namespace std; // typedef long long ll; // namespace IO { // const int buflen=1<<21; // int x; // bool f; // char ch,buf[buflen],*p1=buf,*p2=buf; // inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} // inline int read(){ // x=0,f=1,ch=gc(); // while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} // while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); // return f?x:-x; // } // } // using IO::read; // const int mod=998244353,I2=(mod+1)/2; // map<ll,int>mp; // inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);} // inline int calc(ll x){ // if(x==(x&-x))return 2; // if(mp.count(x))return mp[x];//cerr<<x<<';'; // return mp[x]=(ll)I2*(calc(x^(x&-x))+calc(x+(x&-x))+2)%mod; // } // int main(){ // freopen("calc.in","r",stdin);freopen("calc.out","w",stdout); // int T; // cin>>T; // while(T--){ // ll L,R; // cin>>L>>R; // int ans=0; // for(ll i=L;i<=R;i++)add(ans,calc(i)); // cout<<ans<<'\n'; // } // return 0; // } // /* // g++ -o calc.exe calc.cpp -O2 -lm -std=c++14 -Wall -Wextra // ./calc.exe<in // */ #include <bits/stdc++.h> using namespace std; typedef long long ll; const int mod=998244353,I2=(mod+1)/2; int f[66][2][2],g[66][2][2]; inline void add(int &x,int y){(x+=y)>=mod&&(x-=mod);} int calc(ll lim){ memset(f,0,sizeof f),memset(g,0,sizeof g); f[0][0][0]=0,g[0][0][0]=1; for(int i=0;i<60;i++)for(int j=0;j<=1;j++)for(int b=0;b<=1;b++){ for(int k=0;k<=1;k++){ int _b=(k>(lim>>i&1)?1:k==(lim>>i&1)?b:0); if(j+k==0)add(f[i+1][0][_b],f[i][j][b]),add(g[i+1][0][_b],g[i][j][b]); if(j+k==1){ add(f[i+1][1][_b],((ll)I2*(f[i][j][b]+g[i][j][b]))%mod); add(g[i+1][1][_b],(ll)g[i][j][b]*I2%mod); add(f[i+1][0][_b],((ll)I2*(f[i][j][b]+g[i][j][b]))%mod);//if(i==0&&!_b)cerr<<((ll)I2*(f[i][j][b]+g[i][j][b]))%mod<<':'; add(g[i+1][0][_b],(ll)I2*g[i][j][b]%mod); } if(j+k==2)add(f[i+1][1][_b],f[i][j][b]),add(g[i+1][1][_b],g[i][j][b]); } } //cerr<<f[1][0][0]<<';'; return (f[60][0][0]+f[60][1][0]+2ll*g[60][1][0])%mod; } int main(){ freopen("calc.in","r",stdin);freopen("calc.out","w",stdout); int T; cin>>T; while(T--){ ll L,R; cin>>L>>R; cout<<(calc(R)-calc(L-1)+mod)%mod<<'\n'; } } /* g++ -o calc.exe calc.cpp -O2 -lm -std=c++14 -Wall -Wextra ./calc.exe<in let f[i][j][b] denote the sum of expected steps to make x[0...i-1] zero over all x from 0 to (1<<i)-1 and that's <=(b=0)/>(b=1) lim, and carries j(0/1) to the i-th bit let g[i][j][b] denote the sum of possibilities f[0][0][0]=0,g[0][0][0]=1 enumerate k: j+k==0: f[i+1][0]+=f[i][j] g[i+1][0]+=g[i][j] j+k==1: f[i+1][1]+=f[i][j]/2+g[i][j] g[i+1][1]+=g[i][j]/2 f[i+1][0]+=f[i][j]/2+g[i][j] g[i+1][0]+=g[i][j]/2 j+k==2: f[i+1][1]+=f[i][j] g[i+1][1]+=g[i][j] b' is very easy to transit from b. Overall complexity: O(TlogV) */
C. color on board
有一个 的方格,一开始所有格子都是白色的,你的最终目的是把方格涂成你想要的颜色——二维 01 数组 。
你有三种刷的方法:
- 横着刷连续的 格,代价是 ;
- 竖着刷连续的 格,代价是 ;
- 只刷某个格子,代价是 。
每个格子的颜色是最后刷它的那个刷子的颜色,但是有以下几个限制:
- 每个格子最多只能被刷两次,无论这些刷子是否同色;
- 每个格子不能先刷白色刷子再刷黑色刷子,因为白色染料比较特殊,这会导致格子变成灰色,但是你可以先刷黑色刷子再刷白色刷子,也可以在一开始格子是初始颜色白色时刷黑色刷子。
现在你需要求出,刷出指定颜色的最小代价。
经过一番尝试不难发现这题是道网络流题。
因此考虑拆贡献。
【套路】最小割可以用来计算一个由若干 01 变量 构成的表达式的最小值。
A*(1-xi)*xj
can be translated into an edge from i to j of capacity A
A*xi
can be translated into an edge from S to i of capacity A
A*(1-xi)
can be translated into an edge from i to T of capacity A
(explanation: take A*xi as example, if xi decides to be 0, then it means (S,xi) isn't cut, contribute 0; otherwise cut)
set 4 0/1 variant for each cell: bh[x][y] wv[x][y] _bv[x][y] and _wh[x][y] (bh=brushed black horizontally; _bv=1-[brushed black vertically]) for each (x,y), consider its contribution as each of the roles among the following: 1. one of the cells when black-brushing l->r on the x-th row: a*bh[x][y] 2. one of the cells when white-brushing l->r on the x-th row: a*(1-_wh[x][y]) 3. one of the cells when black-brushing l->r on the y-th column: a*(1-_bv[x][y]) 4. one of the cells when white-brushing l->r on the y-th column: a*wv[x][y] 5. cell y when black-brushing y->some r on the x-th row: b*(1-bh[x][y-1])*bh[x][y] (when y-1==0,bh[x][y-1] is a constant) 6. cell y when white-brushing y->some r on the x-th row: b*_wh[x][y-1]*(1-_wh[x][y]) (same as above) 7. cell x when black-brushing x->some r on the y-th row: b*_bv[x-1][y]*(1-_bv[x][y]) 8. cell x when white-brushing x->some r on the y-th row: b*(1-wv[x-1][y])*wv[x][y]) 9. individual brushing of a cell which should eventually be black: c*(1-bh[x][y])*_bv[x][y]+inf*wv[x][y]+inf*(1-_wh[x][y]) 10. individual brushing of a cell which should eventually be white: c*bh[x][y]*(1-wv[x][y])+c*(1-_bv[x][y])*_wh[x][y]+inf*bh[x][y]*(1-_bv[x][y])
这就是本题拆贡献的方法。
总的来说,这种题就是要:
1. 说出题目中的个体所涉及到的一堆 01 变量,找到其中决定性的那些。
2. 然后写出它们对答案贡献的表达式。
3. 最后通过网络流决定它们最优的取值组合。
const int N=45,V=12805,E=40*40*11+5; int n,m,S,T,a,b,c,idx,tot=1,we[E*2],bh[N][N],wv[N][N],_wh[N][N],_bv[N][N]; char s[N][N]; vector<pair<int,int> >G[V]; int dis[V]; queue<int>Q; inline void adde(int u,int v,int w){ //v+=idx; G[u].emplace_back(v,++tot),we[tot]=w; G[v].emplace_back(u,++tot),we[tot]=0; } bool bfs(){ memset(dis,-1,sizeof dis); dis[S]=0; Q.push(S); while(!Q.empty()){ int x=Q.front();Q.pop(); for(auto e:G[x]){ int y=e.first,z=e.second; if(we[z]&&dis[y]==-1){ dis[y]=dis[x]+1; Q.push(y); } } } return dis[T]!=-1; } int dfs(int x,int in){ if(x==T)return in; int out=0; for(auto e:G[x]){ if(!in)break; int y=e.first,z=e.second; if(we[z]&&dis[y]==dis[x]+1){ int los=dfs(y,min(in,we[z])); in-=los,out+=los,we[z]-=los,we[z^1]+=los; } } if(!out)dis[x]=-1; return out; } int main(){ freopen("color.in","r",stdin);freopen("color.out","w",stdout); int tc; scanf("%d",&tc); while(tc--){ for(int i=1;i<=idx;i++)G[i].clear(); idx=0,tot=1; scanf("%d%d%d%d%d",&n,&m,&a,&b,&c); for(int i=1;i<=n;i++)scanf("%s",s[i]+1); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++){ bh[i][j]=++idx; wv[i][j]=++idx; _bv[i][j]=++idx; _wh[i][j]=++idx; } S=++idx,T=++idx; for(int x=1;x<=n;x++)for(int y=1;y<=m;y++){ adde(S,bh[x][y],a); adde(_wh[x][y],T,a); adde(_bv[x][y],T,a); adde(S,wv[x][y],a); if(y>1)adde(bh[x][y-1],bh[x][y],b);else adde(S,bh[x][y],b); if(y>1)adde(_wh[x][y],_wh[x][y-1],b);else adde(_wh[x][y],T,b); if(x>1)adde(_bv[x][y],_bv[x-1][y],b);else adde(_bv[x][y],T,b); if(x>1)adde(wv[x-1][y],wv[x][y],b);else adde(S,wv[x][y],b); if(s[x][y]=='#')adde(bh[x][y],_bv[x][y],c),adde(S,wv[x][y],1e9),adde(_wh[x][y],T,1e9); else adde(wv[x][y],bh[x][y],c),adde(_bv[x][y],_wh[x][y],c),adde(_bv[x][y],bh[x][y],1e9); } int flow=0; while(bfs())flow+=dfs(S,1e9); cout<<flow<<'\n'; } return 0; }
D. Bad
Formulate the three transformations into the following three functions:
Then we only need to compound the functions in an interval so as to support SGT-binary-search.
It can be shown that the composite function always looks as follows:
inline hanshu merge(hanshu G,hanshu H){ hanshu Z; if(G.y>H.b){ Z.a=G.a,Z.b=G.b,Z.y=G.y+H.y-H.b; } else if(G.y>H.a){ Z.a=G.a,Z.b=G.b+H.b-G.y,Z.y=H.y; } else { Z.a=H.a+G.b-G.y,Z.b=H.b+G.b-G.y,Z.y=H.y; } Z.a=max(Z.a,-10000000000000000ll),Z.b=min(Z.b,10000000000000000ll); if(Z.a>Z.b)Z.a=Z.b; return Z; }
点击查看代码
#include <bits/stdc++.h> using namespace std; typedef long long ll; namespace IO { const int buflen=1<<21; ll x; bool f; char ch,buf[buflen],*p1=buf,*p2=buf; inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} inline ll read(){ x=0,f=1,ch=gc(); while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); return f?x:-x; } } using IO::read; const int N=1e6+5; int n,q,type[N]; ll H,a[N]; struct hanshu { ll a,b,y; }t[N<<2]; inline hanshu merge(hanshu G,hanshu H){ hanshu Z; if(G.y>H.b){ Z.a=G.a,Z.b=G.b,Z.y=G.y+H.y-H.b; } else if(G.y>H.a){ Z.a=G.a,Z.b=G.b+H.b-G.y,Z.y=H.y; } else { Z.a=H.a+G.b-G.y,Z.b=H.b+G.b-G.y,Z.y=H.y; } Z.a=max(Z.a,-10000000000000000ll),Z.b=min(Z.b,10000000000000000ll); if(Z.a>Z.b)Z.a=Z.b; //cerr<<Z.a<<' '<<Z.b<<' '<<Z.y<<'\n'; return Z; } inline ll qiuzhi(ll x,hanshu F){ if(x<=F.a)return -1e16; if(x<=F.b)return F.y; return x-F.b+F.y; } void pushup(int k){ t[k]=merge(t[k<<1],t[k<<1|1]); } void chg(int p,hanshu v,int l,int r,int k){ if(l==r){t[k]=v;return;} int mid=l+r>>1; if(p<=mid)chg(p,v,l,mid,k<<1); else chg(p,v,mid+1,r,k<<1|1); pushup(k); } vector<pair<int,hanshu> >che; void linshi(int p,int l,int r,int k){ che.emplace_back(k,t[k]); if(l==r)return; int mid=l+r>>1; if(p<=mid)linshi(p,l,mid,k<<1),pushup(k); else linshi(p,mid+1,r,k<<1|1),t[k]=t[k<<1|1]; } int ask(int p,ll now,int l,int r,int k){ if(l==r)return l; int mid=l+r>>1; if(mid<p)return ask(p,now,mid+1,r,k<<1|1); ll noww=qiuzhi(now,t[k<<1]);//cerr<<p<<'_'<<l<<' '<<r<<':'<<now<<' '<<noww<< "---"<<t[k<<1].a<<' '<<t[k<<1].b<<' '<<t[k<<1].y <<'\n'; if(noww>0)return ask(p,noww,mid+1,r,k<<1|1); return ask(p,now,l,mid,k<<1); }int lim; void build(int l,int r,int k){ if(l==r){ //if(l<=lim){t[k].a=-1,t[k].b=0,t[k].y=0;return;} if(type[l]==1)t[k].a=a[l],t[k].b=a[l],t[k].y=0; if(type[l]==2)t[k].a=a[l]-1,t[k].b=1e16,t[k].y=a[l]; if(type[l]==3)t[k].a=-1e16,t[k].b=a[l],t[k].y=a[l]; return; } int mid=l+r>>1; build(l,mid,k<<1),build(mid+1,r,k<<1|1); pushup(k); } int main(){ freopen("bad.in","r",stdin);freopen("bad.out","w",stdout); n=read(),q=read(),H=read(); for(int i=1;i<=n;i++)type[i]=read(),a[i]=read(); build(1,n+1,1); cerr<<merge(hanshu{5,5,0},hanshu{5,(ll)1e16,6}).a<<")\n"; ll tmp; for(int qq=1,op,x,ty;qq<=q;qq++){ op=read(),x=read(); if(op==1){ ty=read(),tmp=read(); hanshu hh; if(ty==1)hh.a=tmp,hh.b=tmp,hh.y=0; if(ty==2)hh.a=tmp-1,hh.b=1e16,hh.y=tmp; if(ty==3)hh.a=-1e16,hh.b=tmp,hh.y=tmp; chg(x,hh,1,n+1,1); type[x]=ty,a[x]=tmp; } else { che.clear(); linshi(x,1,n+1,1); int mx=ask(x,H,1,n+1,1); while(!che.empty())t[che.back().first]=che.back().second,che.pop_back(); cout<<(mx==x?-1:mx-1)<<'\n';//cerr<<"__\n"; } } return 0; } /* g++ -o bad.exe bad.cpp -O2 -lm -std=c++14 -Wall -Wextra ./bad.exe<bad2.in>out */ /* g++ -o bad.exe bad.cpp -O2 -lm -std=c++14 -Wall -Wextra ./bad.exe<bad2.in>out f1(x)={-inf (x<=a[i]) | x-a[i] (x>a[i])} f2(x)={-inf (x<a[i]) | a[i] (x>=a[i])} f3(x)={-inf (x<=-inf) | a[i] (-inf<x<=a[i]) | x (x>a[i])} f2(f1(x))={-inf (x<=a[i] or x>a[i] and x<a[i]+a[j]) | a[j] (x>=a[i]+a[j])} G(x)={-inf (x<=a1) | y_1 (a1<x<=b1) | x-b1+y_1 (x>b1)} H(x)={-inf (x<=a2) | y_2 (a2<x<=b2) | x-b2+y_2 (x>b2)} G(H(x))={ -inf y_2 (a1<x<=b1 and a2<y_1<=b2 or x>b1 and (a2+b1-y_1< Xdelete, this part is <=b1,conflict to "x>b1")x<=b2+b1-y_1) min(a1,a2+b1-y_1)<x<=b2+b1-y_1 y_1-b2+y_2 (a1<x<=b1 and y_1>b2) x-b1+y_1-b2+y_2 (x>b1 and x-b1+y_1>b2) } */
Secret
First attempt: Feasible Flow. After doing , the operations become similar to a flow. Using prefix-suffix graph-constructing optimization we can decrease the number of nodes and edges to . But EK is too slow.
Second observation: The positions which we can jump from position is shaped as the union of a prefix and a suffix (possibly empty). We can build the graph using prefix-suffix graph-constructing optimization and compress SCCs so that it becomes a DAG in order to obtain the of each position.
Third relating: [TRICK] "Given an array , the s in can be decreased and added to arbitrarily, check if it is possible to make all zero." This problem can be expressed as the maximum matching of a bipartite graph with on one side and on the other. Use Hall Theorem for vertex-weighted graph to check the feasibility. So we can just use a segment tree that sustains range add and range min to implement "Hall Theorem for vertex-weighted graph".
#include <bits/stdc++.h> using namespace std; typedef long long ll; namespace IO { const int buflen=1<<21; int x; bool f; char ch,buf[buflen],*p1=buf,*p2=buf; inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} inline int read(){ x=0,f=1,ch=gc(); while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); return f?x:-x; } } using IO::read; const int N=15e5+5,E=75e5+5; int n,dfc,Scc,idx,tp,cnte,p[N],q[N],dfn[N],low[N],stk[N],L[N],R[N],uu[E],vv[E],bel[N]; int pre[N],suf[N]; ll a[N],b[N],A[N]; bool instk[N]; vector<pair<int,ll> >qu[N]; ll t[N<<2],tag[N<<2]; vector<int>G[N],dag[N]; void tarjan(int x){ dfn[x]=low[x]=++dfc; instk[x]=1,stk[++tp]=x; for(int y:G[x]){ if(!dfn[y]){ tarjan(y); low[x]=min(low[x],low[y]); } else if(instk[y])low[x]=min(low[x],dfn[y]); } if(low[x]==dfn[x]){ Scc++; L[Scc]=0,R[Scc]=n+2; while(tp){ instk[stk[tp]]=0; bel[stk[tp]]=Scc; if(stk[tp]<=n+1)L[Scc]=max(L[Scc],q[stk[tp]-1]),R[Scc]=min(R[Scc],p[stk[tp]]+1); if(stk[tp--]==x)break; } } } inline void adde(int u,int v){ G[u].emplace_back(v); cnte++; uu[cnte]=u,vv[cnte]=v; } void pushup(int k){ t[k]=min(t[k<<1],t[k<<1|1]); } void pushdown(int k){ if(!tag[k])return; tag[k<<1]+=tag[k],t[k<<1]+=tag[k]; tag[k<<1|1]+=tag[k],t[k<<1|1]+=tag[k]; tag[k]=0; } void build(int l,int r,int k){ tag[k]=0; if(l==r){ t[k]=A[l]; return; } int mid=l+r>>1; build(l,mid,k<<1),build(mid+1,r,k<<1|1); pushup(k); } void chg(int L,int R,ll v,int l,int r,int k){ if(L<=l&&r<=R){ t[k]+=v,tag[k]+=v; return; } pushdown(k); int mid=l+r>>1; if(L<=mid)chg(L,R,v,l,mid,k<<1); if(R>mid)chg(L,R,v,mid+1,r,k<<1|1); pushup(k); } ll ask(int L,int R,int l,int r,int k){ if(L>R)return 0; if(L<=l&&r<=R)return t[k]; pushdown(k); int mid=l+r>>1;ll mn=1e18; if(L<=mid)mn=min(mn,ask(L,R,l,mid,k<<1)); if(R>mid)mn=min(mn,ask(L,R,mid+1,r,k<<1|1)); return mn; } void solve(){ n=read(); for(int i=1;i<=n;i++)a[i]=read(); for(int i=1;i<=n;i++)b[i]=read(); for(int i=1;i<=n;i++)p[i]=read(); for(int i=1;i<=n;i++)q[i]=read(); p[n+1]=n+1,q[n+1]=0; a[n+1]=0,b[n+1]=0; for(int i=1;i<=n+1;i++)a[i]-=b[i]; for(int i=n+1;i;i--)a[i]-=a[i-1]; //q<=i<=p!!! for(int i=1;i<=idx;i++)G[i].clear(),dag[i].clear(),dfn[i]=low[i]=stk[i]=0; dfc=tp=Scc=cnte=0; idx=n+1; pre[1]=1; for(int i=2;i<=n;i++)pre[i]=++idx,adde(pre[i],pre[i-1]),adde(pre[i],i); suf[n+1]=n+1; for(int i=n;i>=2;i--)suf[i]=++idx,adde(suf[i],suf[i+1]),adde(suf[i],i); for(int i=1;i<=n;i++)if(q[i])adde(i+1,pre[q[i]]); for(int i=1;i<=n;i++)if(p[i]<=n)adde(i,suf[p[i]+1]); for(int i=1;i<=idx;i++)if(!dfn[i])tarjan(i); for(int i=1;i<=cnte;i++){ if(bel[uu[i]]==bel[vv[i]])continue; dag[bel[vv[i]]].emplace_back(bel[uu[i]]); } for(int i=1;i<=Scc;i++){ for(int j:dag[i])L[j]=max(L[j],L[i]),R[j]=min(R[j],R[i]); } for(int i=0;i<=n+2;i++)qu[i].clear(); ll all=0; for(int i=1;i<=n+1;i++)if(a[i]>0){ if(L[bel[i]]<R[bel[i]])qu[L[bel[i]]].emplace_back(R[bel[i]],a[i]); all+=a[i]; } A[n+2]=0; for(int i=n+1;i;i--){ A[i]=A[i+1]; if(a[i]<0)A[i]-=a[i]; } build(1,n+2,1);//cerr<<'A'; ll now=0; for(int i=0;i<=n+1;i++){ for(auto j:qu[i]){ chg(1,j.first,-j.second,1,n+2,1); } if(a[i]<0)now-=a[i]; if(now+ask(i+1,n+2,1,n+2,1)<0){puts("NO");return;} } //for(int i=1;i<=n+1;i++)cerr<<a[i]<<'_'<<L[bel[i]]<<' '<<R[bel[i]]<<'\n'; if(A[1]-all<0){puts("NO");return;} puts("YES"); } int main(){ freopen("secret.in","r",stdin);freopen("secret.out","w",stdout); int T=read(); while(T--)solve(); return 0; } /* g++ -o secret.exe secret.cpp -O2 -lm -std=c++14 time ./secret.exe<in>out */
Red
太久没练斜率优化了,导致这种水题都调不出来了。
凸包优化最近几个月见了好几道,于是直接往这方面想了,变成了修改一个后缀的 、插入一个 相同段所对应最大的 这样一个关于 的一次函数(不需要删除,因为删不删无所谓)。那要么李超树(复杂度过高),要么二分维护分段函数。我选择后者,4K代码写完没调完,惨不忍睹。
赛后反思了一下,我始终在以此时的后缀 来考虑列式:,这样看起来就跟斜率优化不搭边。而如果能把 写成原始的形式:,就可以想到交换 max 顺序,从而简化成这样:,前者相当于是定值,就可以斜率优化了。
此外,还有细节,这个斜率优化很明显是要维护①加入 ②用斜率等于 的直线切凸包。那么斜率是递增的负数,点的加入没有横坐标的单调性,但是纵坐标不降。因此不难想到旋转坐标系来维护。
复习一下斜率优化:最大化截距时维护【上凸壳】,最小化截距时维护【下凸壳】,凸包走向的斜率取决于【切线斜率的正负】。
#include <bits/stdc++.h> using namespace std; typedef long long ll; namespace IO { const int buflen=1<<21; int x; bool f; char ch,buf[buflen],*p1=buf,*p2=buf; inline char gc(){return p1==p2&&(p2=buf+fread(p1=buf,1,buflen,stdin),p1==p2)?EOF:*p1++;} inline int read(){ x=0,f=1,ch=gc(); while(ch<'0'||ch>'9'){if(ch=='-')f=0;ch=gc();} while(ch>='0'&&ch<='9')x=(x<<1)+(x<<3)+(ch^48),ch=gc(); return f?x:-x; } } using IO::read; const int N=2e4+5; const ll INF=1e18; int n,m,falizhi,w[105][N],h[105][N],a[105][N]; ll f[105][N]; ll g[N]; int q[N]; int main(){ freopen("red.in","r",stdin);freopen("red.out","w",stdout); n=read(),m=read(),falizhi=read(); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)w[i][j]=read(); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)h[i][j]=read(); for(int i=1;i<=n;i++)for(int j=1;j<=m;j++)a[i][j]=read(); memset(f,-0x3f,sizeof f); for(int j=1;j<=n;j++){ if(j==1)f[1][1]=falizhi; else f[j][1]=f[j-1][1]-w[j-1][1]; for(int i=0;i<=m;i++)g[i]=-INF; g[1]=f[j][1]-w[j][1]; int l=1,r=0; q[++r]=1; for(int i=2;i<=m;i++){ while(l<r&&-(ll)(h[j][q[l+1]]-h[j][q[l]])*a[j][i]<=(ll)(g[q[l+1]]-g[q[l]]))l++; f[j][i]=max(f[j-1][i]-w[j-1][i],g[q[l]]+(ll)h[j][q[l]]*a[j][i]); // cerr<<q[l]<<'.'; g[i]=max(g[i-1],f[j][i]-w[j][i]); while(l<r&&(g[i]!=g[q[r]]&&(ll)(h[j][q[r]]-h[j][q[r-1]])*(g[i]-g[q[r]])<(ll)(h[j][i]-h[j][q[r]])*(g[q[r]]-g[q[r-1]]) ||g[q[r]]==g[i]&&h[j][i]>=h[j][q[r]]))r--; q[++r]=i; } } cout<<max(-1ll,f[n][m])<<'\n'; return 0; }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具