高一上一月上旬日记
1.1
闲话
- 以为下午
开始进校,遂从家里出发已经比较晚了。到学校后发现是 在机房坐好,遂直接拉着行李来机房了。 - 晚上
说今明两天把字符串和动态规划专题收收尾。
做题纪要
CF601E A Museum Robbery
-
线段树分治。
点击查看代码
const ll mod=1000000007,base=10000019; ll st[15010],ed[15010],v[30010],w[30010],f[18][1010],jc[1010],ans[30010],k; struct SMT { struct SegmentTree { vector<ll>info; }tree[120010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void update(ll rt,ll l,ll r,ll x,ll y,ll id) { if(x<=l&&r<=y) { tree[rt].info.push_back(id); return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } void solve(ll rt,ll l,ll r,ll dep) { for(ll i=1;i<=k;i++) { f[dep][i]=f[dep-1][i]; } for(ll i=0;i<tree[rt].info.size();i++) { for(ll j=k;j>=w[tree[rt].info[i]];j--) { f[dep][j]=max(f[dep][j],f[dep][j-w[tree[rt].info[i]]]+v[tree[rt].info[i]]); } } if(l==r) { for(ll i=1;i<=k;i++) { ans[l]=(ans[l]+f[dep][i]*jc[i-1]%mod)%mod; } } else { ll mid=(l+r)/2; solve(lson(rt),l,mid,dep+1); solve(rson(rt),mid+1,r,dep+1); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,pd,x,tim=0,i; cin>>n>>k; for(i=1;i<=n;i++) { cin>>v[i]>>w[i]; st[i]=1; ed[i]=-1; } cin>>m; for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { n++; cin>>v[n]>>w[n]; st[n]=tim+1; ed[n]=-1; } if(pd==2) { cin>>x; ed[x]=tim; } if(pd==3) { tim++; } } for(i=1;i<=n;i++) { ed[i]=(ed[i]==-1)?tim:ed[i]; if(st[i]<=ed[i]) { T.update(1,1,tim,st[i],ed[i],i); } } for(i=0;i<=k-1;i++) { jc[i]=(i==0)?1:jc[i-1]*base%mod; } T.solve(1,1,tim,1); for(i=1;i<=tim;i++) { cout<<ans[i]<<endl; } return 0; }
HZOJ 368. 稳稳的参天大树
-
观察到深度为
的点 在 时刻它的初始权值对 的异或次数为 。- 对于长度为
的数组 ,初始时有 ,对其做 阶前缀和后有 即为异或次数。 - 将前缀和转化成二维平面的格路计数问题后,有
即为所求。
- 对于长度为
-
由
定理可知 当且仅当 ,即 在二进制表示下是 的二进制表示下的子集,等价于 二进制表示是 二进制表示的子集。 -
此时只需要快速地做到对于每个数
求出它二进制表示下所有子集的异或和。 -
考虑对子集按位
,设 表示只有前 位和 不同的 的所有子集的异或和。状态转移方程为 。 -
做前缀和后注意更新顺序。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[2000010],dep[2000010],a[2000010],f[2000010],cnt=0,tot=0,base=(1<<20)-1; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int fa) { dep[x]=dep[fa]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,u,v,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } dfs(1,0); for(i=1;i<=n;i++) { f[dep[i]-1]^=a[i]; } for(j=0;j<20;j++) { for(i=0;i<=base;i++) { if((i>>j)&1) { f[i]^=f[i^(1<<j)]; } } } for(i=1;i<=n;i++) { printf("%d ",f[(i-1)^base]); } return 0; }
luogu P5058 [ZJOI2004] 嗅探器
-
找到割点后判断是否将
分到了两个连通块内。 -
建出圆方树后从
开始遍历,用栈记录下路径上的点即可。注意嗅探器不能安装在中心服务器上。点击查看代码
struct node { int nxt,to; }e[1000010]; int head[200010],dfn[200010],low[200010],a,b,ans=0x7f7f7f7f,v_dcc=0,cnt=0,tot=0; stack<int>s,t; vector<int>g[400010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { v_dcc++; g[v_dcc].push_back(x); g[x].push_back(v_dcc); int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); g[v_dcc].push_back(tmp); g[tmp].push_back(v_dcc); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs(int x,int fa) { s.push(x); if(x==b) { t=s; t.pop(); while(t.size()>=3) { ans=min(ans,t.top()); t.pop(); } } for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa) { dfs(g[x][i],x); } } s.pop(); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,u,v,i; cin>>n; while(cin>>u>>v) { if(u==0&&v==0) { break; } else { add(u,v); add(v,u); } } v_dcc=n; for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } cin>>a>>b; while(s.empty()==0) { s.pop(); } dfs(a,0); if(ans>n) { cout<<"No solution"<<endl; } else { cout<<ans<<endl; } return 0; }
CF487E Tourists
-
对于每个方点开一个
multiset
存储所在点双连通分量内圆点的最小值,修改时若遇到菊花则会被卡成暴力。 -
不妨让每个方点只存储圆方树上儿子节点的最小值,同样使用
multiset
维护。查询时注意细节。点击查看代码
struct node { int nxt,to; }e[200010]; int head[100010],w[200010],dfn[200010],low[100010],fa[200010],siz[200010],dep[200010],son[200010],top[200010],pos[200010],v_dcc=0,cnt=0,tot=0,n; stack<int>s; vector<int>g[200010]; multiset<int>t[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { v_dcc++; g[x].push_back(v_dcc); g[v_dcc].push_back(x); int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); g[tmp].push_back(v_dcc); g[v_dcc].push_back(tmp); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; if(x<=n) { t[x].insert(w[x]); } for(int i=0;i<g[x].size();i++) { if(g[x][i]!=father) { dfs1(g[x][i],x); siz[x]+=siz[g[x][i]]; son[x]=(siz[g[x][i]]>siz[son[x]])?g[x][i]:son[x]; if(x>n) { t[x].insert(w[g[x][i]]); } } } } void dfs2(int x,int id) { top[x]=id; tot++; dfn[x]=tot; pos[tot]=x; if(son[x]!=0) { dfs2(son[x],id); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa[x]&&g[x][i]!=son[x]) { dfs2(g[x][i],g[x][i]); } } } } struct SMT { struct SegmentTree { int minn; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn); } void build(int rt,int l,int r) { if(l==r) { tree[rt].minn=*t[pos[l]].begin(); return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(int rt,int l,int r,int pos,int val) { if(l==r) { tree[rt].minn=val; return; } int mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } int query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt].minn; } int mid=(l+r)/2; if(y<=mid) { return query(lson(rt),l,mid,x,y); } if(x>mid) { return query(rson(rt),mid+1,r,x,y); } return min(query(lson(rt),l,mid,x,y),query(rson(rt),mid+1,r,x,y)); } }T; void update(int x,int y) { t[x].erase(t[x].find(w[x])); t[x].insert(y); T.update(1,1,v_dcc,dfn[x],*t[x].begin()); if(fa[x]!=0) { t[fa[x]].erase(t[fa[x]].find(w[x])); t[fa[x]].insert(y); T.update(1,1,v_dcc,dfn[fa[x]],*t[fa[x]].begin()); } w[x]=y; } int query(int u,int v) { int ans=0x7f7f7f7f; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { ans=min(ans,T.query(1,1,v_dcc,dfn[top[u]],dfn[u])); u=fa[top[u]]; } else { ans=min(ans,T.query(1,1,v_dcc,dfn[top[v]],dfn[v])); v=fa[top[v]]; } } if(dep[u]<dep[v]) { ans=min(ans,T.query(1,1,v_dcc,dfn[u],dfn[v])); if(u>n) { ans=min(ans,*t[fa[u]].begin()); } } else { ans=min(ans,T.query(1,1,v_dcc,dfn[v],dfn[u])); if(v>n) { ans=min(ans,*t[fa[v]].begin()); } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,q,u,v,i; char pd; cin>>n>>m>>q; for(i=1;i<=n;i++) { cin>>w[i]; } for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } v_dcc=n; tarjan(1); dfs1(1,0); tot=0; dfs2(1,1); T.build(1,1,v_dcc); for(i=1;i<=q;i++) { cin>>pd>>u>>v; if(pd=='C') { update(u,v); } else { cout<<query(u,v)<<endl; } } return 0; }
SP2878 KNIGHTS - Knights of the Round Table
-
若
两人之间不相互作用憎恨,则在它们中间连一条边,此时能召开圆桌会议的条件是参加会议的骑士构成了奇环。 -
观察到若两个骑士分属不同的点双连通分量,则他们不可能一起出席会议,故可以只考虑点双连通分量内部的点。
-
进一步挖掘性质,发现若一个点双连通分量内存在奇环,则这个点双连通分量中的所有点都可以被至少一个奇环包含。
- 考虑奇环从某两处断开后得到的两部分长度奇偶性不同即可证明。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[1010],f[1010][1010],dfn[1010],low[1010],vis[1010],v_dcc=0,cnt=0,tot=0,siz,n; stack<int>s; bitset<1010>ans,ins; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } bool dfs(int x,int col) { vis[x]=col; for(int i=head[x];i!=0;i=e[i].nxt) { if(ins[e[i].to]==1) { if(vis[e[i].to]==0&&dfs(e[i].to,3-col)==false) { return false; } if(vis[e[i].to]==col) { return false; } } } return true; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { siz=1; ins.reset(); fill(vis+1,vis+1+n,0); ins[x]=1; int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=1; siz++; } if(siz>=3&&dfs(x,0)==false) { ans|=ins; } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,u,v,i,j; while(cin>>n>>m) { if(n==0&&m==0) { break; } else { ans.reset(); fill(e+1,e+1+cnt,(node){0,0}); fill(head+1,head+1+n,0); fill(dfn+1,dfn+1+n,0); fill(low+1,low+1+n,0); for(i=1;i<=n;i++) { fill(f[i]+1,f[i]+1+n,0); } cnt=tot=v_dcc=0; for(i=1;i<=m;i++) { cin>>u>>v; f[u][v]=f[v][u]=1; } for(i=1;i<=n;i++) { for(j=i+1;j<=n;j++) { if(f[i][j]==0) { add(i,j); add(j,i); } } } for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } cout<<n-ans.count()<<endl; } } return 0; }
1.2
闲话
- 早上到机房后发现电脑又被重启了。而且学校
也上不去。按照上周的安排今天是该补 的,但是被 拉来打模拟赛了。 - 上午
打 accoders NOI 的模拟赛。 - 下午放 @ppllxx_9G 的每日一歌《Good Time》,上完体育课后回来讲题。从机房坐电梯到一楼后被一楼某工作人员
“这电梯都十多年了,还坐这么多人也不怕电梯再坠机了”,到操场后体育老师又让高二的过去集合,进行一番交涉后他们就再没管,然后体育老师就带着其他学生去旁边正在建的体育馆了,之前都以为建的体育馆不归 管呢。 - 下午
让查一下 的结果。貌似在机房的高一只有 @wang54321 , @Abnormal123 和 @jijidawang 过了。 - 晚上 $huge 说明天讲贪心、博弈、构造,让我们先提前把题看一眼。
做题纪要
P684. 切割蛋糕(cake)
luogu P3045 [USACO12FEB] Cow Coupons G
P686. 有根树(tree)
1.3
闲话
-
上午放多校讲贪心、博弈、构造的视频,没放完的部分调到了下午放。
-
下午放 @ppllxx_9G 的每日一歌《One Last Kiss》。放视频的时候被
放课的时候不要不耐烦,知识点已经基本学完了,去哪里讲课基本都是杂题选讲。放完视频 让写一下贪心、博弈、构造的专题。 -
下午
让查一下 的结果。机房内只有 @5k_sync_closer 过了。
做题纪要
UVA11174 Stand in a Line
-
树上拓扑序计数。
点击查看代码
const ll p=1000000007; struct node { ll nxt,to; }e[40010]; ll head[40010],din[40010],siz[40010],cnt=0,ans; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs(ll x) { siz[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { dfs(e[i].to); siz[x]+=siz[e[i].to]; } ans=ans*x%p*qpow(siz[x],p-2,p)%p; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,n,m,u,v,i,j; cin>>t; for(j=1;j<=t;j++) { cnt=0; ans=1; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(din,0,sizeof(din)); cin>>n>>m; for(i=1;i<=m;i++) { cin>>v>>u; add(u,v); din[v]++; } for(i=1;i<=n;i++) { if(din[i]==0) { dfs(i); } } cout<<ans<<endl; } return 0; }
luogu P5689 [CSP-S2019 江西] 多叉堆
-
动态维护子树内部所有点
的乘积。点击查看代码
const ll p=1000000007; ll jc[300010]; ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } struct DSU { ll fa[500010],siz[500010],f[500010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; siz[i]=f[i]=1; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } void merge(ll x,ll y) { x=find(x); y=find(y); if(x!=y) { fa[x]=y; f[y]=f[y]*qpow(siz[y],p-2,p)%p*f[x]%p*(siz[x]+siz[y])%p; siz[y]+=siz[x]; } } }D; int main() { //#define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,pd,u,v,ans=0,i; cin>>n>>m; D.init(n); jc[0]=1; for(i=1;i<=n;i++) { jc[i]=jc[i-1]*i%p; } for(i=1;i<=m;i++) { cin>>pd>>u; u=(u+ans)%n+1; if(pd==1) { cin>>v; v=(v+ans)%n+1; D.merge(u,v); } else { u=D.find(u); ans=jc[D.siz[u]]*qpow(D.f[u],p-2,p)%p; cout<<ans<<endl; } } return 0; }
[ABC160F] Distributing Integers
-
换根维护以每个点为根节点时各节点
的乘积。点击查看代码
const ll p=1000000007; struct node { ll nxt,to; }e[400010]; ll head[400010],siz[400010],f[400010],n,cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } ll qpow(ll a,ll b,ll p) { ll ans=1; while(b) { if(b&1) { ans=ans*a%p; } b>>=1; a=a*a%p; } return ans; } void dfs(ll x,ll fa) { siz[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); siz[x]+=siz[e[i].to]; } } f[1]=f[1]*x%p*qpow(siz[x],p-2,p)%p; } void reroot(ll x,ll fa) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { f[e[i].to]=f[x]*qpow(n-siz[e[i].to],p-2,p)%p*siz[e[i].to]%p; reroot(e[i].to,x); } } } int main() { //#define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll u,v,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } f[1]=1; dfs(1,0); reroot(1,0); for(i=1;i<=n;i++) { cout<<f[i]<<endl; } return 0; }
luogu P4630 [APIO2018] 铁人两项
luogu P4606 [SDOI2018] 战略游戏
-
在圆方树上建立
的虚树,上面的圆点数量即为所求。 -
具体实现时可以将圆点权值设为
,方点权值设为 从而将点权转化成边权,接着套用虚树关于边数的结论即可。 -
特殊处理第一个点和最后一个点的
是圆点的情况。点击查看代码
struct node { int nxt,to; }e[400010]; int head[100010],dfn[200010],low[100010],dis[200010],fa[200010],siz[200010],son[200010],top[200010],dep[200010],a[200010],v_dcc=0,tot=0,cnt=0,n; stack<int>s; vector<int>g[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } bool cmp(int a,int b) { return dfn[a]<dfn[b]; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { v_dcc++; g[x].push_back(v_dcc); g[v_dcc].push_back(x); int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); g[tmp].push_back(v_dcc); g[v_dcc].push_back(tmp); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs1(int x,int father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; dis[x]=dis[father]+(x<=n); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=father) { dfs1(g[x][i],x); siz[x]+=siz[g[x][i]]; son[x]=(siz[g[x][i]]>siz[son[x]])?g[x][i]:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa[x]&&g[x][i]!=son[x]) { dfs2(g[x][i],g[x][i]); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } int get_dis(int x,int y) { return dis[x]+dis[y]-2*dis[lca(x,y)]; } int main() { //#define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,m,q,u,v,ans=0,i,j,k,h; cin>>t; for(h=1;h<=t;h++) { for(i=1;i<=v_dcc;i++) { g[i].clear(); } cnt=tot=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(son,0,sizeof(son)); cin>>n>>m; v_dcc=n; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } tarjan(1); tot=0; dfs1(1,0); dfs2(1,1); cin>>q; for(i=1;i<=q;i++) { cin>>k; ans=0; for(j=1;j<=k;j++) { cin>>a[j]; } sort(a+1,a+1+k,cmp); for(j=1;j<=k-1;j++) { ans+=get_dis(a[j],a[j+1]); } ans+=get_dis(a[k],a[1]); cout<<ans/2-k+(lca(a[1],a[k])<=n)<<endl; } } return 0; }
luogu P10517 国土规划
-
转化为求有多少个点
使得存在 ,而 不连通。set
动态维护 的集合即可。点击查看代码
struct node { int nxt,to; }e[400010]; int head[100010],dfn[200010],pos[200010],low[100010],fa[200010],siz[200010],dep[200010],dis[200010],son[200010],top[200010],a[100010],tot=0,v_dcc=0,cnt=0,ans=0,n; stack<int>s; vector<int>g[200010]; set<int>st; set<int>::iterator pre,nxt; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; s.push(x); for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { tarjan(e[i].to); low[x]=min(low[x],low[e[i].to]); if(dfn[x]==low[e[i].to]) { v_dcc++; g[x].push_back(v_dcc); g[v_dcc].push_back(x); int tmp=0; while(e[i].to!=tmp) { tmp=s.top(); s.pop(); g[tmp].push_back(v_dcc); g[v_dcc].push_back(tmp); } } } else { low[x]=min(low[x],dfn[e[i].to]); } } } void dfs1(int x,int father) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; dis[x]=dis[father]+(x<=n); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=father) { dfs1(g[x][i],x); siz[x]+=siz[g[x][i]]; son[x]=(siz[g[x][i]]>siz[son[x]])?g[x][i]:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa[x]&&g[x][i]!=son[x]) { dfs2(g[x][i],g[x][i]); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } int get_dis(int x,int y) { return dis[x]+dis[y]-2*dis[lca(x,y)]; } void update(int x,int pd) { nxt=st.upper_bound(x); pre=--st.lower_bound(x); if(*nxt!=0x7f7f7f7f) { if(*pre!=-0x7f7f7f7f) { ans-=pd*get_dis(pos[*pre],pos[*nxt]); ans+=pd*get_dis(pos[*pre],pos[x]); } ans+=pd*get_dis(pos[x],pos[*nxt]); } else { if(*pre!=-0x7f7f7f7f) { ans+=pd*get_dis(pos[*pre],pos[x]); } } if(pd==1) { st.insert(x); } else { st.erase(st.find(x)); } } int ask() { if(st.size()<=3) { return 0; } else { pre=st.upper_bound(-0x7f7f7f7f); nxt=--st.lower_bound(0x7f7f7f7f); return (ans+get_dis(pos[*pre],pos[*nxt]))/2-(st.size()-2)+(lca(pos[*pre],pos[*nxt])<=n); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,q,u,v,i; cin>>n>>m>>q; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } v_dcc=n; tarjan(1); tot=0; dfs1(1,0); dfs2(1,1); st.insert(-0x7f7f7f7f); st.insert(0x7f7f7f7f); for(i=1;i<=q;i++) { cin>>u; a[u]^=1; update(dfn[u],((a[u]==1)?1:-1)); cout<<n-(st.size()-2)-ask()<<endl; } return 0; }
[AGC052A] Long Common Subsequence
-
多倍经验: bjtuOJ 2064. Dream
-
容易得到两种比较简单的构造方案。
- 方案一:
个 , 个 , 个 。- 证明
下标从 开始。- 设
表示 中第 个 的位置, 表示 中第 个 的位置。 - 考虑有
个 一定能在 中匹配完,此时满足 后面有 个 ,而 个 可以接在后面,即在 中匹配完,此时 中一定至少存在一个 能够进行匹配。
- 证明
- 方案二:
个 , 个 , 个 。- 证明方法同方案一。
点击查看代码
string s; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n>>s>>s>>s; for(i=1;i<=2*n;i++) { cout<<(i>n); } cout<<0<<endl; } return 0; }
- 方案一:
luogu P2495 [SDOI2011] 消耗战
-
定义资源丰富的岛屿为关键点。
-
设
表示 不与子树内部任何一个关键点连接的最小代价,状态转移方程为 ,其中 。每组询问都重新做一遍树形 的时间复杂度为 ,无法接受。 -
观察到
很小,即存在较多重复的转移。考虑建出虚树。 -
虚树板子。
- 任意两个节点的
也是用来保存重要信息的,故虚树中不一定只有关键点。 - 虚树中祖先后代的关系不会发生改变,故在满足前面的限制条件下可以任意添加节点。
- 为了方便,我们通常把根节点也加入到虚树中,容易发现并不会影响答案。
- 常见的构造虚树有两种方法。
-
第一种方法:二次排序加
连边。- 根据
序的性质,先将所有关键点按照 序排序,然后将相邻两个点 的 也加入序列 ,此时序列 中已经包含了所求虚树的所有点,但是可能会有重复,不妨再对 进行一次排序并去重。 - 最后,将相邻两个点
的 向 连边。这样我们就不重不漏地构建了我们所需的虚树。- 容易知道
上不会有其他关键点。 - 因
一定是根节点,所以可以不对其做操作。
- 容易知道
- 在忽略
的时间复杂度情况下,时间复杂度为 ,常数较大但仍可接受,其中 为关键点数量。
点击查看代码
bool cmp(int a,int b) { return dfn[a]<dfn[b]; } struct Virtual_Tree { vector<ll>g[250010],h; void build(ll len) { h.clear(); for(ll i=1;i<=len;i++) { h.push_back(a[i]); g[a[i]].clear(); } sort(h.begin(),h.end(),cmp); for(ll i=0;i+1<len;i++) { h.push_back(lca(h[i],h[i+1])); g[lca(h[i],h[i+1])].clear(); } h.push_back(1); g[1].clear(); sort(h.begin(),h.end(),cmp); h.erase(unique(h.begin(),h.end()),h.end()); for(ll i=0;i+1<h.size();i++) { g[lca(h[i],h[i+1])].push_back(h[i+1]); } } }V;
- 根据
-
第二种方法:使用单调栈维护虚树上的链。
- 先将所有点按照
序排序,然后使用单调栈维护虚树上从根节点到当前节点这条链(自栈底到栈顶 序单调递增),即栈内某个节点的父亲就是它在栈中下面的那个节点。 - 先将
加入到栈中,然后添加关键点。- 若当前节点与栈顶节点的
就是栈顶节点,说明在同一条链上,可以直接把当前节点加入栈。 - 否则,不断弹栈直至存在当前节点与栈顶节点的
是栈顶节点,但是若 从始至终都未加入栈中则需要将其加入(可以通过 序的大小关系判断)。同时在弹栈的过程中向父亲节点连边。
- 若当前节点与栈顶节点的
- 最后将栈中元素顺次连边即可。
- 在忽略
的时间复杂度情况下,时间复杂度为 ,常数较小。
点击查看代码
bool cmp(int a,int b) { return dfn[a]<dfn[b]; } struct Virtual_Tree { vector<int>g[250010]; stack<int>s; void build(int len) { sort(a+1,a+1+len,cmp); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(int i=1;i<=len;i++) { int rt=lca(a[i],s.top()); while(s.top()!=rt) { int tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=1) { int tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } }V;
- 先将所有点按照
-
- 任意两个节点的
-
本题中虚树上的边权为两点间路径的边权最小值,倍增或树剖加
表维护。点击查看第一种方法的代码
struct node { ll nxt,to,w; }e[500010]; ll head[250010],fa[250010][20],dis[250010][20],dep[250010],dfn[250010],f[250010],a[250010],vis[250010],cnt=0,tot=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll father) { tot++; dfn[x]=tot; fa[x][0]=father; dep[x]=dep[father]+1; for(ll i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; dis[x][i]=min(dis[x][i-1],dis[fa[x][i-1]][i-1]); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dis[e[i].to][0]=e[i].w; dfs(e[i].to,x); } } } ll lca(ll x,ll y) { if(dep[x]>dep[y]) { swap(x,y); } for(ll i=18;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { y=fa[y][i]; } } if(x==y) { return x; } for(ll i=18;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } ll get_dis(ll x,ll rt) { ll ans=0x7f7f7f7f7f7f7f7f; for(ll i=20;i>=0;i--) { if(dep[rt]+(1<<i)<=dep[x]) { ans=min(ans,dis[x][i]); x=fa[x][i]; } } return ans; } bool cmp(ll a,ll b) { return dfn[a]<dfn[b]; } struct Virtual_Tree { vector<ll>g[250010],h; void build(ll len) { h.clear(); for(ll i=1;i<=len;i++) { h.push_back(a[i]); g[a[i]].clear(); } sort(h.begin(),h.end(),cmp); for(ll i=0;i+1<len;i++) { h.push_back(lca(h[i],h[i+1])); g[lca(h[i],h[i+1])].clear(); } h.push_back(1); g[1].clear(); sort(h.begin(),h.end(),cmp); h.erase(unique(h.begin(),h.end()),h.end()); for(ll i=0;i+1<h.size();i++) { g[lca(h[i],h[i+1])].push_back(h[i+1]); } } void dfs(ll x) { f[x]=0; for(ll i=0;i<g[x].size();i++) { dfs(g[x][i]); f[x]+=((vis[g[x][i]]==1)?get_dis(g[x][i],x):min(f[g[x][i]],get_dis(g[x][i],x))); } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,q,u,v,w,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } memset(dis,0x3f,sizeof(dis)); dfs(1,0); cin>>q; for(j=1;j<=q;j++) { cin>>m; for(i=1;i<=m;i++) { cin>>a[i]; vis[a[i]]=1; } V.build(m); V.dfs(1); cout<<f[1]<<endl; for(i=1;i<=m;i++) { vis[a[i]]=0; } } return 0; }
点击查看第二种方法的代码
struct node { ll nxt,to,w; }e[500010]; ll head[250010],fa[250010][20],dis[250010][20],dep[250010],dfn[250010],f[250010],a[250010],vis[250010],cnt=0,tot=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs(ll x,ll father) { tot++; dfn[x]=tot; fa[x][0]=father; dep[x]=dep[father]+1; for(ll i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; dis[x][i]=min(dis[x][i-1],dis[fa[x][i-1]][i-1]); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dis[e[i].to][0]=e[i].w; dfs(e[i].to,x); } } } ll lca(ll x,ll y) { if(dep[x]>dep[y]) { swap(x,y); } for(ll i=18;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { y=fa[y][i]; } } if(x==y) { return x; } for(ll i=18;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } ll get_dis(ll x,ll rt) { ll ans=0x7f7f7f7f7f7f7f7f; for(ll i=20;i>=0;i--) { if(dep[rt]+(1<<i)<=dep[x]) { ans=min(ans,dis[x][i]); x=fa[x][i]; } } return ans; } bool cmp(ll a,ll b) { return dfn[a]<dfn[b]; } struct Virtual_Tree { vector<ll>g[250010]; stack<ll>s; void build(ll len) { sort(a+1,a+1+len,cmp); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(ll i=1;i<=len;i++) { ll rt=lca(a[i],s.top()); while(s.top()!=rt) { ll tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=1) { ll tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(ll x) { f[x]=0; for(ll i=0;i<g[x].size();i++) { dfs(g[x][i]); f[x]+=((vis[g[x][i]]==1)?get_dis(g[x][i],x):min(f[g[x][i]],get_dis(g[x][i],x))); } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,q,u,v,w,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } memset(dis,0x3f,sizeof(dis)); dfs(1,0); cin>>q; for(j=1;j<=q;j++) { cin>>m; for(i=1;i<=m;i++) { cin>>a[i]; vis[a[i]]=1; } V.build(m); V.dfs(1); cout<<f[1]<<endl; for(i=1;i<=m;i++) { vis[a[i]]=0; } } return 0; }
luogu P4103 [HEOI2014] 大工程
-
建出虚树后三问都可以简单树形
处理出来。点击查看代码
struct node { ll nxt,to; }e[2000010]; ll head[1000010],fa[1000010],siz[1000010],dep[1000010],son[1000010],top[1000010],dfn[1000010],a[1000010],f[3][1000010],ans[3],vis[1000010],tot=0,cnt=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(ll x,ll father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(ll x,ll id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } ll lca(ll u,ll v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } ll get_dis(ll x,ll rt) { return dep[x]-dep[rt]; } bool cmp(ll a,ll b) { return dfn[a]<dfn[b]; } struct Vitrual_Tree { vector<ll>g[1000010]; stack<ll>s; void build(ll len) { sort(a+1,a+1+len,cmp); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(ll i=1;i<=len;i++) { ll rt=lca(a[i],s.top()); while(s.top()!=rt) { ll tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=1) { ll tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(ll x,ll n) { siz[x]=vis[x]; f[2][x]=0; f[1][x]=(vis[x]==1)?0:0x7f7f7f7f; for(ll i=0;i<g[x].size();i++) { dfs(g[x][i],n); ans[0]+=siz[g[x][i]]*(n-siz[g[x][i]])*get_dis(g[x][i],x); if(siz[x]>=1&&siz[g[x][i]]>=1) { ans[1]=min(ans[1],f[1][x]+f[1][g[x][i]]+get_dis(g[x][i],x)); ans[2]=max(ans[2],f[2][x]+f[2][g[x][i]]+get_dis(g[x][i],x)); } siz[x]+=siz[g[x][i]]; f[1][x]=min(f[1][x],f[1][g[x][i]]+get_dis(g[x][i],x)); f[2][x]=max(f[2][x],f[2][g[x][i]]+get_dis(g[x][i],x)); } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,q,u,v,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); cin>>q; for(j=1;j<=q;j++) { cin>>m; for(i=1;i<=m;i++) { cin>>a[i]; vis[a[i]]=1; } V.build(m); ans[0]=ans[2]=0; ans[1]=0x7f7f7f7f; V.dfs(1,m); cout<<ans[0]<<" "<<ans[1]<<" "<<ans[2]<<endl; for(i=1;i<=m;i++) { vis[a[i]]=0; } } return 0; }
[ABC359G] Sum of Tree Distance
-
对所有颜色建虚树后树形
。点击查看代码
struct node { ll nxt,to; }e[400010]; ll head[200010],fa[200010],siz[200010],dep[200010],son[200010],top[200010],dfn[200010],a[200010],cnt=0,tot=0,ans=0; vector<ll>col[200010]; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(ll x,ll father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(ll x,ll id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } ll lca(ll u,ll v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } ll get_dis(ll x,ll rt) { return dep[x]-dep[rt]; } bool cmp(ll a,ll b) { return dfn[a]<dfn[b]; } struct Vitrual_Tree { vector<ll>g[200010]; stack<ll>s; void build(ll id) { sort(col[id].begin(),col[id].end(),cmp); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(ll i=0;i<col[id].size();i++) { ll rt=lca(col[id][i],s.top()); while(s.top()!=rt) { ll tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(col[id][i]); g[col[id][i]].clear(); } while(s.top()!=1) { ll tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(ll x,ll id) { siz[x]=(a[x]==id); for(ll i=0;i<g[x].size();i++) { dfs(g[x][i],id); ans+=siz[g[x][i]]*(col[id].size()-siz[g[x][i]])*get_dis(g[x][i],x); siz[x]+=siz[g[x][i]]; } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,u,v,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } for(i=1;i<=n;i++) { cin>>a[i]; col[a[i]].push_back(i); } dfs1(1,0); dfs2(1,1); for(i=1;i<=n;i++) { V.build(i); V.dfs(1,i); } cout<<ans<<endl; return 0; }
CF613D Kingdom and its Cities
-
建出虚树后贪心。
- 若
是关键点,则有关键点的子树都要与其断开。 - 若
不是关键点,但有 个子树内含有关键点则将 断开,否则不需要处理。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[100010],fa[100010],siz[100010],dep[100010],son[100010],top[100010],dfn[100010],vis[100010],a[100010],cnt=0,tot=0,ans; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x,int father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } bool cmp(int a,int b) { return dfn[a]<dfn[b]; } struct Vitrual_Tree { vector<int>g[100010]; stack<int>s; void build(int len) { sort(a+1,a+1+len,cmp); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(int i=1;i<=len;i++) { int rt=lca(a[i],s.top()); while(s.top()!=rt) { int tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=1) { int tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(int x) { siz[x]=vis[x]; if(siz[x]==1) { for(int i=0;i<g[x].size();i++) { dfs(g[x][i]); ans+=(siz[g[x][i]]!=0); } } else { for(int i=0;i<g[x].size();i++) { dfs(g[x][i]); siz[x]+=siz[g[x][i]]; } if(siz[x]>=2) { ans++; siz[x]=0; } } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,u,v,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); cin>>q; for(j=1;j<=q;j++) { cin>>m; for(i=1;i<=m;i++) { cin>>a[i]; vis[a[i]]=1; } ans=0; for(i=1;i<=m;i++) { if(vis[fa[a[i]]]==1) { ans=-1; break; } } if(ans==-1) { cout<<ans<<endl; } else { V.build(m); V.dfs(1); cout<<ans<<endl; } for(i=1;i<=m;i++) { vis[a[i]]=0; } } return 0; }
- 若
luogu P5327 [ZJOI2019] 语言
-
转化为求对于每个数
,所能到达的点集 大小(记得除去自己)。 -
显然是若干个包含 的 的路径的并,考虑维护虚树大小,有 即为所求。 -
对
做树上差分后线段树合并即可。 -
为减小常数,线段树每个节点可以只维护
,最后的部分用的时候先算。点击查看代码
struct node { ll nxt,to; }e[200010]; ll head[100010],siz[100010],fa[100010],dep[100010],son[100010],top[100010],dfn[100010],rk[100010],tot=0,cnt=0,ans=0,n; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(ll x,ll father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(ll x,ll id) { top[x]=id; tot++; dfn[x]=tot; rk[tot]=x; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } ll lca(ll u,ll v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } struct SMT { ll root[100010],rt_sum; struct SegmentTree { ll ls,rs,cnt,sum,maxx,minn; }tree[100010<<7]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=tree[rt_sum].maxx=tree[rt_sum].minn=0; return rt_sum; } void pushup(ll rt) { tree[rt].minn=((tree[lson(rt)].minn!=0)?tree[lson(rt)].minn:tree[rson(rt)].minn); tree[rt].maxx=((tree[rson(rt)].maxx!=0)?tree[rson(rt)].maxx:tree[lson(rt)].maxx); tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; if(tree[lson(rt)].maxx!=0&&tree[rson(rt)].minn!=0) { tree[rt].sum-=dep[lca(rk[tree[lson(rt)].maxx],rk[tree[rson(rt)].minn])]; } } void update(ll &rt,ll l,ll r,ll pos,ll val) { rt=(rt==0)?build_rt():rt; if(l==r) { tree[rt].cnt+=val; tree[rt].minn=tree[rt].maxx=(tree[rt].cnt>=1)*l; tree[rt].sum=(tree[rt].cnt>=1)*dep[rk[l]]; return; } ll mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,val); } else { update(rson(rt),mid+1,r,pos,val); } pushup(rt); } ll merge(ll rt1,ll rt2,ll l,ll r) { if(rt1==0||rt2==0) { return rt1+rt2; } if(l==r) { tree[rt1].cnt+=tree[rt2].cnt; tree[rt1].minn=tree[rt1].maxx=(tree[rt1].cnt>=1)*l; tree[rt1].sum=(tree[rt1].cnt>=1)*dep[rk[l]]; return rt1; } ll mid=(l+r)/2; lson(rt1)=merge(lson(rt1),lson(rt2),l,mid); rson(rt1)=merge(rson(rt1),rson(rt2),mid+1,r); pushup(rt1); return rt1; } ll query(ll rt) { return (tree[rt].minn!=0&&tree[rt].maxx!=0)?tree[rt].sum-dep[lca(rk[tree[rt].minn],rk[tree[rt].maxx])]:tree[rt].sum; } }T; void dfs(ll x) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { dfs(e[i].to); T.root[x]=T.merge(T.root[x],T.root[e[i].to],1,n); } } ans+=T.query(T.root[x]); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll m,u,v,rt,i; cin>>n>>m; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); for(i=1;i<=m;i++) { cin>>u>>v; rt=lca(u,v); T.update(T.root[u],1,n,dfn[u],1); T.update(T.root[v],1,n,dfn[u],1); T.update(T.root[rt],1,n,dfn[u],-1); T.update(T.root[u],1,n,dfn[v],1); T.update(T.root[v],1,n,dfn[v],1); T.update(T.root[rt],1,n,dfn[v],-1); if(fa[rt]!=0) { T.update(T.root[fa[rt]],1,n,dfn[u],-1); T.update(T.root[fa[rt]],1,n,dfn[v],-1); } } dfs(1); cout<<ans/2<<endl; return 0; }
1.4
闲话
- 上午
打 accoders NOI 的模拟赛。 - 下午放 @wang54321 的每日一歌《Beautiful World》。
- 晚上
说今天的题貌似有点困难,但并不影响高手的发挥,让我们赶快弥补差距。然后讲题。
做题纪要
luogu P3233 [HNOI2014] 世界树
-
建出虚树后虚树上的点的影响是容易统计的,换根
维护,写法类似 luogu P11324 【MX-S7-T2】「SMOI-R2」Speaker | luogu P9432 [NAPC-#1] rStage5 - Hard Conveyors 。 -
对于属于虚树相邻两点
间的点及其子树内部的点,若 到达的关键点不同,其到达的关键点存在一个分割点使得分成的两个部分到达的关键点各与 的关键点相同,倍增处理即可,注意分讨中间节点的选择;否则,都需要到达 所在的关键点,扣掉在虚树上儿子的子树大小加入贡献即可。 -
具体实现时,可以把虚树上的点的贡献和不在虚树上的点的贡献合并到一起来写,以此避免额外的
分讨。点击查看代码
struct node { int nxt,to; }e[600010]; int head[300010],fa[300010][20],siz[300010],sum[300010],dep[300010],dfn[300010],vis[300010],ans[300010],tot=0,cnt=0; pair<int,int>a[300010],f[300010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int father) { tot++; dfn[x]=tot; siz[x]=1; fa[x][0]=father; dep[x]=dep[father]+1; for(int i=1;(1<<i)<=dep[x];i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs(e[i].to,x); siz[x]+=siz[e[i].to]; } } } int lca(int x,int y) { if(dep[x]>dep[y]) { swap(x,y); } for(int i=19;i>=0;i--) { if(dep[x]+(1<<i)<=dep[y]) { y=fa[y][i]; } } if(x==y) { return x; } for(int i=19;i>=0;i--) { if(fa[x][i]!=fa[y][i]) { x=fa[x][i]; y=fa[y][i]; } } return fa[x][0]; } int get_dis(int x,int rt) { return dep[x]-dep[rt]; } int kth_fa(int x,int k) { int rt=x; for(int i=19;i>=0;i--) { if(dep[x]-dep[fa[rt][i]]<=k) { rt=fa[rt][i]; } } return rt; } bool cmp1(pair<int,int> a,pair<int,int> b) { return dfn[a.first]<dfn[b.first]; } bool cmp2(pair<int,int> a,pair<int,int> b) { return a.second<b.second; } struct Vitrual_Tree { vector<int>g[300010]; stack<int>s; void build(int len) { sort(a+1,a+1+len,cmp1); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(int i=1;i<=len;i++) { int rt=lca(a[i].first,s.top()); while(s.top()!=rt) { int tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i].first); g[a[i].first].clear(); } while(s.top()!=1) { int tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(int x) { f[x]=(vis[x]==1)?make_pair(x,0):make_pair(0,0x7f7f7f7f); sum[x]=siz[x]; for(int i=0;i<g[x].size();i++) { int y=g[x][i]; dfs(y); if((f[y].second+get_dis(y,x)<f[x].second)||(f[y].second+get_dis(y,x)==f[x].second&&f[y].first<f[x].first)) { f[x]=make_pair(f[y].first,f[y].second+get_dis(y,x)); } } } void reroot(int x) { for(int i=0;i<g[x].size();i++) { int y=g[x][i]; if((f[x].second+get_dis(y,x)<f[y].second)||(f[x].second+get_dis(y,x)==f[y].second&&f[x].first<f[y].first)) { f[y]=make_pair(f[x].first,f[x].second+get_dis(y,x)); } reroot(y); if(f[x].first==f[y].first) { sum[x]-=siz[y]; } else { int rt=kth_fa(y,(f[x].second+f[y].second+get_dis(y,x)-(f[x].first<f[y].first))/2-f[y].second); sum[x]-=siz[rt]; sum[y]+=siz[rt]-siz[y]; } ans[f[y].first]+=sum[y]; } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,u,v,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); cin>>q; for(j=1;j<=q;j++) { cin>>m; for(i=1;i<=m;i++) { cin>>a[i].first; a[i].second=i; vis[a[i].first]=1; } V.build(m); V.dfs(1); V.reroot(1); ans[f[1].first]+=sum[1]; sort(a+1,a+1+m,cmp2); for(i=1;i<=m;i++) { cout<<ans[a[i].first]<<" "; vis[a[i].first]=ans[a[i].first]=0; } cout<<endl; } return 0; }
luogu P5680 [GZOI2017] 共享单车
-
回收路线树即最短路树。
-
对回收区域建虚树(边权为原最短路树上两点间边权总和)后做树形
即可。点击查看代码
struct node { int nxt,to,w; }e[200010]; int head[50010],dis[50010],vis[50010],fa[50010],siz[50010],son[50010],dep[50010],top[50010],dfn[50010],a[50010],f[50010],tot=0,cnt=0; pair<int,int>pre[50010]; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dijkstra(int k) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<int,int> >q; q.push(make_pair(0,k)); dis[k]=0; while(q.empty()==0) { int x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(dis[e[i].to]==0x3f3f3f3f||dis[e[i].to]>dis[x]+e[i].w) { dis[e[i].to]=dis[x]+e[i].w; pre[e[i].to]=make_pair(x,e[i].w); q.push(make_pair(-dis[e[i].to],e[i].to)); } else { if(dis[e[i].to]==dis[x]+e[i].w&&pre[e[i].to].first>x) { pre[e[i].to]=make_pair(x,e[i].w); } } } } } } void dfs1(int x,int father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { dis[e[i].to]=dis[x]+e[i].w; dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } int get_dis(int x,int rt) { return dis[x]-dis[rt]; } bool cmp(int a,int b) { return dfn[a]<dfn[b]; } struct Vitrual_Tree { vector<int>g[50010]; stack<int>s; void build(int len,int k) { sort(a+1,a+1+len,cmp); while(s.empty()==0) { s.pop(); } s.push(k); g[k].clear(); for(int i=1;i<=len;i++) { int rt=lca(a[i],s.top()); while(s.top()!=rt) { int tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=k) { int tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); } } void dfs(int x) { f[x]=0; for(int i=0;i<g[x].size();i++) { dfs(g[x][i]); f[x]+=(vis[g[x][i]]==1)?get_dis(g[x][i],x):min(get_dis(g[x][i],x),f[g[x][i]]); } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,k,u,v,w,i,j; cin>>n>>m>>k>>q; for(i=1;i<=m;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dijkstra(k); cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); for(i=1;i<=n;i++) { if(i!=k) { add(pre[i].first,i,pre[i].second); } } dfs1(k,0); dfs2(k,k); memset(vis,0,sizeof(vis)); for(j=1;j<=q;j++) { cin>>u>>m; if(u==0) { for(i=1;i<=m;i++) { cin>>a[i]; vis[a[i]]^=1; } } else { for(i=1;i<=m;i++) { cin>>a[i]; } V.build(m,k); V.dfs(k); cout<<((f[k]==0)?-1:f[k])<<endl; } } return 0; }
P688. aw
CF1111E Tree
-
严格的祖先关系使得我们不能直接树形
分讨一条边连接的两个点所属关系转移。 -
建虚树后处理出新的
序。注意强制插入 节点且建双向边。 -
将
按照 序排序,此时很容易且转移没有后效性。 -
设
表示前 个节点分成恰好 组的方案数,因祖先节点内部彼此也不能在同一组,故有 ,其中 表示 中有多少个是 的祖先节点。单点加,链求和是容易处理的。 -
进一步发现虚树是完全没必要建出来的,完全可以通过
排序以代替原 序排序。点击查看代码
const ll p=1000000007; struct node { ll nxt,to; }e[200010]; ll head[100010],fa[100010],siz[100010],son[100010],dep[100010],top[100010],dfn[100010],d[100010],f[2][310],a[100010],cnt=0,tot=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(ll x,ll father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(ll x,ll id) { tot++; dfn[x]=tot; top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } bool cmp(ll a,ll b) { return d[a]<d[b]; } struct BIT { ll c[100010]; ll lowbit(ll x) { return (x&(-x)); } void add(ll n,ll x,ll val) { for(ll i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } ll getsum(ll x) { ll ans=0; for(ll i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } ll query(ll l,ll r) { return getsum(r)-getsum(l-1); } }B; ll query(ll u,ll v) { ll ans=0; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { ans+=B.query(dfn[top[u]],dfn[u]); u=fa[top[u]]; } else { ans+=B.query(dfn[top[v]],dfn[v]); v=fa[top[v]]; } } if(dep[u]<dep[v]) { ans+=B.query(dfn[u],dfn[v]); } else { ans+=B.query(dfn[v],dfn[u]); } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,q,k,m,r,u,v,ans,i,j,h; cin>>n>>q; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); for(h=1;h<=q;h++) { cin>>k>>m>>r; ans=0; for(i=1;i<=k;i++) { cin>>a[i]; B.add(n,dfn[a[i]],1); } for(i=1;i<=k;i++) { d[a[i]]=query(a[i],r)-1; } sort(a+1,a+1+k,cmp); memset(f,0,sizeof(f)); f[1][1]=1; for(i=2;i<=k;i++) { for(j=1;j<=min(i,m);j++) { f[i&1][j]=(j-d[a[i]]>=1)*((j-d[a[i]])*f[(i-1)&1][j]%p+f[(i-1)&1][j-1])%p; } } for(i=1;i<=m;i++) { ans=(ans+f[k&1][i])%p; } cout<<ans<<endl; for(i=1;i<=k;i++) { B.add(n,dfn[a[i]],-1); } } return 0; }
P689. awa
1.5
闲话
- 上午
让写贪心专题。快 的时候, 往 上放了篇读物 全网都在扒的DeepSeek团队,是清北应届生撑起一片天 ,让我们自己找“亮点”,找到关键词“赵成钢”后,跟我们讲他是 年的银牌,希望我们也能在 年拿到金牌/银牌,而且他现在团队里的一个同学是东师附的(也和我们一起参加多校联训),提到以后我们的创业伙伴很有可能就在身边的同学中。 - 下午放 @Qyun 的每日一歌《覆灭重生》和《晓》。放完后
说现在我们喜欢的东西他是一点都欣赏不了。然后讲题。
做题纪要
CF1320E Treeland and Viruses
-
病毒感染可以写换根
处理,也可以多源 转移过程中记录转移轮数、当前轮数剩余时间、距离即可。 -
注意去重。
点击查看代码
struct node { int col,v,dis,tim; bool operator < (const node &another) const { if(tim!=another.tim) return tim<another.tim; if(col!=another.col) return col<another.col; return dis>another.dis; } }dis[200010]; struct quality { node dis; int id; bool operator < (const quality &another) const { return another.dis<dis; } }; priority_queue<quality>qu; int fa[200010],siz[200010],dep[200010],son[200010],top[200010],dfn[200010],a[400010],ask[200010],vis[200010],tot=0; vector<int>e[200010]; void add(int u,int v) { e[u].push_back(v); } void dfs1(int x,int father) { tot++; dfn[x]=tot; siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=0;i<e[x].size();i++) { if(e[x][i]!=father) { dfs1(e[x][i],x); siz[x]+=siz[e[x][i]]; son[x]=(siz[e[x][i]]>siz[son[x]])?e[x][i]:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=0;i<e[x].size();i++) { if(e[x][i]!=fa[x]&&e[x][i]!=son[x]) { dfs2(e[x][i],e[x][i]); } } } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } int get_dis(int x,int y) { return dep[x]+dep[y]-2*dep[lca(x,y)]; } bool cmp(int a,int b) { return dfn[a]<dfn[b]; } struct Vitrual_Tree { vector<int>g[200010]; stack<int>s; void build(int len) { sort(a+1,a+1+len,cmp); len=unique(a+1,a+1+len)-(a+1); while(s.empty()==0) { s.pop(); } s.push(1); g[1].clear(); for(int i=1;i<=len;i++) { int rt=lca(a[i],s.top()); while(s.top()!=rt) { int tmp=s.top(); s.pop(); if(dfn[s.top()]<dfn[rt]) { s.push(rt); g[rt].clear(); } g[s.top()].push_back(tmp); g[tmp].push_back(s.top()); } s.push(a[i]); g[a[i]].clear(); } while(s.top()!=1) { int tmp=s.top(); s.pop(); g[s.top()].push_back(tmp); g[tmp].push_back(s.top()); } } node work(int x,int y) { int d=get_dis(x,y); node tmp=dis[x]; if(d<=tmp.dis) { tmp.dis-=d; return tmp; } d-=tmp.dis; int t=ceil(1.0*d/dis[x].v); tmp.dis=t*dis[x].v-d; tmp.tim+=t; return tmp; } void dijkstra() { while(qu.empty()==0) { int x=qu.top().id; qu.pop(); if(vis[x]==0) { vis[x]=1; for(int i=0;i<g[x].size();i++) { node tmp=work(x,g[x][i]); if(tmp<dis[g[x][i]]) { dis[g[x][i]]=tmp; qu.push((quality){dis[g[x][i]],g[x][i]}); } } } } } void clear(int x,int fa,int n) { dis[x].col=dis[x].tim=n+1; vis[x]=0; for(int i=0;i<g[x].size();i++) { if(g[x][i]!=fa) { clear(g[x][i],x,n); } } } }V; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,k,u,v,i,j; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } for(i=1;i<=n;i++) { dis[i].col=dis[i].tim=n+1; vis[i]=0; } dfs1(1,0); dfs2(1,1); cin>>q; for(j=1;j<=q;j++) { cin>>k>>m; for(i=1;i<=k;i++) { cin>>a[i]; cin>>dis[a[i]].v; dis[a[i]].col=i; dis[a[i]].tim=dis[a[i]].dis=0; qu.push((quality){dis[a[i]],a[i]}); } for(i=1;i<=m;i++) { cin>>ask[i]; a[i+k]=ask[i]; } V.build(k+m); V.dijkstra(); for(i=1;i<=m;i++) { cout<<dis[ask[i]].col<<" "; } cout<<endl; V.clear(1,0,n); } return 0; }
CF765F Souvenirs
-
莫队套线段树不可接受,回滚莫队的做法太过困难。
-
把询问挂在右端点进行扫描线,维护
表示 的答案。显然有 单调不降。 -
此时
的答案一定是 的一段后缀最小值。指针每次向右移动一次就暴力进行修改的时间复杂度不可接受,但我们可以减少一些不必要的修改。 -
为减少修改次数,我们只更改一些数作为后缀的最小值中最靠右的位置。自右向左考虑每增加一个
的贡献,若 先前的决策点 不如 优则需要对 进行修改,移项后可知此时修改次数为 。 -
具体实现时,线段树从右到左进行修改,若当前区间答案不如已知答案优则直接返回。
-
类似势能线段树的分析方式,时间复杂度目测是
,但好像跑不满(?)。点击查看代码
int a[100010],ans[300010],minn; vector<pair<int,int> >q[300010]; struct SMT { struct SegmentTree { int minn; vector<int>s; }tree[400010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { tree[rt].minn=min(tree[lson(rt)].minn,tree[rson(rt)].minn); } void build(int rt,int l,int r) { for(int i=l;i<=r;i++) { tree[rt].s.push_back(a[i]); } sort(tree[rt].s.begin(),tree[rt].s.end()); tree[rt].minn=0x7f7f7f7f; if(l==r) { return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void update(int rt,int l,int r,int y,int val) { if(r<=y) { vector<int>::iterator it=lower_bound(tree[rt].s.begin(),tree[rt].s.end(),val); if(it!=tree[rt].s.end()) { tree[rt].minn=min(tree[rt].minn,*it-val); } if(it!=tree[rt].s.begin()) { it--; tree[rt].minn=min(tree[rt].minn,val-*it); } if(tree[rt].minn>=minn) { return; } } if(l==r) { minn=min(minn,tree[rt].minn); return; } int mid=(l+r)/2; if(y>mid) { update(rson(rt),mid+1,r,y,val); } update(lson(rt),l,mid,y,val); pushup(rt); minn=min(minn,tree[rt].minn); } int query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt].minn; } int mid=(l+r)/2; if(y<=mid) { return query(lson(rt),l,mid,x,y); } if(x>mid) { return query(rson(rt),mid+1,r,x,y); } return min(query(lson(rt),l,mid,x,y),query(rson(rt),mid+1,r,x,y)); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l,r,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&a[i]); } scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d",&l,&r); q[r].push_back(make_pair(l,i)); } T.build(1,1,n); for(i=2;i<=n;i++) { minn=0x7f7f7f7f; T.update(1,1,n,i-1,a[i]); for(j=0;j<q[i].size();j++) { ans[q[i][j].second]=T.query(1,1,n,q[i][j].first,i); } } for(i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; }
luogu P3899 [湖南集训] 更为厉害
-
当
是 的祖先节点时,有 即为所求。 -
以
序为下标,以 为权值插入到主席树中,查询一段连续的深度权值之和即可。点击查看代码
struct node { ll nxt,to; }e[600010]; ll head[300010],siz[300010],dep[300010],dfn[300010],out[300010],tot=0,cnt=0; vector<ll>pos[300010]; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(ll x,ll fa) { tot++; dfn[x]=tot; siz[x]=1; dep[x]=dep[fa]+1; pos[dep[x]].push_back(x); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); siz[x]+=siz[e[i].to]; } } out[x]=tot; } struct PDS_SMT { ll root[300010],rt_sum=0; struct SegmentTree { ll ls,rs,sum; }tree[300010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0; return rt_sum; } void update(ll pre,ll &rt,ll l,ll r,ll pos,ll val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val; if(l==r) { return; } ll mid=(l+r)/2; if(pos<=mid) { update(lson(pre),lson(rt),l,mid,pos,val); } else { update(rson(pre),rson(rt),mid+1,r,pos,val); } } ll query(ll rt1,ll rt2,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt2].sum-tree[rt1].sum; } ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt1),lson(rt2),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); } return ans; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,u,v,i,j; cin>>n>>m; for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); for(i=1;i<=n;i++) { T.root[i]=T.root[i-1]; for(j=0;j<pos[i].size();j++) { T.update(T.root[i],T.root[i],1,n,dfn[pos[i][j]],siz[pos[i][j]]-1); } } for(i=1;i<=m;i++) { cin>>u>>v; cout<<T.query(T.root[dep[u]],T.root[min(dep[u]+v,n)],1,n,dfn[u],out[u])+min(dep[u]-1,v)*(siz[u]-1)<<endl; } return 0; }
luogu P1484 种树
luogu P4053 [JSOI2007] 建筑抢修
luogu P11328 [NOISG 2022 Finals] Gym Badges
-
多倍经验: luogu P11457 [USACO24DEC] Job Completion G | AT_cf17_final_d Zabuton
-
反悔贪心的做法很正确,但问题在于怎么确定如何确定正确的排序顺序。
-
考虑临项交换,假设
在 之前进行操作且不优,当且仅当 ,得到 。以 升序排序即可。点击查看代码
pair<ll,ll>a[500010]; priority_queue<ll>q; bool cmp(pair<ll,ll>a,pair<ll,ll>b) { return a.first+a.second<b.first+b.second; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,sum=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].second; } for(i=1;i<=n;i++) { cin>>a[i].first; } sort(a+1,a+1+n,cmp); for(i=1;i<=n;i++) { if(sum<=a[i].first) { sum+=a[i].second; ans++; q.push(a[i].second); } else { if(q.empty()==0&&q.top()>=a[i].second) { sum+=a[i].second-q.top(); q.pop(); q.push(a[i].second); } } } cout<<ans<<endl; return 0; }
AT_dp_x Tower
-
按照
升序排序后做背包 。点击查看代码
struct node { ll w,s,v; }a[1010]; ll f[20010]; bool cmp(node a,node b) { return a.w+a.s<b.w+b.s; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].w>>a[i].s>>a[i].v; } sort(a+1,a+1+n,cmp); for(i=1;i<=n;i++) { for(j=a[i].s;j>=0;j--) { f[j+a[i].w]=max(f[j+a[i].w],f[j]+a[i].v); } } for(i=1;i<=20000;i++) { ans=max(ans,f[i]); } cout<<ans<<endl; return 0; }
luogu P3620 [APIO/CTSC2007] 数据备份
- 多倍经验: SP1553 BACKUP - Backup Files | CF958E2 Guard Duty (medium)
- 详见 5.贪心 I luogu P3620 [APIO/CTSC2007] 数据备份 。
[AGC032E] Modulo Pairing
CF335F Buy One, Get One Free
luogu P6400 [COI2008] UMNOZAK
-
多倍经验: SP3314 UMNOZAK - Umnozak
-
设
表示 的位数积,显然有 。又因为 ,可知 。 -
考虑枚举
的值 ,从而得到 。 -
观察到
只可能含有 作为质因子,其指数最大分别为 ,即 的取值不会很多,在 范围内仅有 个数。点击查看打表代码
const ll N=1000000000,p[5]={0,2,3,5,7},c[5]={0,29,18,12,10}; vector<ll>s; ll qpow(ll a,ll b) { ll ans=1; while(b) { if(b&1) { ans=ans*a; } b>>=1; a=a*a; } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll a,b,c,d,e,cnt=0; double val; for(a=0;a<=29;a++) for(b=0;b<=max({18ll,29-a});b++) for(c=0;c<=max({12ll,29-a,18-b});c++) for(d=0;d<=max({10ll,29-a,18-b,12-c});d++) { val=pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d); if(val<=N) { cout<<a<<" "<<b<<" "<<c<<" "<<d<<" "<<qpow(2,a)*qpow(3,b)*qpow(5,c)*qpow(7,d)<<endl; cnt++; } } cout<<cnt<<endl; return 0; }
-
然后做数位
即可,注意前导零的处理。点击查看代码
#define endl '\n' const ll N=1000000000,t[10][5]={{0,0,0,0,0},{0,0,0,0,0},{0,1,0,0,0},{0,0,1,0,0},{0,2,0,0,0},{0,0,0,1,0},{0,1,1,0,0},{0,0,0,0,1},{0,3,0,0,0},{0,0,2,0,0}}; ll a[25],f[25][35][20][15][15]; ll qpow(ll a,ll b) { ll ans=1; while(b) { if(b&1) { ans=ans*a; } b>>=1; a=a*a; } return ans; } ll divide(ll n,ll a[]) { ll len=0; while(n) { len++; a[len]=n%10; n/=10; } return len; } ll dfs(ll pos,ll cnt2,ll cnt3,ll cnt5,ll cnt7,ll limit,ll lead) { if(pos<=0) { return (cnt2==0&&cnt3==0&&cnt5==0&&cnt7==0&&lead==0); } if(f[pos][cnt2][cnt3][cnt5][cnt7]!=-1&&lead==0&&limit==0) { return f[pos][cnt2][cnt3][cnt5][cnt7]; } ll ans=0,maxx=(limit==0)?9:a[pos]; for(ll i=1;i<=maxx;i++) { if(cnt2>=t[i][1]&&cnt3>=t[i][2]&&cnt5>=t[i][3]&&cnt7>=t[i][4]) { ans+=dfs(pos-1,cnt2-t[i][1],cnt3-t[i][2],cnt5-t[i][3],cnt7-t[i][4],(i==maxx)*limit,(i==0)*lead); } } if(lead==1) { ans+=dfs(pos-1,cnt2,cnt3,cnt5,cnt7,0,1); } return (limit==0&&lead==0)?f[pos][cnt2][cnt3][cnt5][cnt7]=ans:ans; } ll ask(ll n,ll cnt2,ll cnt3,ll cnt5,ll cnt7) { ll len=divide(n,a); return dfs(len,cnt2,cnt3,cnt5,cnt7,1,1); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll l,r,ans=0,t,a,b,c,d; double val; cin>>l>>r; memset(f,-1,sizeof(f)); for(a=0;a<=29;a++) for(b=0;b<=max({18ll,29-a});b++) for(c=0;c<=max({12ll,29-a,18-b});c++) for(d=0;d<=max({10ll,29-a,18-b,12-c});d++) { val=pow(2,a)*pow(3,b)*pow(5,c)*pow(7,d); if(val<=N) { t=qpow(2,a)*qpow(3,b)*qpow(5,c)*qpow(7,d); if(r/t>=ceil(1.0*l/t)&&ceil(1.0*l/t)>=1) { ans+=ask(r/t,a,b,c,d)-ask(ceil(1.0*l/t)-1,a,b,c,d); } } } cout<<ans<<endl; return 0; }
牛客周赛 Round 75 A 小红的正整数计数
-
懒得写式子遂直接枚举。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,ans=0,i; cin>>n>>m; for(i=n;i<=m;i++) { ans+=(i%2==0); } cout<<ans<<endl; return 0; }
牛客周赛 Round 75 B 小红的双生串
-
循环结构。
点击查看代码
int cnt[200010]; char s[200010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,ans=0,maxx=0,i; scanf("%s",s+1); n=strlen(s+1); for(i=1;i<=n/2;i++) { cnt[s[i]-'a'+1]++; maxx=max(maxx,cnt[s[i]-'a'+1]); } ans+=n/2-maxx; memset(cnt,0,sizeof(cnt)); maxx=0; for(i=n/2+1;i<=n;i++) { cnt[s[i]-'a'+1]++; maxx=max(maxx,cnt[s[i]-'a'+1]); } ans+=n/2-maxx; cout<<ans<<endl; return 0; }
牛客周赛 Round 75 C 小红的双生排列
-
只能奇偶交错放,乘上排列数即可。
点击查看代码
const ll p=1000000007; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=1,i; cin>>n; if(n%2==0) { for(i=1;i<=n/2;i++) { ans=ans*i%p; } for(i=1;i<=n/2;i++) { ans=ans*i%p; } ans=ans*2%p; } else { for(i=1;i<=n/2;i++) { ans=ans*i%p; } for(i=1;i<=n/2+1;i++) { ans=ans*i%p; } } cout<<ans<<endl; return 0; }
牛客周赛 Round 75 E 小红的双生英雄
-
分讨双生英雄的数量然后分治,
能过是没想到的。点击查看代码
ll f[2][1010][5],g[1010],u[1010],v[1010],w[1010],a[1010],b[1010],vis[1010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,c,ans=0,i,j,k; cin>>n>>c>>m; for(i=1;i<=n;i++) { cin>>a[i]>>b[i]; } for(i=1;i<=m;i++) { cin>>u[i]>>v[i]>>w[i]; } if(m>=0) { for(i=1;i<=n;i++) { for(j=0;j<=c;j++) { for(k=0;k<=4;k++) { f[i&1][j][k]=f[(i-1)&1][j][k]; if(j-a[i]>=0&&k>=1) { f[i&1][j][k]=max(f[i&1][j][k],f[(i-1)&1][j-a[i]][k-1]+b[i]); } } } } for(i=0;i<=c;i++) { for(k=0;k<=4;k++) { ans=max(ans,f[n&1][i][k]); } } } if(m>=1) { for(k=1;k<=m;k++) { vis[u[k]]=vis[v[k]]=1; memset(f,0,sizeof(f)); a[n+1]=a[u[k]]+a[v[k]]; b[n+1]=b[u[k]]+b[v[k]]+w[k]; if(a[n+1]<=c) { ans=max(ans,b[n+1]); } for(i=1;i<=n;i++) { if(vis[i]==0&&a[n+1]+a[i]<=c) { ans=max(ans,b[n+1]+b[i]); for(j=i+1;j<=n;j++) { if(vis[j]==0&&a[n+1]+a[i]+a[j]<=c) { ans=max(ans,b[n+1]+b[i]+b[j]); } } } } vis[u[k]]=vis[v[k]]=0; } } if(m>=2) { for(i=1;i<=m;i++) { for(j=i+1;j<=m;j++) { if(a[u[i]]+a[v[i]]+a[u[j]]+a[v[j]]<=c) { ans=max(ans,b[u[i]]+b[v[i]]+b[u[j]]+b[v[j]]+w[i]+w[j]); } } } } cout<<ans<<endl; return 0; }
SP3273 ORDERSET - Order statistic set
-
平衡树。
点击查看代码
set<int>s; struct BST { const int INF=0x7f7f7f7f; int rt_sum,root; struct FHQ_Treap { int son[2],val,rnd,cnt,siz; }tree[200010]; #define lson(rt) (tree[rt].son[0]) #define rson(rt) (tree[rt].son[1]) BST() { rt_sum=root=0; srand(time(0)); } void pushup(int rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+tree[rt].cnt; } int build_rt(int val) { rt_sum++; lson(rt_sum)=rson(rt_sum)=00; tree[rt_sum].val=val; tree[rt_sum].rnd=rand(); tree[rt_sum].cnt=tree[rt_sum].siz=1; return rt_sum; } void split(int rt,int val,int &ls,int &rs) { if(rt==0) { ls=rs=0; return; } if(tree[rt].val<=val) { ls=rt; split(rson(rt),val,rson(ls),rs); } else { rs=rt; split(lson(rt),val,ls,lson(rs)); } pushup(rt); } int merge(int rt1,int rt2) { if(rt1==0||rt2==0) { return rt1+rt2; } if(tree[rt1].rnd<tree[rt2].rnd) { rson(rt1)=merge(rson(rt1),rt2); pushup(rt1); return rt1; } else { lson(rt2)=merge(rt1,lson(rt2)); pushup(rt2); return rt2; } } void insert(int val) { int ls,rs; split(root,val,ls,rs); root=merge(merge(ls,build_rt(val)),rs); } void del(int val) { int ls,rs,rt; split(root,val,ls,rs); split(ls,val-1,ls,rt); root=merge(merge(ls,merge(lson(rt),rson(rt))),rs); } int query_rk(int val) { int ls,rs,ans; split(root,val-1,ls,rs); ans=tree[ls].siz; root=merge(ls,rs); return ans; } int kth_min(int rt,int k) { if(rt==0) { return 0; } if(k<=tree[lson(rt)].siz) { return kth_min(lson(rt),k); } if(tree[lson(rt)].siz+tree[rt].cnt<k) { return kth_min(rson(rt),k-(tree[lson(rt)].siz+tree[rt].cnt)); } return tree[rt].val; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,x,i; char pd; cin>>m; for(i=1;i<=m;i++) { cin>>pd>>x; if(pd=='I') { if(s.find(x)==s.end()) { s.insert(x); T.insert(x); } } if(pd=='D') { if(s.find(x)!=s.end()) { s.erase(x); T.del(x); } } if(pd=='K') { if(s.size()>=x) { cout<<T.kth_min(T.root,x)<<endl; } else { cout<<"invalid"<<endl; } } if(pd=='C') { cout<<T.query_rk(x)<<endl; } } return 0; }
CF908G New Year and Original Order
-
考虑将每种数码的贡献拆开计算。
-
设
表示 中数码 的位数和,则有 即为所求。 -
考虑枚举
,接着数位 处理出数码 的位数和 的数的个数,然后统计答案。点击查看代码
const ll p=1000000007; ll a[710],f[710][710][10]; char s[710]; ll dfs(ll pos,ll cnt,ll num,ll limit) { if(pos<=0) { return (cnt==0); } if(limit==0&&f[pos][cnt][num]!=-1) { return f[pos][cnt][num]; } ll ans=0,maxx=(limit==0)?9:a[pos]; for(ll i=0;i<=maxx&&cnt-(i>=num)>=0;i++) { ans=(ans+dfs(pos-1,cnt-(i>=num),num,(i==maxx)*limit))%p; } return (limit==0)?f[pos][cnt][num]=ans:ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,i,j,k; cin>>(s+1); n=strlen(s+1); for(i=1;i<=n;i++) { a[i]=s[i]-'0'; } reverse(a+1,a+1+n); memset(f,-1,sizeof(f)); for(i=1;i<=9;i++) { for(j=1,k=1;j<=n;j++,k=(10*k%p+1)%p) { ans=(ans+dfs(n,j,i,1)*k%p)%p; } } cout<<ans<<endl; return 0; }
牛客周赛 Round 75 G 小红的双生树hard
-
自下而上考虑,叶子节点必须与父亲同色,可知仅有两种染色方案,且颜色状态取后后可以得到另一种状态。
-
不妨从上往下钦定颜色,当
与 不同色时, 必须有且仅有一个儿子子树大小为奇数并让这个儿子的颜色和 相同,否则一定无解。 -
先预处理出一种方案在原树修改后的代价,修改时减去原来蓝色变成红色的代价,加上现在红色变成蓝色(即原来是蓝色变成蓝色)的代价,树上差分或者树剖维护即可。
-
注意最后和另一种方案的答案取
。点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],siz[200010],fa[200010],dep[200010],son[200010],top[200010],col[200010],sum[2][200010],d[200010],tot=0,cnt=0,flag=0; char s[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } void dfs3(int x,int fa) { tot+=(col[x]!=(s[x]=='B'));// col=1 为蓝色,否则为红色 sum[0][x]=sum[0][fa]+(col[x]==1&&s[x]=='B');//蓝变蓝 sum[1][x]=sum[1][fa]+(col[x]==0&&s[x]=='B');//蓝变红 for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { if(siz[e[i].to]%2==1&&col[fa]!=col[x]) { if(d[x]!=0) { flag=-1; } else { d[x]=e[i].to; } col[d[x]]=col[x]; } else { col[e[i].to]=col[x]^1; } dfs3(e[i].to,x); } } if(col[x]!=col[fa]&&d[x]==0) { flag=-1; } } int lca(int u,int v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return (dep[u]<dep[v])?u:v; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,rt,tmp,i; cin>>n>>m; scanf("%s",s+1); for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); col[0]=1; dfs3(1,0); for(i=1;i<=m;i++) { cin>>u>>v; rt=lca(u,v); tmp=tot+sum[0][u]+sum[0][v]-sum[0][rt]-sum[0][fa[rt]]-sum[1][u]-sum[1][v]+sum[1][rt]+sum[1][fa[rt]]; cout<<((flag==-1)?flag:min(tmp,n-tmp))<<endl; } return 0; }
1.6
闲话
- 早上起床后听见高二所在的大操场在放《奇迹再现》。候操时得知每周周二按照量化重新划分跑操位置,会在周一进行提醒更新。
- 上午
打学校 的模拟赛。 - 下午放
的每日一歌《凡人歌》,说以后如果没人放他就随机放歌了。 - 晚上讲题。
做题纪要
牛客周赛 Round 75 D 小红的双生数
-
偶数的情况如需撤销则依次向前调整,无法调整则直接输出。
点击查看代码
char s[100010],t[100010]; void work(int pos,int n,int d) { for(int i=pos,j=d;i<=n;i+=2,j^=1) { t[i]=t[i+1]=j+'0'; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,flag,i,j; char c; scanf("%s",s+1); n=strlen(s+1); if(n%2==1) { n++; work(1,n,1); } else { for(i=1;i<=n;i+=2) { c=(s[i]>=s[i+1])?s[i]:s[i]+1; if(c!=t[i-1]) { t[i]=t[i+1]=c; if(s[i]!=s[i+1]) { work(i+2,n,0); break; } } else { if(c!='9') { t[i]=t[i+1]=c+1; work(i+2,n,0); } else { flag=0; for(j=i-2;j>=1&&flag==0;j-=2) { c=t[j]+1+(t[j]+1==t[j-1]); if(c<='9') { flag=1; t[j]=t[j+1]=c; work(j+2,n,0); } } if(flag==0) { n+=2; work(1,n,1); } } break; } } } printf("%s",t+1); return 0; }
luogu P10060 [SNOI2024] 树 V 图
luogu P4097 【模板】李超线段树 / [HEOI2013] Segment
-
李超线段树板子。
- 李超线段树常用于解决插入直线/线段,查询单点极值的问题。
- 和普通线段树一样,李超线段树支持动态开点和可持久化,支持撤销(同并查集一样,拿栈存一下更改的节点信息),但不支持删除。
- 考虑对定义域建李超线段树。李超线段树上每个节点
保存的是其中点 处的优势线段(本题中定义为在满足纵坐标最大的情况下,编号最小的线段)。- 类似标记永久化的思想,保存信息实际上是一个懒惰标记表示区间内每个点都需要被其更新,但不对其进行
pushdown
,仅在询问时进行更新。 - 更新信息时对于全部经过同一个区间内的若干条线段,我们只保存其中的最优线段。
- 类似标记永久化的思想,保存信息实际上是一个懒惰标记表示区间内每个点都需要被其更新,但不对其进行
- 插入
- 将直线/线段写成形如
的形式,特别地,当 时表示不存在斜率。 - 操作等价于在
都加入 这条线段。考虑延续线段树的区间修改操作,先定位到需要修改的线段树区间上。然后尝试更新其内部的优势线段。 - 以本题中定义的优势线段为例,对于
的中点 若先前的线段 不如新加入的线段 优,则交换 。难点在于判断更小的区间中“新”加入的线段 是否会更优。- 对于
及其中点 ,若 ,则不可能存在 使得 且 ,即若 在中点处比 更优,则 比 更优的部分必然被区间某半边完全包含。
- 对于
- 判断两条线段的斜率需要大量分讨,过于麻烦。考虑通过零点存在定理比较端点处的值来决定向哪个方向递归。
- 若左端点处
更优,说明 只有在 才可能继续比 优。 - 若右端点处
更优,说明 只有在 才可能继续比 优。 - 若上面两种情况都不满足,说明
始终不如 优,自然不需要继续修改了。否则由于懒惰标记的存在,上面两种情况只能满足一种,单次拆分线段的时间复杂度为 。
- 若左端点处
- 单次时间复杂度为
。
- 将直线/线段写成形如
- 查询
- 同线段树单点查询,叶子节点储存的信息不一定是最终答案,中途经过的节点也可能是最终答案,需要对其进行更新。单次时间复杂度为
。
- 同线段树单点查询,叶子节点储存的信息不一定是最终答案,中途经过的节点也可能是最终答案,需要对其进行更新。单次时间复杂度为
点击查看代码
const double eps=1e-9; struct line { double k,b; }li[100010]; double f(int id,int x) { return li[id].k*x+li[id].b; } bool cmp(int a,int b,int x) { if(f(a,x)-f(b,x)>eps) return true; if(f(b,x)-f(a,x)>eps) return false; return a<b; } int sx_max(int a,int b,int x) { return cmp(a,b,x)==true?a:b; } struct LiChao_Tree { struct SegmentTree { int id; }tree[160010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void add(int rt,int l,int r,int id) { int mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) { swap(tree[rt].id,id); } if(l==r)//在 0 的时候会出现奇奇怪怪的问题 { return; } if(cmp(tree[rt].id,id,l)==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,r)==false) { add(rson(rt),mid+1,r,id); } } void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { add(rt,l,r,id); return; } int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } int query(int rt,int l,int r,int pos) { if(l==r) { return tree[rt].id; } int mid=(l+r)/2; if(pos<=mid) { return sx_max(tree[rt].id,query(lson(rt),l,mid,pos),pos); } else { return sx_max(tree[rt].id,query(rson(rt),mid+1,r,pos),pos); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m=0,pd,x1,x2,y1,y2,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>pd>>x1; x1=(x1+ans-1)%39989+1; if(pd==0) { ans=T.query(1,1,39989,x1); cout<<ans<<endl; } else { cin>>y1>>x2>>y2; y1=(y1+ans-1)%1000000000+1; x2=(x2+ans-1)%39989+1; y2=(y2+ans-1)%1000000000+1; if(x1>x2) { swap(x1,x2); swap(y1,y2); } m++; if(x1==x2)//特判斜率不存在的情况 { li[m].k=0; li[m].b=max(y1,y2); } else { li[m].k=1.0*(y2-y1)/(x2-x1); li[m].b=y1-li[m].k*x1; } T.update(1,1,39989,x1,x2,m); } } return 0; }
luogu P3081 [USACO13MAR] Hill Walk G
-
自边缘处起跳等价于找到与
相交的直线中最大的 。普通的李超线段树不支持删除操作,直接套用貌似很难处理。 -
观察到若能从
跳跃至 ,则一定有 。以 排序后顺次扫描线加入即可。 -
需要离散化和动态开点,注意计算函数值的时候要拿原值来算。
点击查看代码
const double eps=1e-9,inf=1e18; struct node { int x1,y1,x2,y2,l,r,pos; bool operator < (const node &another) const { return x1<another.x1; } }a[100010]; struct line { double k,b; }li[100010]; int b[300010]; double f(int id,int x) { return (id==0)?-inf:li[id].k*x+li[id].b; } bool cmp(int a,int b,int x) { if(f(a,x)-f(b,x)>eps) return true; if(f(b,x)-f(a,x)>eps) return false; return a<b; } int sx_max(int a,int b,int x) { return cmp(a,b,x)==true?a:b; } struct LiChao_Tree { struct SegmentTree { int id; }tree[1200010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void add(int rt,int l,int r,int id) { int mid=(l+r)/2; if(cmp(tree[rt].id,id,b[mid])==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,b[l])==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,b[r])==false) { add(rson(rt),mid+1,r,id); } } void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { add(rt,l,r,id); return; } int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } int query(int rt,int l,int r,int pos) { if(l==r) { return tree[rt].id; } int mid=(l+r)/2; if(pos<=mid) { return sx_max(tree[rt].id,query(lson(rt),l,mid,pos),b[pos]); } else { return sx_max(tree[rt].id,query(rson(rt),mid+1,r,pos),b[pos]); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,ans=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].x1>>a[i].y1>>a[i].x2>>a[i].y2; b[++b[0]]=a[i].l=a[i].x1; b[++b[0]]=a[i].r=a[i].x2-1; b[++b[0]]=a[i].pos=a[i].x2; } sort(b+1,b+1+b[0]); b[0]=unique(b+1,b+1+b[0])-(b+1); for(i=1;i<=n;i++) { a[i].l=lower_bound(b+1,b+1+b[0],a[i].l)-b; a[i].r=lower_bound(b+1,b+1+b[0],a[i].r)-b; a[i].pos=lower_bound(b+1,b+1+b[0],a[i].pos)-b; } sort(a+2,a+1+n); for(i=1;i<=n;i++) { li[i].k=1.0*(a[i].y2-a[i].y1)/(a[i].x2-a[i].x1); li[i].b=a[i].y1-li[i].k*a[i].x1; } for(i=1,j=2;i!=0;i=T.query(1,1,b[0],a[i].pos)) { ans++; for(;j<=n&&a[j].x1<=a[i].x2;j++) { if(a[j].x2>a[i].x2&&f(j,a[i].x2)<=a[i].y2) { T.update(1,1,b[0],a[j].l,a[j].r,j); } } } cout<<ans<<endl; return 0; }
luogu P4254 [JSOI2008] Blue Mary 开公司
-
李超线段树板子。
点击查看代码
const double eps=1e-9; struct line { double k,b; }li[100010]; double f(int id,int x) { return li[id].k*x+li[id].b; } bool cmp(int a,int b,int x) { if(f(a,x)-f(b,x)>eps) return true; if(f(b,x)-f(a,x)>eps) return false; return a<b; }; int sx_max(int a,int b,int x) { return cmp(a,b,x)==true?a:b; } struct LiChao_Tree { struct SegmentTree { int id; }tree[200010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void add(int rt,int l,int r,int id) { int mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,l)==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,r)==false) { add(rson(rt),mid+1,r,id); } } void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { add(rt,l,r,id); return; } int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } int query(int rt,int l,int r,int pos) { if(l==r) { return tree[rt].id; } int mid=(l+r)/2; if(pos<=mid) { return sx_max(tree[rt].id,query(lson(rt),l,mid,pos),pos); } else { return sx_max(tree[rt].id,query(rson(rt),mid+1,r,pos),pos); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m=0,t,x,i; string s; cin>>n; for(i=1;i<=n;i++) { cin>>s; if(s=="Query") { cin>>t; t--; x=T.query(1,0,50000,t); cout<<(int)((li[x].k*t+li[x].b)/100)<<endl; } else { m++; cin>>li[m].b>>li[m].k; T.update(1,0,50000,0,50000,m); } } return 0; }
luogu P4069 [SDOI2016] 游戏
-
同 BZOJ3221 Obserbing the tree树上询问 ,考虑把路径拆成
两部分。 -
一次函数最值显然在端点处取到,树剖套李超线段树即可。正确性由同一条重链上的距离单调可以保证。
点击查看代码
const ll inf=123456789123456789; struct node { ll nxt,to,w; }e[200010]; ll head[100010],fa[100010],siz[100010],dep[100010],dis[100010],son[100010],top[100010],dfn[100010],pos[100010],cnt=0,tot=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(ll x,ll father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { dis[e[i].to]=dis[x]+e[i].w; dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(ll x,ll id) { top[x]=id; tot++; dfn[x]=tot; pos[tot]=x; if(son[x]!=0) { dfs2(son[x],id); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } struct line { ll k,b; }li[200010]; ll f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)<0) return true; if(f(b,x)-f(a,x)<0) return false; return a<b; } struct LiChao_Tree { struct SegmentTree { ll id,minn; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void build(ll rt,ll l,ll r) { tree[rt].id=0; tree[rt].minn=inf; if(l==r) { return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushup(ll rt,ll l,ll r) { tree[rt].minn=min(tree[rt].minn,min(f(tree[rt].id,dis[pos[l]]),f(tree[rt].id,dis[pos[r]]))); if(l!=r) { tree[rt].minn=min(tree[rt].minn,min(tree[lson(rt)].minn,tree[rson(rt)].minn)); } } void add(ll rt,ll l,ll r,ll id) { ll mid=(l+r)/2; if(cmp(tree[rt].id,id,dis[pos[mid]])==false) { swap(tree[rt].id,id); } if(l==r) { pushup(rt,l,r); return; } if(cmp(tree[rt].id,id,dis[pos[l]])==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,dis[pos[r]])==false) { add(rson(rt),mid+1,r,id); } pushup(rt,l,r); } void update(ll rt,ll l,ll r,ll x,ll y,ll id) { if(x<=l&&r<=y) { add(rt,l,r,id); return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } pushup(rt,l,r); } ll query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].minn; } ll mid=(l+r)/2,ans=min(f(tree[rt].id,dis[pos[max(l,x)]]),f(tree[rt].id,dis[pos[min(r,y)]])); if(x<=mid) { ans=min(ans,query(lson(rt),l,mid,x,y)); } if(y>mid) { ans=min(ans,query(rson(rt),mid+1,r,x,y)); } return ans; } }T; ll lca(ll u,ll v) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { u=fa[top[u]]; } else { v=fa[top[v]]; } } return dep[u]<dep[v]?u:v; } void update(ll u,ll v,ll n,ll id) { while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { T.update(1,1,n,dfn[top[u]],dfn[u],id); u=fa[top[u]]; } else { T.update(1,1,n,dfn[top[v]],dfn[v],id); v=fa[top[v]]; } } if(dep[u]<dep[v]) { T.update(1,1,n,dfn[u],dfn[v],id); } else { T.update(1,1,n,dfn[v],dfn[u],id); } } ll query(ll u,ll v,ll n) { ll ans=inf; while(top[u]!=top[v]) { if(dep[top[u]]>dep[top[v]]) { ans=min(ans,T.query(1,1,n,dfn[top[u]],dfn[u])); u=fa[top[u]]; } else { ans=min(ans,T.query(1,1,n,dfn[top[v]],dfn[v])); v=fa[top[v]]; } } if(dep[u]<dep[v]) { ans=min(ans,T.query(1,1,n,dfn[u],dfn[v])); } else { ans=min(ans,T.query(1,1,n,dfn[v],dfn[u])); } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,q,m=0,u,v,w,a,b,rt,i; cin>>n>>q; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs1(1,0); dfs2(1,1); T.build(1,1,n); li[0].b=inf; for(i=1;i<=q;i++) { cin>>w>>u>>v; if(w==1) { cin>>a>>b; rt=lca(u,v); m++; li[m].k=-a; li[m].b=a*dis[u]+b; update(u,rt,n,m); m++; li[m].k=a; li[m].b=a*dis[u]+b-a*2*dis[rt]; update(v,rt,n,m); } else { cout<<query(u,v,n)<<endl; } } return 0; }
1.7
闲话
- 候操位置变成了东家属院门口处,班主任说以后让我们早操到位早一点。
- 吃完早饭到机房后
说今天写博弈专题; 说让我们以后下午 到位,我说时间是不是有点紧了,他未作回应。临吃午饭时 和 先后说了高一和高二的下午都要 到位。 - 下午貌似有
查到位;然后放 @HANGRY_sol 的每日一歌《One More Time,One More Chance》和 @5k_sync_closer 的每日一歌《Good Escape》。放完每日一歌后 解释了下昨天放两首歌的原因是因为放的第一首歌太短了,又给我们说了下 的专题有的是选做的,让我们先做学校 上要求的。然后给我们讲为什么要求我们 到位:我们班班主任在班里是这么要求的(属于是默认从机房到教室的距离很短导致的);我们都很优秀,要求也需要更加严格;大部分同学都能遵守(有点担心偶尔晚到的后果)。然后说因为今天体育组有测试,所以高二整个年级的体育课取消了,我们体育课也就取消了。 - 因
嫌我们不往学校 上交题而只往 上交题,他说“你们要听话,要是不听话的话就只能把 关掉了”。 - 晚上讲题。
做题纪要
luogu P10061 [SNOI2024] 矩阵
luogu P8726 [蓝桥杯 2020 省 AB3] 旅行家
-
设
表示在 停留的最大 值,状态转移方程为 。 -
李超线段树维护斜率优化
板子。- 拆掉
后有 ,李超线段树查询 处的 即可。 - 通常需要特殊处理
处的转移。
点击查看代码
struct line { ll k,b; }li[500010]; ll dp[500010],t[500010],a[500010]; ll f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)>0) return true; if(f(b,x)-f(a,x)>0) return false; return a<b; } struct LiChao_Tree { struct SegmentTree { ll id; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void add(ll rt,ll l,ll r,ll id) { ll mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,l)==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,r)==false) { add(rson(rt),mid+1,r,id); } } void update(ll rt,ll l,ll r,ll x,ll y,ll id) { if(x<=l&&r<=y) { add(rt,l,r,id); return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,id); } if(y>mid) { update(rson(rt),mid+1,r,x,y,id); } } ll query(ll rt,ll l,ll r,ll pos) { if(l==r) { return f(tree[rt].id,pos); } ll mid=(l+r)/2; if(pos<=mid) { return max(f(tree[rt].id,pos),query(lson(rt),l,mid,pos)); } else { return max(f(tree[rt].id,pos),query(rson(rt),mid+1,r,pos)); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>t[i]; } for(i=1;i<=n;i++) { cin>>a[i]; } li[0].b=-1e18; for(i=1;i<=n;i++) { dp[i]=(i!=1)*T.query(1,1,200000,t[i]); li[i].k=t[i]; li[i].b=dp[i]/2-a[i]; T.update(1,1,200000,1,200000,i); ans=max(ans,dp[i]); } cout<<ans<<endl; return 0; }
- 拆掉
CF932F Escape Through Leaf
-
设
表示 的答案,状态转移方程为 。 -
插入
的直线后查询 处的最小值即可。 -
李超线段树合并板子。
- 对于上述过程暴力重建李超树不可接受,观察到子树信息可以进行合并。
- 类似线段树合并的过程,设当时我们在合并两棵李超树上的代表同一区间内的两个节点
,且要求最终合并至 处。- 若
其中一个为空,则直接继承对方节点的信息。 - 否则将
的线段插入 中,并更新 的优势线段。 - 递归合并
的子树进行合并。
- 若
- 设总点数为
,合并节点的时间复杂度(均摊后)为 。线段的合并时要么使其深度 ,要么直接删除,故时间复杂度也为 。
点击查看代码
struct node { ll nxt,to; }e[200010]; ll head[100010],a[100010],b[100010],dp[100010],cnt=0,m=0; void add(ll u,ll v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct line { ll k,b; }li[100010]; ll f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)<0) return true; if(f(b,x)-f(a,x)<0) return false; return a<b; } ll sx_min(ll a,ll b,ll x) { return cmp(a,b,x)==true?a:b; } struct LiChao_Tree { ll root[100010],rt_sum=0; struct SegmentTree { ll ls,rs,id; }tree[100010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].id=0; return rt_sum; } void add(ll &rt,ll l,ll r,ll id) { if(rt==0) { rt=build_rt(); tree[rt].id=id; return; } ll mid=(l+r)/2; if(cmp(tree[rt].id,id,mid-100000)==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,l-100000)==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,r-100000)==false) { add(rson(rt),mid+1,r,id); } } ll merge(ll rt1,ll rt2,ll l,ll r) { if(rt1==0||rt2==0) { return rt1+rt2; } if(l==r) { tree[rt1].id=sx_min(tree[rt1].id,tree[rt2].id,l-100000); return rt1; } add(rt1,l,r,tree[rt2].id); ll mid=(l+r)/2; lson(rt1)=merge(lson(rt1),lson(rt2),l,mid); rson(rt1)=merge(rson(rt1),rson(rt2),mid+1,r); return rt1; } ll query(ll rt,ll l,ll r,ll pos) { if(rt==0) { return 0x7f7f7f7f7f7f7f7f; } if(l==r) { return f(tree[rt].id,pos-100000); } ll mid=(l+r)/2; if(pos<=mid) { return min(f(tree[rt].id,pos-100000),query(lson(rt),l,mid,pos)); } else { return min(f(tree[rt].id,pos-100000),query(rson(rt),mid+1,r,pos)); } } }T; void dfs(ll x,ll fa) { for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); T.root[x]=T.merge(T.root[x],T.root[e[i].to],0,200000); } } dp[x]=(T.root[x]!=0)*T.query(T.root[x],1,200000,a[x]+100000); m++; li[m].k=b[x]; li[m].b=dp[x]; T.add(T.root[x],1,200000,m); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,u,v,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { cin>>b[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } dfs(1,0); for(i=1;i<=n;i++) { cout<<dp[i]<<" "; } return 0; }
[ARC131C] Zero XOR
-
某个局面先手必胜当且仅当
。 -
考虑第一回合先手无法立刻获胜的情况。
- 若
为奇数,设当前异或和为 ,先手只需要找到一个 使得不存在 使得 ,由 两两不同可知即使两两配对也会剩下一个数,从而规约至 的情况。而 时先手必胜故 为奇数时先手必胜。 - 否则先手任意取也无法立刻获胜,从而使得后手必胜。
点击查看代码
int a[400010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,sum=0,flag,i; cin>>n; flag=n%2; for(i=1;i<=n;i++) { cin>>a[i]; sum^=a[i]; } for(i=1;i<=n;i++) { flag|=(sum==a[i]); } cout<<((flag==1)?"Win":"Lose")<<endl; return 0; }
- 若
[ARC143C] Piles of Pebbles
-
考虑先后手之间的模仿,将
变成 并不会影响结果。- 若更改后先手必胜,那么先手在执行完必要的行动(必胜策略)后模仿后手行动即可,否则后手模仿先手行动。
-
若
则后手全程模仿先手行动,后手必胜。 -
若
,先手第一次从满足 的 中取走后模仿后手行动从而使后手必败,先手必胜。 -
若
,后手第一次从满足 的 和 的 (若 未被先手取走的情况下)中取走后模仿先手行动从而使后手必胜,否则先手必胜。点击查看代码
int a[200010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,x,y,maxx=0,flag=0,i; cin>>n>>x>>y; for(i=1;i<=n;i++) { cin>>a[i]; a[i]%=(x+y); maxx=max(maxx,a[i]); flag|=(y<=a[i]&&a[i]<x); } if(maxx<x) { cout<<"Second"<<endl; } else { cout<<((flag==0)?"First":"Second")<<endl; } return 0; }
[ABC261Ex] Game on Graph
-
难点在于环上行走的过程中有其他出边时的处理。
-
设
表示当前在 时轮到Takahashi
/Aoki
移动时的答案,状态转移方程为 ,边界为 。 -
因为有环的存在,不能直接拓扑排序后从下往上转移,即在反图上从上往下转移。
-
转移过程中
可以直接去更新其他节点,但 需要在所有出边都更新完后才能去更新其他节点。 -
具体实现时,考虑在反图上利用
转移,记录 有多少条出边被更新过,当都被更新后再将 加入优先队列中。点击查看代码
struct node { ll nxt,to,w; }e[200010]; struct quality { ll dis,x,id; bool operator < (const quality &another) const { return dis>another.dis; } }; ll head[200010],f[2][200010],vis[2][200010],din[200010],cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dijsktra(ll n) { priority_queue<quality>q; memset(f[0],0x3f,sizeof(f[0])); memset(vis,0,sizeof(vis)); for(ll i=1;i<=n;i++) { if(din[i]==0) { f[0][i]=0; q.push((quality){f[0][i],i,0}); q.push((quality){f[1][i],i,1}); } } while(q.empty()==0) { ll x=q.top().x,id=q.top().id; q.pop(); if(vis[id][x]==0) { vis[id][x]=1; if(id==0) { for(ll i=head[x];i!=0;i=e[i].nxt) { din[e[i].to]--; f[1][e[i].to]=max(f[1][e[i].to],f[0][x]+e[i].w); if(din[e[i].to]==0) { q.push((quality){f[1][e[i].to],e[i].to,1}); } } } else { for(ll i=head[x];i!=0;i=e[i].nxt) { if(f[0][e[i].to]>f[1][x]+e[i].w) { f[0][e[i].to]=f[1][x]+e[i].w; q.push((quality){f[0][e[i].to],e[i].to,0}); } } } } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,s,u,v,w,i; cin>>n>>m>>s; for(i=1;i<=m;i++) { cin>>u>>v>>w; add(v,u,w); din[u]++; } dijsktra(n); if(f[0][s]==0x3f3f3f3f3f3f3f3f) { cout<<"INFINITY"<<endl; } else { cout<<f[0][s]<<endl; } return 0; }
HDU1524 A Chess Game
HDU3032 Nim or not Nim?
-
每堆石子相互独立,只需要计算每堆石子各自的
函数值。 -
。 -
打表得到
。点击查看代码
int sg(int x) { if(x%4==0) return x-1; if(x%4==3) return x+1; return x; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,x,sum,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; sum=0; for(i=1;i<=n;i++) { cin>>x; sum^=sg(x); } cout<<((sum==0)?"Bob":"Alice")<<endl; } return 0; }
LibreOJ 10241. 「一本通 6.7 例 1」取石子游戏 1
-
巴什博弈。
-
若当前局面有
颗石子,则后手必胜。博弈策略为取若干石子使得剩余石子为 的倍数。 -
时先手必胜。点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,k; cin>>n>>k; cout<<((n%(k+1)==0)?2:1)<<endl; return 0; }
[ARC046B] 石取り大作戦
-
时是巴什博弈,且 时也可以归约至巴什博弈。必胜当且仅当 ,其中后者是为了考虑第一回合而加的条件。 -
时考虑先手对巴什博弈的反制,发现一旦在第一回合无法取到胜利后面就无法改变局面,故 时必胜。点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,a,b; cin>>n>>a>>b; cout<<((a>=n||((n%(a+1)!=0||b==n-2)&&a>=b))?"Takahashi":"Aoki")<<endl; return 0; }
[AGC010F] Tree Game
-
在一条边上来回走貌似是很有用的,而且只会向
更小的点走。 -
观察到
,不妨只考虑仅能往子树内走的情况。 -
设
表示当前在 时的先手获胜情况,状态转移方程为 。点击查看代码
struct node { int nxt,to; }e[6010]; int head[3010],a[3010],f[3010],cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs(int x,int fa) { f[x]=0; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x); f[x]|=(f[e[i].to]==0&&a[x]>a[e[i].to]); } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,u,v,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v; add(u,v); add(v,u); } for(i=1;i<=n;i++) { dfs(i,0); if(f[i]==1) { cout<<i<<" "; } } return 0; }
[AGC014D] Black and White Tree
luogu P6047 丝之割
-
同 luogu P2900 [USACO08MAR] Land Acquisition G ,按照
升序, 降序排序并删去不必要的弦。 -
维护前后缀
后斜率优化 即可。点击查看代码
struct node { ll u,v; }a[300010]; ll pre[300010],suf[300010],u[300010],v[300010],f[300010]; deque<ll>q; bool cmp(node a,node b) { return (a.u==b.u)?(a.v>b.v):(a.u<b.u); } ll x(ll j) { return -pre[u[j+1]-1]; } ll y(ll j) { return f[j]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,cnt=0,maxx=0,i; cin>>n>>m; for(i=1;i<=n;i++) cin>>pre[i]; for(i=2;i<=n;i++) pre[i]=min(pre[i],pre[i-1]); for(i=1;i<=n;i++) cin>>suf[i]; for(i=n-1;i>=1;i--) suf[i]=min(suf[i],suf[i+1]); for(i=1;i<=m;i++) cin>>a[i].u>>a[i].v; sort(a+1,a+1+m,cmp); for(i=1;i<=m;i++) { if(a[i].v>maxx) { cnt++; maxx=max(maxx,a[i].v); u[cnt]=a[i].u; v[cnt]=a[i].v; } } u[cnt+1]=n+1; f[0]=0; q.push_back(0); for(i=1;i<=cnt;i++) { while(q.size()>=2&&y(q[1])-y(q.front())<=suf[v[i]+1]*(x(q[1])-x(q.front()))) { q.pop_front(); } f[i]=f[q.front()]+pre[u[q.front()+1]-1]*suf[v[i]+1]; while(q.size()>=2&&(y(q.back())-y(q[q.size()-2]))*(x(i)-x(q.back()))>=(y(i)-y(q.back()))*(x(q.back())-x(q[q.size()-2]))) { q.pop_back(); } q.push_back(i); } cout<<f[cnt]<<endl; return 0; }
[AGC002D] Stamp Rally
-
以编号为边权建立
重构树,记录子树大小表示经过能到达的点的数量,然后二分答案即可。点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],fa[200010][20],siz[200010],c[200010],cnt=0; pair<int,int>d[200010]; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct DSU { int fa[200010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; void kruskal(int n,int m) { D.init(2*n); for(int i=1,tot=n;i<=m&&tot<=2*n-1;i++) { int x=D.find(d[i].first),y=D.find(d[i].second); if(x!=y) { tot++; c[tot]=i; D.fa[x]=D.fa[y]=tot; add(tot,x); add(tot,y); } } } void dfs(int x,int father) { siz[x]=(c[x]==0); fa[x][0]=father; for(int i=1;i<=18;i++) { fa[x][i]=fa[fa[x][i-1]][i-1]; } for(int i=head[x];i!=0;i=e[i].nxt) { dfs(e[i].to,x); siz[x]+=siz[e[i].to]; } } int ask(int x,int y,int w) { for(int i=18;i>=0;i--) { if(c[fa[x][i]]<=w&&fa[x][i]!=0) { x=fa[x][i]; } if(c[fa[y][i]]<=w&&fa[y][i]!=0) { y=fa[y][i]; } } return siz[x]+(x!=y)*siz[y]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,u,v,w,l,r,ans,mid,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>d[i].first>>d[i].second; } kruskal(n,m); dfs(2*n-1,0); cin>>q; for(i=1;i<=q;i++) { cin>>u>>v>>w; l=1; r=m; ans=0; while(l<=r) { mid=(l+r)/2; if(ask(u,v,mid)>=w) { r=mid-1; ans=mid; } else { l=mid+1; } } cout<<ans<<endl; } return 0; }
[AGC002E] Candy Piles
1.8
闲话
- 回宿舍后听班里人说他们今天的体育课也取消了。
- 下午放 @int_R 的每日一歌《关不上的窗》。
- 貌似今天有香港中文大学(深圳)的 一个教授来学校做宣讲,但教练没说让去。
- 晚上讲题。
做题纪要
[AGC029D] Grid game
[AGC016F] Games on DAG
CodeChef Destructive Nim
luogu P11522 [THUPC2025 初赛] Harmful Machine Learning
-
每次移动都会保证至少取到三个数中的次大值。
-
考虑每次移动后只能用先前的数反复交换进行反制,注意特判。
点击查看代码
int a[200010]; priority_queue<int,vector<int>,less<int> >q1; priority_queue<int,vector<int>,greater<int> >q2; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,x,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n>>x; while(q1.empty()==0) { q1.pop(); } while(q2.empty()==0) { q2.pop(); } for(i=1;i<=n;i++) { cin>>a[i]; if(abs(i-x)<=1) { q1.push(a[i]); } else { q2.push(a[i]); } } sort(a+1,a+1+n); if(n<=4) { cout<<a[min(3,n)]<<endl; } else { if(q1.top()>q2.top()) { q1.push(q2.top()); q2.pop(); q2.push(q1.top()); q1.pop(); } cout<<(q1.size()==2?max(q1.top(),q2.top()):q1.top())<<endl; } } return 0; }
[AGC010D] Decrementing
luogu P3959 [NOIP2017 提高组] 宝藏
-
对深度分层进行状压
。 -
具体实现时可以钦定两层之间的深度代价为
, 可以证明这并不影响答案。点击查看代码
int a[15][15],f[15][(1<<12)+10],to[(1<<12)+10],dis[(1<<12)+10][15]; vector<pair<int,int> >pre[(1<<12)+10]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,w,ans=0x3f3f3f3f,s,t,sum,i,j; cin>>n>>m; memset(a,0x3f,sizeof(a)); memset(dis,0x3f,sizeof(dis)); memset(f,0x3f,sizeof(f)); for(i=1;i<=m;i++) { cin>>u>>v>>w; u--; v--; a[u][v]=a[v][u]=min(a[u][v],w); } for(s=0;s<(1<<n);s++) { to[s]=s; for(i=0;i<n;i++) { if((s>>i)&1) { dis[s][i]=0; for(j=0;j<n;j++) { if(a[i][j]!=0x3f3f3f3f) { to[s]|=(1<<j); dis[s][j]=min(dis[s][j],a[i][j]); } } } } } for(s=0;s<(1<<n);s++) { for(t=s;t!=0;t=s&(t-1)) { if((to[t]&s)==s) { sum=0; j=s^t; for(i=0;i<n;i++) { if((j>>i)&1) { sum+=dis[t][i]; } } pre[s].push_back(make_pair(t,sum)); } } } for(i=0;i<n;i++) { f[1][1<<i]=0; } for(i=2;i<=n;i++) { for(s=1;s<(1<<n);s++) { for(j=0;j<pre[s].size();j++) { f[i][s]=min(f[i][s],f[i-1][pre[s][j].first]+(i-1)*pre[s][j].second); } } } for(i=1;i<=n;i++) { ans=min(ans,f[i][(1<<n)-1]); } cout<<ans<<endl; return 0; }
luogu P4027 [NOI2007] 货币兑换
-
观察到最优买卖方案满足每次买进操作每次买进操作使用完所有的人民币,每次卖出操作卖出所有的金券。
-
设
表示到第 天能够获得的最大金钱数目,状态转移方程为 。 -
设
,后面的式子等价于 。 -
以
为斜率, 为截距插入李超线段树,查询 处的最大值,需要离散化。点击查看代码
const double eps=1e-9; struct line { double k,b; }li[100010]; double a[100010],b[100010],rate[100010],dp[100010],c[100010],d[100010]; double f(int id,double x) { return li[id].k*x+li[id].b; } bool cmp(int a,int b,double x) { if(f(a,x)-f(b,x)>eps) return true; if(f(b,x)-f(a,x)>eps) return false; return a<b; } double sx_max(double a,double b) { return a-b>eps?a:b; } struct LiChao_Tree { struct SegmentTree { int id,maxx; }tree[400010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void add(int rt,int l,int r,int id) { int mid=(l+r)/2; if(cmp(tree[rt].id,id,d[mid])==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,d[l])==false) { add(lson(rt),l,mid,id); } if(cmp(tree[rt].id,id,d[r])==false) { add(rson(rt),mid+1,r,id); } } double query(int rt,int l,int r,int pos) { if(l==r) { return f(tree[rt].id,d[pos]); } int mid=(l+r)/2; if(pos<=mid) { return sx_max(f(tree[rt].id,d[pos]),query(lson(rt),l,mid,pos)); } else { return sx_max(f(tree[rt].id,d[pos]),query(rson(rt),mid+1,r,pos)); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,s,i; cin>>n>>s; for(i=1;i<=n;i++) { cin>>a[i]>>b[i]>>rate[i]; c[i]=d[i]=a[i]/b[i]; } sort(d+1,d+1+n); m=unique(d+1,d+1+n)-(d+1); for(i=1;i<=n;i++) { c[i]=lower_bound(d+1,d+1+m,c[i])-d; if(i==1) { dp[i]=s; } else { dp[i]=max(dp[i-1],b[i]*T.query(1,1,m,c[i])); } li[i].b=dp[i]/(rate[i]*a[i]+b[i]); li[i].k=li[i].b*rate[i]; T.add(1,1,m,i); } printf("%.3lf\n",dp[n]); return 0; }
luogu P10602 [CEOI 2009] Harbingers
-
设
表示 点的答案,状态转移方程为 。 -
以
为斜率, 为截距插入李超线段树,查询 处的最小值。 -
可持久化李超线段树板子。
- 因维护的是从根节点到当前节点的一条链,且普通李超线段树不支持删除,考虑可持久化李超线段树。
- 更新优势线段的过程中左右儿子若没有修改则直接继承上一个节点的左右儿子信息。
点击查看代码
struct node { ll nxt,to,w; }e[200010]; ll head[200010],tim[200010],speed[200010],dp[200010],m=0,cnt=0; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } struct line { ll k,b; }li[100010]; ll f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)<0) return true; if(f(b,x)-f(a,x)<0) return false; return a<b; } struct LiChao_Tree { ll root[100010],rt_sum=0; struct SegmentTree { ll ls,rs,id; }tree[100010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].id=0; return rt_sum; } void add(ll pre,ll &rt,ll l,ll r,ll id) { rt=build_rt(); tree[rt]=tree[pre]; if(pre==0) { tree[rt].id=id; return; } ll mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) { swap(tree[rt].id,id); } if(l==r) { return; } if(cmp(tree[rt].id,id,l)==false) add(lson(pre),lson(rt),l,mid,id); else lson(rt)=lson(pre); if(cmp(tree[rt].id,id,r)==false) add(rson(pre),rson(rt),mid+1,r,id); else rson(rt)=rson(pre); } ll query(ll rt,ll l,ll r,ll pos) { if(rt==0) { return 0x7f7f7f7f7f7f7f7f; } if(l==r) { return f(tree[rt].id,pos); } ll mid=(l+r)/2; if(pos<=mid) { return min(f(tree[rt].id,pos),query(lson(rt),l,mid,pos)); } else { return min(f(tree[rt].id,pos),query(rson(rt),mid+1,r,pos)); } } }T; void dfs(ll x,ll fa,ll dis) { dp[x]=(x!=1)*(tim[x]+speed[x]*dis+T.query(T.root[fa],0,1000000000,speed[x])); m++; li[m].k=-dis; li[m].b=dp[x]; T.add(T.root[fa],T.root[x],0,1000000000,m); for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,dis+e[i].w); } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,u,v,w,i; cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } for(i=2;i<=n;i++) { cin>>tim[i]>>speed[i]; } dfs(1,0,0); for(i=2;i<=n;i++) { cout<<dp[i]<<" "; } return 0; }
luogu P2305 [NOI2014] 购票
-
若没有
的限制就能和 luogu P10602 [CEOI 2009] Harbingers 一样做了。 -
考虑外层线段树(单点修改、区间查询)套内层李超线段树(插入直线,单点查询),修改时外层线段树树顶到叶子都需要修改,此时需要查询某条树链的答案,在套一遍树剖没必要,考虑欧拉序辅助运算。
-
具体实现时可以把信息都挂在出栈序上然后向后查询一段区间。
点击查看代码
struct node { ll nxt,to,w; }e[200010]; ll head[200010],p[200010],q[200010],l[200010],dis[200010],out[200010],s[200010],dp[200010],m=0,cnt=0,tot=0,n; struct line { ll k,b; }li[200010]; ll f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)<0) return true; if(f(b,x)-f(a,x)<0) return false; return a<b; } struct LiChao_Tree { ll rt_sum=0; struct SegmentTree { ll ls,rs,id; }tree[200010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].id=0; return rt_sum; } void add(ll &rt,ll l,ll r,ll id) { if(rt==0) { rt=build_rt(); tree[rt].id=id; return; } ll mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) swap(tree[rt].id,id); if(l==r) return; if(cmp(tree[rt].id,id,l)==false) add(lson(rt),l,mid,id); if(cmp(tree[rt].id,id,r)==false) add(rson(rt),mid+1,r,id); } ll query(ll rt,ll l,ll r,ll pos) { if(rt==0) return 0x3f3f3f3f3f3f3f3f; if(l==r) { return f(tree[rt].id,pos); } ll mid=(l+r)/2; if(pos<=mid) { return min(f(tree[rt].id,pos),query(lson(rt),l,mid,pos)); } else { return min(f(tree[rt].id,pos),query(rson(rt),mid+1,r,pos)); } } #undef lson #undef rson }T; struct SMT { struct SegmentTree { ll root; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void update(ll rt,ll l,ll r,ll pos,ll id) { T.add(tree[rt].root,0,1000000,id); if(l==r) { return; } ll mid=(l+r)/2; if(pos<=mid) { update(lson(rt),l,mid,pos,id); } else { update(rson(rt),mid+1,r,pos,id); } } ll query(ll rt,ll l,ll r,ll x,ll y,ll pos) { if(x<=l&&r<=y) { return T.query(tree[rt].root,0,1000000,pos); } ll mid=(l+r)/2; if(y<=mid) { return query(lson(rt),l,mid,x,y,pos); } if(x>mid) { return query(rson(rt),mid+1,r,x,y,pos); } return min(query(lson(rt),l,mid,x,y,pos),query(rson(rt),mid+1,r,x,y,pos)); } }S; void add(ll u,ll v,ll w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(ll x) { for(ll i=head[x];i!=0;i=e[i].nxt) { dfs1(e[i].to); } tot++; out[x]=tot; } void dfs2(ll x,ll w) { tot++; dis[tot]=dis[tot-1]+w; s[tot]=x; if(x!=1) { dp[x]=dis[tot]*p[x]+q[x]+S.query(1,1,n,out[x],out[s[lower_bound(dis+1,dis+1+tot,dis[tot]-l[x])-dis]],p[x]); } m++; li[m].k=-dis[tot]; li[m].b=dp[x]; S.update(1,1,n,out[x],m); for(ll i=head[x];i!=0;i=e[i].nxt) { dfs2(e[i].to,e[i].w); } tot--; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,u,w,i; cin>>n>>t; for(i=2;i<=n;i++) { cin>>u>>w>>p[i]>>q[i]>>l[i]; add(u,i,w); } dfs1(1); tot=0; dfs2(1,0); for(i=2;i<=n;i++) { cout<<dp[i]<<endl; } return 0; }
CF1175G Yet Another Partiton Problem
CF1083E The Fair Nut and Rectangles
-
由于任意两个矩形都不包含,故按照横坐标升序排序后纵坐标降序排序。
-
设
表示处理到第 个矩形,选择第 个矩形的最大值。状态转移方程为 。 -
拆掉
后有 ,单调队列优化即可。 -
卡掉了通分判大小,需要求斜率。
点击查看代码
namespace IO{ #ifdef LOCAL FILE*Fin(fopen("test.in","r")),*Fout(fopen("test.out","w")); #else FILE*Fin(stdin),*Fout(stdout); #endif class qistream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qistream(FILE*_fp=stdin):fp(_fp),p(0){fread(buf+p,1,SIZE-p,fp);}void flush(){memmove(buf,buf+p,SIZE-p),fread(buf+SIZE-p,1,p,fp),p=0;}qistream&operator>>(char&x){x=getch();while(isspace(x))x=getch();return*this;}template<class T>qistream&operator>>(T&x){x=0;p+BLOCK>=SIZE?flush():void();bool flag=false;for(;!isdigit(buf[p]);++p)flag=buf[p]=='-';for(;isdigit(buf[p]);++p)x=x*10+buf[p]-'0';x=flag?-x:x;return*this;}char getch(){p+BLOCK>=SIZE?flush():void();return buf[p++];}qistream&operator>>(char*str){char ch=getch();while(ch<=' ')ch=getch();int i=0;for(;ch>' ';++i,ch=getch())str[i]=ch;str[i]='\0';return*this;}}qcin(Fin); class qostream{static const size_t SIZE=1<<16,BLOCK=64;FILE*fp;char buf[SIZE];int p;public:qostream(FILE*_fp=stdout):fp(_fp),p(0){}~qostream(){fwrite(buf,1,p,fp);}void flush(){fwrite(buf,1,p,fp),p=0;}template<class T>qostream&operator<<(T x){int len=0;p+BLOCK>=SIZE?flush():void();x<0?(x=-x,buf[p++]='-'):0;do buf[p+len]=x%10+'0',x/=10,++len;while(x);for(int i=0,j=len-1;i<j;++i,--j)std::swap(buf[p+i],buf[p+j]);p+=len;return*this;}qostream&operator<<(char x){putch(x);return*this;}void putch(char ch){p+BLOCK>=SIZE?flush():void();buf[p++]=ch;}qostream&operator<<(char*str){for(int i=0;str[i];++i)putch(str[i]);return*this;}qostream&operator<<(const char*s){for(int i=0;s[i];++i)putch(s[i]);return*this;}}qcout(Fout); } #define cin IO::qcin #define cout IO::qcout struct node { ll x,y,w; }a[1000010]; ll f[1000010]; deque<ll>q; bool cmp(node a,node b) { return a.x<b.x; } ll x(ll i) { return a[i].x; } ll y(ll i) { return f[i]; } double slope(ll a,ll b) { return x(a)==x(b)?0x7f7f7f7f:1.0*(y(b)-y(a))/(x(b)-x(a)); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].x>>a[i].y>>a[i].w; } sort(a+1,a+1+n,cmp); f[0]=0; q.push_back(0); for(i=1;i<=n;i++) { while(q.size()>=2&&slope(q.front(),q[1])>=a[i].y) { q.pop_front(); } f[i]=f[q.front()]+(a[i].x-a[q.front()].x)*a[i].y-a[i].w; ans=max(ans,f[i]); while(q.size()>=2&&slope(q[q.size()-2],q.back())<=slope(q.back(),i)) { q.pop_back(); } q.push_back(i); } cout<<ans<<endl; return 0; }
1.9
闲话
说让我们明后两天去补 。- 上午
打 accoders NOI 的模拟赛。 - 下午放 @xrlong 的每日一歌《春日影》。体育课正常。
- 晚上讲题,
又干了一些唐事。
做题纪要
QOJ7877. Balanced Array
luogu P2497 [SDOI2012] 基站建设
-
由勾股定理可知
,解得 。 -
设
表示第 个基站的答案,状态转移方程为 。 -
以
为斜率, 为截距插入李超线段树,查询 处的最小值。点击查看代码
const double eps=1e-9; struct line { double k,b; }li[500010]; ll x[500010],r[500010],v[500010]; double dp[500010]; double f(ll id,ll x) { return li[id].k*x+li[id].b; } bool cmp(ll a,ll b,ll x) { if(f(a,x)-f(b,x)<eps) return true; if(f(b,x)-f(a,x)<eps) return false; return a<b; } double sx_min(double a,double b) { return a-b<eps?a:b; } struct LiChao_Tree { ll root,rt_sum=0; struct SegmentTree { ll ls,rs,id; }tree[500010<<5]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].id=0; return rt_sum; } void add(ll &rt,ll l,ll r,ll id) { if(rt==0) { rt=build_rt(); tree[rt].id=id; return; } ll mid=(l+r)/2; if(cmp(tree[rt].id,id,mid)==false) swap(tree[rt].id,id); if(l==r) return; if(cmp(tree[rt].id,id,l)==false) add(lson(rt),l,mid,id); if(cmp(tree[rt].id,id,r)==false) add(rson(rt),mid+1,r,id); } double query(ll rt,ll l,ll r,ll pos) { if(rt==0) { return 0x3f3f3f3f3f3f3f3f; } if(l==r) { return f(tree[rt].id,pos); } ll mid=(l+r)/2; if(pos<=mid) { return sx_min(f(tree[rt].id,pos),query(lson(rt),l,mid,pos)); } else { return sx_min(f(tree[rt].id,pos),query(rson(rt),mid+1,r,pos)); } } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,i; double ans=0x3f3f3f3f3f3f3f3f; cin>>n>>m; for(i=1;i<=n;i++) { cin>>x[i]>>r[i]>>v[i]; dp[i]=v[i]+(i!=1)*T.query(T.root,1,1000000000000,x[i]); li[i].k=0.5/sqrt(1.0*r[i]); li[i].b=dp[i]-x[i]*li[i].k; T.add(T.root,1,1000000000000,i); if(x[i]+r[i]>=m) { ans=min(ans,dp[i]); } } printf("%.3lf\n",ans); return 0; }
luogu P3835 【模板】可持久化平衡树
-
可持久化
板子。- 对于
,主要是对于 和 操作过程中复制路径上经过的所有节点来支持可持久化。 - 因一般可支持的操作在
后操作完进行相应的 ,故仅对操作途中的 和 进行可持久化即可。 从上往下遍历的过程中沿途新建节点并复制历史节点即可。 因维护信息的差异,是否新建节点存在一定的不同。- 基础判定依据是若
中需要改变的都是 中新建的节点,就不需要再新建节点了。- 若将相同的关键字维护成不同的节点,若遇到删除操作,分裂出的一棵子树只有根节点通往最左边和最右边的两条链的是新的节点,如果不新建节点的话因按照随机权值合并的存在,实际访问的路径不一定再是
中新建的节点。 - 若将相同的关键字维护成不同的节点,可以保证真实改变的只有一个节点,而从这个节点到根的链上已经在
过程中新建了,故可以不用再进行新建。 - 分析详见 可持久化平衡树详解及实现方法分析 。
- 若将相同的关键字维护成不同的节点,若遇到删除操作,分裂出的一棵子树只有根节点通往最左边和最右边的两条链的是新的节点,如果不新建节点的话因按照随机权值合并的存在,实际访问的路径不一定再是
- 新建节点所需空间略大于不新建节点所需空间,应按照实际情况选择合适的写法。
- 基础判定依据是若
点击查看代码
struct PDS_BST { const int INF=2147483647; int root[500010],rt_sum; struct FHQ_Treap { int son[2],val,rnd,cnt,siz; }tree[500010<<6]; #define lson(rt) (tree[rt].son[0]) #define rson(rt) (tree[rt].son[1]) PDS_BST() { rt_sum=0; srand(time(0)); } void pushup(int rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+tree[rt].cnt; } int build_rt(int val) { rt_sum++; lson(rt_sum)=rson(rt_sum)=0; tree[rt_sum].val=val; tree[rt_sum].rnd=rand(); tree[rt_sum].cnt=tree[rt_sum].siz=1; return rt_sum; } int copy_rt(int rt) { rt_sum++; tree[rt_sum]=tree[rt]; return rt_sum; } void split(int rt,int val,int &x,int &y) { if(rt==0) { x=y=0; return; } if(tree[rt].val<=val) { x=copy_rt(rt); split(rson(rt),val,rson(x),y); pushup(x); } else { y=copy_rt(rt); split(lson(rt),val,x,lson(y)); pushup(y); } } int merge(int rt1,int rt2) { if(rt1==0||rt2==0) return rt1+rt2; int rt; if(tree[rt1].rnd<tree[rt2].rnd) { rt=copy_rt(rt1); rson(rt)=merge(rson(rt),rt2); } else { rt=copy_rt(rt2); lson(rt)=merge(rt1,lson(rt)); } pushup(rt); return rt; } void insert(int &root,int val) { int x,y; split(root,val,x,y); root=merge(merge(x,build_rt(val)),y); } void del(int &root,int val) { int x,y,rt; split(root,val,x,y); split(x,val-1,x,rt); root=merge(merge(x,merge(lson(rt),rson(rt))),y); } int kth_min(int rt,int k) { if(rt==0) return 0; if(k<=tree[lson(rt)].siz) return kth_min(lson(rt),k); if(tree[lson(rt)].siz+tree[rt].cnt<k) return kth_min(rson(rt),k-tree[lson(rt)].siz-tree[rt].cnt); return tree[rt].val; } int query_rk(int &root,int val)//因 merge 和 split 新建节点的需要,同样需要引用 { int x,y,ans; split(root,val-1,x,y); ans=tree[x].siz+1; root=merge(x,y); return ans; } int query_pre(int &root,int val) { int x,y,ans; split(root,val-1,x,y); ans=(x==0)?-INF:kth_min(x,tree[x].siz); root=merge(x,y); return ans; } int query_nxt(int &root,int val) { int x,y,ans; split(root,val,x,y); ans=(y==0)?INF:kth_min(y,1); root=merge(x,y); return ans; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,tim,pd,x,i; cin>>n; for(i=1;i<=n;i++) { cin>>tim>>pd>>x; T.root[i]=T.root[tim]; if(pd==1) T.insert(T.root[i],x); if(pd==2) T.del(T.root[i],x); if(pd==3) cout<<T.query_rk(T.root[i],x)<<endl; if(pd==4) cout<<T.kth_min(T.root[i],x)<<endl; if(pd==5) cout<<T.query_pre(T.root[i],x)<<endl; if(pd==6) cout<<T.query_nxt(T.root[i],x)<<endl; } return 0; }
- 对于
luogu P5055 【模板】可持久化文艺平衡树
-
因可持久化的需要,在
pushdown
的时候也需要新建节点。 -
因本题维护信息的特殊性,在
时不需要新建节点。点击查看代码
struct PDS_BST { ll root[200010],rt_sum; struct FHQ_Treap { ll son[2],rnd,cnt,siz,lazy,val,sum; }tree[200010<<6]; #define lson(rt) (tree[rt].son[0]) #define rson(rt) (tree[rt].son[1]) PDS_BST() { rt_sum=0; srand(time(0)); } void pushup(ll rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+tree[rt].cnt; tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum+tree[rt].val; } ll build_rt(ll val) { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].lazy=0; tree[rt_sum].val=tree[rt_sum].sum=val; tree[rt_sum].rnd=rand(); tree[rt_sum].cnt=tree[rt_sum].siz=1; return rt_sum; } ll copy_rt(ll rt) { rt_sum++; tree[rt_sum]=tree[rt]; return rt_sum; } void pushdown(ll rt) { if(tree[rt].lazy!=0) { if(lson(rt)!=0) lson(rt)=copy_rt(lson(rt)); if(rson(rt)!=0) rson(rt)=copy_rt(rson(rt)); swap(lson(rt),rson(rt)); if(lson(rt)!=0) tree[lson(rt)].lazy^=1; if(rson(rt)!=0) tree[rson(rt)].lazy^=1; tree[rt].lazy=0; } } void split(ll rt,ll k,ll &x,ll &y) { if(rt==0) { x=y=0; return; } pushdown(rt); if(tree[lson(rt)].siz+tree[rt].cnt<=k) { x=copy_rt(rt); split(rson(rt),k-tree[lson(rt)].siz-tree[rt].cnt,rson(x),y); pushup(x); } else { y=copy_rt(rt); split(lson(rt),k,x,lson(y)); pushup(y); } } ll merge(ll rt1,ll rt2) { if(rt1==0||rt2==0) return rt1+rt2; pushdown(rt1); pushdown(rt2); if(tree[rt1].rnd<tree[rt2].rnd) { rson(rt1)=merge(rson(rt1),rt2); pushup(rt1); return rt1; } else { lson(rt2)=merge(rt1,lson(rt2)); pushup(rt2); return rt2; } } void insert(ll &root,ll pos,ll val) { ll x,y; split(root,pos,x,y); root=merge(merge(x,build_rt(val)),y); } void del(ll &root,ll pos) { ll x,y,rt; split(root,pos,x,y); split(x,pos-1,x,rt); root=merge(x,y); } void reverse(ll &root,ll l,ll r) { ll x,y,rt; split(root,r,x,y); split(x,l-1,x,rt); tree[rt].lazy^=1; root=merge(merge(x,rt),y); } ll query(ll &root,ll l,ll r) { ll x,y,rt,ans; split(root,r,x,y); split(x,l-1,x,rt); ans=tree[rt].sum; root=merge(merge(x,rt),y); return ans; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,tim,pd,l,r,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>tim>>pd>>l; l^=ans; if(pd!=2) cin>>r; if(pd!=2) r^=ans; T.root[i]=T.root[tim]; if(pd==1) T.insert(T.root[i],l,r); if(pd==2) T.del(T.root[i],l); if(pd==3) T.reverse(T.root[i],l,r); if(pd==4) cout<<(ans=T.query(T.root[i],l,r))<<endl; } return 0; }
CF702F T-Shirts
-
先将物品按照
降序排序,人按照 升序排序。 -
考虑统计每个物品被多少个人买了,此时需要支持每次将序列内
的数减去 。 -
平衡树直接分裂出
貌似仍不能很好维护其相对顺序。 -
不妨将平衡树分裂成
三部分,其中对于 暴力修改并插入 中,对于 打上 标记。 -
由势能分析可知时间复杂度为
。点击查看代码
int ans[200010]; pair<int,int>a[200010]; bool cmp(pair<int,int>a,pair<int,int>b) { return (a.second==b.second)?(a.first<b.first):(a.second>b.second); } struct BST { int root,rt_sum=0; struct FHQ_Treap { int son[2],val,ans,id,rnd,cnt,siz,lazy_add,lazy_ans; }tree[200010]; #define lson(rt) (tree[rt].son[0]) #define rson(rt) (tree[rt].son[1]) BST() { rt_sum=0; srand(time(0)); } int build_rt(int val,int id) { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].ans=tree[rt_sum].lazy_add=tree[rt_sum].lazy_ans=0; tree[rt_sum].val=val; tree[rt_sum].id=id; tree[rt_sum].rnd=rand(); tree[rt_sum].cnt=tree[rt_sum].siz=1; return rt_sum; } void pushup(int rt) { tree[rt].siz=tree[lson(rt)].siz+tree[rson(rt)].siz+tree[rt].cnt; } void pushlazy(int rt,int lazy_add,int lazy_ans) { tree[rt].lazy_add+=lazy_add; tree[rt].val-=lazy_add; tree[rt].lazy_ans+=lazy_ans; tree[rt].ans+=lazy_ans; } void pushdown(int rt) { pushlazy(lson(rt),tree[rt].lazy_add,tree[rt].lazy_ans); pushlazy(rson(rt),tree[rt].lazy_add,tree[rt].lazy_ans); tree[rt].lazy_add=tree[rt].lazy_ans=0; } void split(int rt,int val,int &x,int &y) { if(rt==0) { x=y=0; return; } pushdown(rt); if(tree[rt].val<=val) { x=rt; split(rson(rt),val,rson(x),y); } else { y=rt; split(lson(rt),val,x,lson(y)); } pushup(rt); } int merge(int rt1,int rt2) { if(rt1==0||rt2==0) return rt1+rt2; pushdown(rt1); pushdown(rt2); if(tree[rt1].rnd<tree[rt2].rnd) { rson(rt1)=merge(rson(rt1),rt2); pushup(rt1); return rt1; } else { lson(rt2)=merge(rt1,lson(rt2)); pushup(rt2); return rt2; } } void insert(int val,int id) { int x,y; split(root,val,x,y); root=merge(merge(x,build_rt(val,id)),y); } void update(int rt,int &root,int c) { if(rt==0) return; pushdown(rt); update(lson(rt),root,c); update(rson(rt),root,c); lson(rt)=rson(rt)=0; pushlazy(rt,c,1); int x,y; split(root,tree[rt].val,x,y); root=merge(merge(x,rt),y); } void change(int c) { int x,y,z; split(root,c-1,x,y); split(y,2*c,y,z); pushlazy(z,c,1); update(y,x,c); root=merge(x,z); } void dfs(int rt) { if(rt==0) return; pushdown(rt); ans[tree[rt].id]=tree[rt].ans; dfs(lson(rt)); dfs(rson(rt)); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,x,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].first>>a[i].second; } cin>>m; for(i=1;i<=m;i++) { cin>>x; T.insert(x,i); } sort(a+1,a+1+n,cmp); for(i=1;i<=n;i++) { T.change(a[i].first); } T.dfs(T.root); for(i=1;i<=m;i++) { cout<<ans[i]<<" "; } return 0; }
AT_joi2012ho3 夜店 (Night Market)
-
背包
。点击查看代码
ll f[3010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,s,ans=0,a,b,i,j; cin>>n>>m>>s; for(i=1;i<=n;i++) { cin>>a>>b; for(j=m;j>=b;j--) { if(j-b>=s||j<=s) { f[j]=max(f[j],f[j-b]+a); } } } for(i=1;i<=m;i++) { ans=max(ans,f[i]); } cout<<ans<<endl; return 0; }
1.10
闲话
- 上午到机房后
说明、后两天再补 ,今天写构造专题。 - 下午放 @Abnormal123 的每日一歌《消愁》。
见我们构造专题仅有 @jijidawang 在 交了一发,问了下我们今天在写啥专题,得知昨天模拟赛题目过于困难,前置知识较多后说让我们赶紧改完后去写构造专题,让我们保障进度,还 了下博弈专题后面几个题怎么还有人没写。 说明天我们从第三节课再开始补 ,模拟赛也就不参加了。- 吃完晚饭后
问我们在班里的时候有单独时间吃水果吗,现在如果遇到校领导坐电梯来机房视察时看见众人都围在电梯口吃水果、零食我们怎么跟校领导解释。他说让我们说这是因集训每周单独拿出的时间来我们放松、聊聊天的,让我们保证尊敬对方的态度并不要跟对方起冲突、不要硬碰硬,万一出现问题(指教练晚上都因为有事没来机房,校领导来视察的时候看见都在楼道里闲聊,嫌我们状态差、教练不称职等)让我们跟对方解释清楚,反正也只是说一顿的事情。然后说距离省选就剩不到 天了,数学专题还要往后放一放,暑假讲了的但没练习的知识点和自己做题没见过的知识点比如点分治、网络流等要提上自己的进程,同时还要保证专题的进度;奇技淫巧。脑电波题要多积累。接着就问了下我们最近每周 的做题情况,说 直接从 题开始做就行了, 可以机房一起打(不等于开黑)来保证效率,但因为 accoders NOI 和学校 还有模拟赛,让我们以模拟赛和专题、知识点为主,周六晚上要是累了,想写写水题的话可以打打 ,平常的话建议多写写总结并保证效率。另外明天 accoders NOI 的模拟赛码量仍较大,且还是 赛制,让我们赶紧提升自己的代码能力。
做题纪要
CF757G Can Bash Save the Day?
-
按照 luogu P3241 [HNOI2015] 开店 的写法套用动态开点权值线段树空间开不下。
-
考虑将
拆成 ,难点在于如何求出 。 -
类比 luogu P5305 [GXOI/GZOI2019] 旧词 转化成链并的做法,使用主席树维护。
-
空间复杂度为
,因直接实现空间仍开不下,需要定期重构。点击查看代码
const int mod=1<<30; struct node { int nxt,to,w; }e[400010]; int head[200010],p[200010],c[200010],fa[200010],siz[200010],son[200010],dep[200010],dfn[200010],top[200010],pos[200010],tot=0,cnt=0,n; ll dis[200010],sum[200010],num[200010]; void add(int u,int v,int w) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].w=w; head[u]=cnt; } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { c[e[i].to]=e[i].w; dis[e[i].to]=dis[x]+e[i].w; dfs1(e[i].to,x); siz[x]+=siz[e[i].to]; son[x]=(siz[e[i].to]>siz[son[x]])?e[i].to:son[x]; } } } void dfs2(int x,int id) { top[x]=id; tot++; dfn[x]=tot; pos[tot]=x; if(son[x]!=0) { dfs2(son[x],id); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]&&e[i].to!=son[x]) { dfs2(e[i].to,e[i].to); } } } } struct PDS_SMT { int root[200010],rt_sum=0,limit; int ls[400005*90],rs[400005*90],lazy[400005*90]; ll val[400005*90]; #define lson(rt) (ls[rt]) #define rson(rt) (rs[rt]) int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=lazy[rt_sum]=val[rt_sum]=0; return rt_sum; } void update(int pre,int &rt,int l,int r,int x,int y) { if(pre<=limit) { rt=build_rt(); ls[rt]=ls[pre]; rs[rt]=rs[pre]; lazy[rt]=lazy[pre]; val[rt]=val[pre]; } val[rt]+=(sum[min(r,y)]-sum[max(l,x)-1]); if(x<=l&&r<=y) { lazy[rt]++; return; } int mid=(l+r)/2; if(x<=mid) update(lson(pre),lson(rt),l,mid,x,y); if(y>mid) update(rson(pre),rson(rt),mid+1,r,x,y); } ll query(int rt1,int rt2,int l,int r,int x,int y) { if(x<=l&&r<=y) { return val[rt2]-val[rt1]; } int mid=(l+r)/2; ll ans=(lazy[rt2]-lazy[rt1])*(sum[min(r,y)]-sum[max(l,x)-1]); if(x<=mid) ans+=query(lson(rt1),lson(rt2),l,mid,x,y); if(y>mid) ans+=query(rson(rt1),rson(rt2),mid+1,r,x,y); return ans; } }T; void update(int id,int u) { T.limit=T.rt_sum; while(top[u]) { T.update(T.root[id],T.root[id],1,n,dfn[top[u]],dfn[u]); u=fa[top[u]]; } } ll query(int rt1,int rt2,int u) { ll ans=0; while(top[u]) { ans+=T.query(rt1,rt2,1,n,dfn[top[u]],dfn[u]); u=fa[top[u]]; } return ans; } void rebuild() { T.rt_sum=0; for(int i=1;i<=n;i++) { T.root[i]=T.root[i-1]; update(i,p[i]); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,u,v,w,pd,cnt=0,i; ll l,r,x,ans=0; cin>>n>>m; for(i=1;i<=n;i++) { cin>>p[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } dfs1(1,0); dfs2(1,1); for(i=1;i<=n;i++) { sum[i]=sum[i-1]+c[pos[i]]; num[i]=num[i-1]+dis[p[i]]; } rebuild(); for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { cin>>l>>r>>x; l^=ans; r^=ans; x^=ans; ans=dis[x]*(r-l+1)+num[r]-num[l-1]-2*query(T.root[l-1],T.root[r],x); cout<<ans<<endl; ans%=mod; } else { cin>>x; x^=ans; swap(p[x],p[x+1]); num[x]=num[x-1]+dis[p[x]]; cnt++; if(cnt==120000) { cnt=0; rebuild(); } else { T.root[x]=T.root[x-1]; update(x,p[x]); } } } return 0; }
[AGC010E] Rearranging
luogu P8207 [THUPC2022 初赛] 最小公倍树
-
考虑最小化
的影响。 -
考虑枚举公因数
,从 向 内其他 的倍数连边。点击查看代码
struct node { ll from,to,w; }; vector<node>e; bool cmp(node a,node b) { return a.w<b.w; } ll lcm(ll a,ll b) { return a/__gcd(a,b)*b; } struct DSU { ll fa[1000010]; void init(ll l,ll r) { for(ll i=l;i<=r;i++) { fa[i]=i; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; ll kruskal(ll l,ll r) { D.init(l,r); sort(e.begin(),e.end(),cmp); ll ans=0; for(ll i=0;i<e.size();i++) { ll x=D.find(e[i].from),y=D.find(e[i].to); if(x!=y) { ans+=e[i].w; D.fa[x]=y; } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll l,r,x,i,j; cin>>l>>r; for(i=1;i<=r;i++) { x=ceil(1.0*l/i)*i; for(j=x+i;j<=r;j+=i) { e.push_back((node){x,j,lcm(x,j)}); } } cout<<kruskal(l,r)<<endl; return 0; }
luogu P2757 [国家集训队] 等差子序列
-
多倍经验: CF452F Permutation
-
考虑枚举中间项
和差值 ,有解当且仅当 在 左侧且 在 右侧。 -
由于是排列,不妨在值域上考虑这个问题,每扫到一个
就将 的状态记为 ,此时等价于询问是否只有 或 的状态为 。 -
正难则反,转化为是否存在以
为中心的回文子串,线段树加哈希判断。点击查看代码
const ull base=13331; int a[500010],n; ull jc[500010]; struct SMT { struct SegmentTree { int len; ull hsh1,hsh2; }tree[2000010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { tree[rt].hsh1=tree[lson(rt)].hsh1*jc[tree[rson(rt)].len]+tree[rson(rt)].hsh1; tree[rt].hsh2=tree[rson(rt)].hsh2*jc[tree[lson(rt)].len]+tree[lson(rt)].hsh2; } void build(int rt,int l,int r) { tree[rt].len=r-l+1; tree[rt].hsh1=tree[rt].hsh2=0; if(l==r) { return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void update(int rt,int l,int r,int pos) { if(l==r) { tree[rt].hsh1=tree[rt].hsh2=1; return; } int mid=(l+r)/2; if(pos<=mid) update(lson(rt),l,mid,pos); else update(rson(rt),mid+1,r,pos); pushup(rt); } pair<int,ull> query1(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return make_pair(tree[rt].len,tree[rt].hsh1); } int mid=(l+r)/2; pair<int,ull> p=make_pair(0,0),q=make_pair(0,0); if(x<=mid) p=query1(lson(rt),l,mid,x,y); if(y>mid) q=query1(rson(rt),mid+1,r,x,y); return make_pair(p.first+q.first,p.second*jc[q.first]+q.second); } pair<int,ull> query2(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return make_pair(tree[rt].len,tree[rt].hsh2); } int mid=(l+r)/2; pair<int,ull> p=make_pair(0,0),q=make_pair(0,0); if(x<=mid) p=query2(lson(rt),l,mid,x,y); if(y>mid) q=query2(rson(rt),mid+1,r,x,y); return make_pair(p.first+q.first,q.second*jc[p.first]+p.second); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,ans,len,i,j; cin>>t; jc[0]=1; for(i=1;i<=500000;i++) { jc[i]=jc[i-1]*base; } for(j=1;j<=t;j++) { cin>>n; ans=0; T.build(1,1,n); for(i=1;i<=n;i++) { cin>>a[i]; len=min(a[i],n-a[i]+1); T.update(1,1,n,a[i]); ans|=(T.query1(1,1,n,a[i]-len+1,a[i]+len-1)!=T.query2(1,1,n,a[i]-len+1,a[i]+len-1)); } cout<<((ans==1)?"Y":"N")<<endl; } return 0; }
[AGC005E] Sugigma: The Showdown
SP32079 ADAGF - Ada and Greenflies
-
和 CF475D CGCDSSQ 一样做即可。
点击查看代码
struct node { ll val,l,r; }g[300010]; ll a[300010],cnt_g; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,len=0,i,j; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } for(i=1;i<=n;i++) { for(j=1;j<=cnt_g;j++) { g[j].val=gcd(g[j].val,a[i]); } cnt_g++; g[cnt_g]=(node){a[i],i,i}; len=0; for(j=1;j<=cnt_g;j++) { if(g[j].val==g[j-1].val) { g[len].r=g[j].r; } else { len++; g[len]=g[j]; } } cnt_g=len; for(j=1;j<=cnt_g;j++) { ans+=g[j].val*(g[j].r-g[j].l+1); } } cout<<ans<<endl; return 0; }
CF875E Delivery Club
-
二分答案,维护快递员可能出现的区间。
点击查看代码
int x[100010]; set<int>s; bool check(int mid,int n,int s1) { s.clear(); s.insert(s1); for(int i=0;i<=n;i++) { s.erase(s.begin(),s.lower_bound(x[i]-mid)); s.erase(s.upper_bound(x[i]+mid),s.end()); if(s.size()==0) return false; s.insert(x[i]); } return true; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,s1,s2,l=0,r=1000000000,ans=0,mid,i; cin>>n>>s1>>s2; x[0]=s2; for(i=1;i<=n;i++) { cin>>x[i]; } while(l<=r) { mid=(l+r)/2; if(check(mid,n,s1)==true) { ans=mid; r=mid-1; } else { l=mid+1; } } cout<<ans<<endl; return 0; }
luogu P6406 [COCI2014-2015#2] Norma
-
多倍经验: SP22343 NORMA2 - Norma
-
将
拆成 的形式,线段树维护历史版本和。点击查看代码
const ll p=1000000000; int a[500010]; stack<int>s1,s2; struct SMT { struct SegmentTree { int len,sum,s[2],t[2],h[2],lazy[2]; }tree[2000010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { for(int i=0;i<=1;i++) { tree[rt].s[i]=(tree[lson(rt)].s[i]+tree[rson(rt)].s[i])%p; tree[rt].t[i]=(tree[lson(rt)].t[i]+tree[rson(rt)].t[i])%p; tree[rt].h[i]=(tree[lson(rt)].h[i]+tree[rson(rt)].h[i])%p; } } void build(int rt,int l,int r) { tree[rt].len=r-l+1; tree[rt].sum=1ll*(r-l+1)*(l+r)/2%p; if(l==r) return; int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); } void pushlazy(int rt,int lazy,int op) { tree[rt].s[op]=(tree[rt].s[op]+1ll*tree[rt].len*lazy%p)%p; tree[rt].t[op]=(tree[rt].t[op]+1ll*tree[rt].sum*lazy%p)%p; tree[rt].h[0]=(tree[rt].h[0]+1ll*tree[rt].s[op^1]*lazy%p)%p; tree[rt].h[1]=(tree[rt].h[1]+1ll*tree[rt].t[op^1]*lazy%p)%p; tree[rt].lazy[op]=(tree[rt].lazy[op]+lazy)%p; } void pushdown(int rt) { for(int i=0;i<=1;i++) { if(tree[rt].lazy[i]!=0) { pushlazy(lson(rt),tree[rt].lazy[i],i); pushlazy(rson(rt),tree[rt].lazy[i],i); tree[rt].lazy[i]=0; } } } void update(int rt,int l,int r,int x,int y,int val,int op) { if(x<=l&&r<=y) { pushlazy(rt,val,op); return; } pushdown(rt); int mid=(l+r)/2; if(x<=mid) update(lson(rt),l,mid,x,y,val,op); if(y>mid) update(rson(rt),mid+1,r,x,y,val,op); pushup(rt); } int query(int r) { return (1ll*tree[1].h[0]*(r+1)%p-tree[1].h[1]+p)%p; } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,tmp,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } T.build(1,1,n); for(i=1;i<=n;i++) { while(s1.empty()==0&&a[s1.top()]<=a[i]) { tmp=s1.top(); s1.pop(); T.update(1,1,n,(s1.empty()==0?s1.top():0)+1,tmp,a[i]-a[tmp],0); } s1.push(i); T.update(1,1,n,i,i,a[i],0); while(s2.empty()==0&&a[s2.top()]>=a[i]) { tmp=s2.top(); s2.pop(); T.update(1,1,n,(s2.empty()==0?s2.top():0)+1,tmp,(a[i]-a[tmp]+p)%p,1); } s2.push(i); T.update(1,1,n,i,i,a[i],1); ans=(ans+T.query(i))%p; } cout<<ans<<endl; return 0; }
[ABC282Ex] Min + Sum
-
启发式分裂,需要维护区间内
某个数的个数,直接二分。点击查看代码
ll a[200010],b[200010],sum[200010],l[200010],r[200010]; stack<ll>s; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,ans=0,i,j; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; while(s.empty()==0&&a[s.top()]>a[i]) s.pop(); l[i]=(s.empty()==0)?s.top()+1:1; s.push(i); } while(s.empty()==0) s.pop(); for(i=n;i>=1;i--) { while(s.empty()==0&&a[s.top()]>=a[i]) s.pop(); r[i]=(s.empty()==0)?s.top()-1:n; s.push(i); } for(i=1;i<=n;i++) { cin>>b[i]; sum[i]=sum[i-1]+b[i]; } for(i=1;i<=n;i++) { if(i-l[i]+1<=r[i]-i+1) { for(j=l[i];j<=i;j++) { ans+=upper_bound(sum+i,sum+r[i]+1,m-a[i]+sum[j-1])-(sum+i); } } else { for(j=r[i];j>=i;j--) { ans+=(i-l[i]-1)-(lower_bound(sum+l[i]-1,sum+i,sum[j]+a[i]-m)-(sum+l[i])-1); } } } cout<<ans<<endl; return 0; }
CF1609F Interesting Sections
-
序列分治板子。
- 序列分治通常用于解决子区间贡献的问题。核心思想仍是分治,尝试快速统计经过
的子区间的贡献。 - 以本题中的
为例,维护 的前缀 数组 和 的后缀 数组 。 - 从右往左枚举左端点
,设右端点为 ,考虑找到一个分界点 使得 时 且 时 。观察到随着 的减小 单调不降,可以直接继承转移。 - 通常需要桶、平衡树等数据结构存储符合某一限制条件的数的个数辅助进行贡献统计。
- 序列分治通常用于解决子区间贡献的问题。核心思想仍是分治,尝试快速统计经过
-
设
和 的分节点分别为 。 -
对于
,若 ,则将 加入答案。 -
对于
,以 为例,需要统计 的数量,移动指针的过程中开桶维护 的个数即可。 -
对于
,需要统计 的数量,前缀和统计即可。点击查看代码
ll a[1000010],sum[1000010],cnt[2][70],pre_max[1000010],pre_min[1000010],ans=0; void solve(ll l,ll r) { if(l==r) { ans++; return; } ll mid=(l+r)/2,suf_max=0,suf_min=0x7f7f7f7f7f7f7f7f; memset(cnt,0,sizeof(cnt)); pre_max[mid+1]=pre_min[mid+1]=a[mid+1]; sum[mid+1]=1; for(ll i=mid+2;i<=r;i++) { pre_max[i]=max(pre_max[i-1],a[i]); pre_min[i]=min(pre_min[i-1],a[i]); sum[i]=sum[i-1]+(__builtin_popcountll(pre_max[i])==__builtin_popcountll(pre_min[i])); } for(ll i=mid,k1=mid,k2=mid;i>=l;i--) { suf_max=max(suf_max,a[i]); suf_min=min(suf_min,a[i]); while(k1+1<=r&&suf_max>=pre_max[k1+1]&&suf_min<=pre_min[k1+1]) { k1++; cnt[0][__builtin_popcountll(pre_max[k1])]--; cnt[1][__builtin_popcountll(pre_min[k1])]--; } while(k2+1<=r&&(suf_max>=pre_max[k2+1]||suf_min<=pre_min[k2+1])) { k2++; cnt[0][__builtin_popcountll(pre_max[k2])]++; cnt[1][__builtin_popcountll(pre_min[k2])]++; } ans+=(__builtin_popcountll(suf_max)==__builtin_popcountll(suf_min))*(k1-mid); ans+=(suf_min<=pre_min[k2]?cnt[0][__builtin_popcountll(suf_min)]:cnt[1][__builtin_popcountll(suf_max)]); ans+=sum[r]-sum[k2]; } solve(l,mid); solve(mid+1,r); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,i; scanf("%lld",&n); for(i=1;i<=n;i++) { scanf("%lld",&a[i]); } solve(1,n); printf("%lld\n",ans); return 0; }
CF873B Balanced Substring
-
维护
前缀和出现次数的差分数组,map
存储最左边出现的位置。点击查看代码
int s[100010]; map<int,int>f; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,ans=0,i; char c; cin>>n; f[0]=0; for(i=1;i<=n;i++) { cin>>c; s[i]=s[i-1]+(c=='0'?-1:1); if(f.find(s[i])==f.end()) f[s[i]]=i; else ans=max(ans,i-f[s[i]]); } cout<<ans<<endl; return 0; }
AT_joisc2014_h JOIOJI
-
仍考虑维护差分数组,分成两部分即可。
点击查看代码
pair<int,int>s[200010]; map<pair<int,int>,int>f; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,ans=0,i; char c; cin>>n; f[make_pair(0,0)]=0; for(i=1;i<=n;i++) { cin>>c; if(c=='J') s[i]=make_pair(s[i-1].first+1,s[i-1].second+1); if(c=='O') s[i]=make_pair(s[i-1].first-1,s[i-1].second); if(c=='I') s[i]=make_pair(s[i-1].first,s[i-1].second-1); if(f.find(s[i])==f.end()) f[s[i]]=i; else ans=max(ans,i-f[s[i]]); } cout<<ans<<endl; return 0; }
CF549F Yura and Developers
-
分治的过程中维护前缀和
的桶和前缀和减前缀 后的值 的桶,同余方程移项即可。 -
指针移动的过程中维护两者。
点击查看代码
int a[300010],sum[300010],pre[300010],cnt[2][1000010],p; ll ans=0; void solve(int l,int r) { if(l==r) return; int mid=(l+r)/2,suf_max=0,suf_sum=0; pre[mid+1]=a[mid+1]; sum[mid+1]=a[mid+1]%p; cnt[0][0]=1; for(int i=mid+2;i<=r;i++) { pre[i]=max(pre[i-1],a[i]); sum[i]=(sum[i-1]+a[i])%p; cnt[0][(pre[i]%p-sum[i]+p)%p]++; } for(int i=mid,k=mid;i>=l;i--) { suf_max=max(suf_max,a[i]); suf_sum=(suf_sum+a[i])%p; while(k+1<=r&&suf_max>=pre[k+1]) { k++; cnt[0][(pre[k]%p-sum[k]+p)%p]--; cnt[1][sum[k]]++; } ans+=cnt[1][(suf_max%p-suf_sum+p)%p]; ans+=cnt[0][suf_sum]; } for(int i=mid+1;i<=r;i++) cnt[0][(pre[i]%p-sum[i]+p)%p]=cnt[1][sum[i]]=0; solve(l,mid); solve(mid+1,r); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,i; cin>>n>>p; for(i=1;i<=n;i++) { cin>>a[i]; } solve(1,n); cout<<ans<<endl; return 0; }
[ABC248Ex] Beautiful Subsequences
-
类似 CF1609F Interesting Sections ,考虑分成三部分分别统计答案,树状数组维护。
点击查看代码
int a[140010],pre_max[140010],pre_min[140010],n,m; ll ans=0; struct BIT { int c[500010]; int lowbit(int x) { return (x&(-x)); } void add(int n,int x,int val) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]+=val; } } void del(int n,int x) { for(int i=x;i<=n;i+=lowbit(i)) { c[i]=0; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }T[3]; void solve(int l,int r) { if(l==r) { ans++; return; } int mid=(l+r)/2,suf_max=0,suf_min=0x7f7f7f7f; pre_max[mid+1]=pre_min[mid+1]=a[mid+1]; for(int i=mid+2;i<=r;i++) { pre_max[i]=max(pre_max[i-1],a[i]); pre_min[i]=min(pre_min[i-1],a[i]); } for(int i=mid+1;i<=r;i++) T[0].add(500000,pre_max[i]-pre_min[i]-i+300000,1); for(int i=mid,k1=mid,k2=mid;i>=l;i--) { suf_max=max(suf_max,a[i]); suf_min=min(suf_min,a[i]); while(k1+1<=r&&suf_max>=pre_max[k1+1]&&suf_min<=pre_min[k1+1]) { k1++; T[1].add(500000,pre_max[k1]-k1+300000,-1); T[2].add(500000,-k1-pre_min[k1]+300000,-1); } while(k2+1<=r&&(suf_max>=pre_max[k2+1]||suf_min<=pre_min[k2+1])) { k2++; T[0].add(500000,pre_max[k2]-pre_min[k2]-k2+300000,-1); T[1].add(500000,pre_max[k2]-k2+300000,1); T[2].add(500000,-k2-pre_min[k2]+300000,1); } ans+=max(0,k1-max(mid+1,suf_max-suf_min-m+i)+1); ans+=T[0].getsum(m-i+300000); ans+=(suf_min<=pre_min[k2])?T[1].getsum(suf_min-i+m+300000):T[2].getsum(-suf_max-i+m+300000); } for(int i=mid+1;i<=r;i++) T[0].del(500000,pre_max[i]-pre_min[i]-i+300000); for(int i=mid+1;i<=r;i++) T[1].del(500000,pre_max[i]-i+300000); for(int i=mid+1;i<=r;i++) T[2].del(500000,-i-pre_min[i]+300000); solve(l,mid); solve(mid+1,r); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif cin>>n>>m; for(int i=1;i<=n;i++) { cin>>a[i]; } solve(1,n); cout<<ans<<endl; return 0; }
CF1834E MEX of LCM
-
显然有答案上界为
,取极限数据下第 个质数 作为答案上界。 -
又因为
每次变大至少会翻倍,故对于每个左端点有用的 只有 个,使用set
维护有用的 即可,写法和 luogu P5502 [JSOI2015] 最大公约数 差不多。点击查看代码
ll a[300010]; set<ll>ans,s,tmp; set<ll>::iterator it; ll lcm(ll a,ll b) { return a/__gcd(a,b)*b; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll t,n,sum,i,j; cin>>t; for(j=1;j<=t;j++) { ans.clear(); s.clear(); cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; ans.insert(a[i]); } for(i=1;i<=n;i++) { tmp.clear(); tmp.insert(a[i]); for(it=s.begin();it!=s.end();it++) { if(lcm(*it,a[i])<=4300000) { tmp.insert(lcm(*it,a[i])); ans.insert(lcm(*it,a[i])); } } s.swap(tmp); } for(sum=1;ans.find(sum)!=ans.end();sum++); cout<<sum<<endl; } return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18646272,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示