高一上十二月下旬日记
12.21
闲话
- 侯操时得知今天又有人来
参观研学,让我们课间操好好跑,跑出 \(HZ\) 的风采;问班里的同学得知他们仅在周六跑课间操。 - 回班上早读后得知英语在信奥的“大力参与”下班级平均分仍保持在 \(132pts\) 左右,对班里其他同学的有英语分数震惊到了。
- 早上到机房后,找 \(field\) 看了眼三调的成绩,语文 \(102pts\) ,数学 \(116pts\) ,英语 \(117pts\) ,物理 \(86pts\) ,化学 \(77pts\) ,生物 \(72pts\) ,信奥内部 \(rk6\) ,班级 \(rk48\) ,九科年级 \(rk5013\) ,理科年级 \(rk2156\)。
- 上午听多校的讲图论,讲到菠萝的时候掉线遂去打题了,然后 \(miaomiao\) 过来问我咋没接着听课。
- 下午不知道为啥没放每日一歌;听多校的讲网络流,全程掉线,到 \(17:20\) 的时候主讲人还没有停下来的意思,遂 \(field\) 说等他讲完课我们再上体活,等 \(17:30\) 的时候他紧急停止了,还说剩下的东西让我们自己看看就行了。
做题纪要
LibreOJ 121. 「离线可过」动态图连通性
-
线段树分治板子。
点击查看代码
int u[500010],v[500010],w[500010],st[500010],ed[500010],ans[500010]; pair<int,int>e[500010]; map<pair<int,int>,int>f; struct quality { int id,fa,siz; }; struct DSU { int fa[500010],siz[500010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } int find(int x) { return fa[x]==x?x:find(fa[x]); } void merge(int x,int y,stack<quality>&s) { x=find(x); y=find(y); if(x!=y) { s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); if(siz[x]<siz[y]) { swap(x,y); } fa[y]=x; siz[x]+=siz[y]; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().id]=s.top().fa; siz[s.top().id]=s.top().siz; s.pop(); } } }D; struct SMT { struct SegmentTree { vector<int>info; }tree[2000010]; int lson(int x) { return x*2; } int rson(int x) { return x*2+1; } void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { tree[rt].info.push_back(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); } } void solve(int rt,int l,int r) { stack<quality>s; int mid=(l+r)/2; for(int i=0;i<tree[rt].info.size();i++) { D.merge(u[tree[rt].info[i]],v[tree[rt].info[i]],s); } if(l==r) { ans[l]=(D.find(e[l].first)==D.find(e[l].second)); } else { solve(lson(rt),l,mid); solve(rson(rt),mid+1,r); } D.split(s); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m=0,q,pd,tim=0,x,y,i; cin>>n>>q; for(i=1;i<=q;i++) { cin>>pd; if(pd==0) { m++; cin>>u[m]>>v[m]; f[make_pair(u[m],v[m])]=f[make_pair(v[m],u[m])]=m; st[m]=tim+1; ed[m]=-1; } if(pd==1) { cin>>x>>y; ed[f[make_pair(x,y)]]=tim; } if(pd==2) { tim++; cin>>e[tim].first>>e[tim].second; } } if(tim!=0) { for(i=1;i<=m;i++) { ed[i]=(ed[i]==-1)?tim:ed[i]; if(st[i]<=ed[i]) { T.update(1,1,tim,st[i],ed[i],i); } } D.init(n); T.solve(1,1,tim); for(i=1;i<=tim;i++) { cout<<(ans[i]==1?"Y":"N")<<endl; } } return 0; }
luogu P11363 [NOIP2024] 树的遍历
-
观察到遍历边的过程实际上是第一次到达 \(x\) 后对于剩下的 \(du_{x}-1\) 条边以一条链的形式走完,中途可能会有其他的边加入但不影响这条链上只有这 \(du_{x}-1\) 条边。故可以得到 \(k=1\) 时答案为 \(\prod\limits_{i=1}^{n}(du_{i}-1)!\) 。
-
同时发现若两条边均可以作为同一棵新树的根节点,那么原树上这两条边之间的所有边都可以作为根节点生成这棵新树。
-
对于一个由关键边组成的边集,若存在一棵新树使其集合中的边均能作为这棵新树上的根节点,则有这些关键边在原树上必然能形成一条链。
-
设 \(g_{i}\) 表示所有链上有 \(i\) 条关键边的链(链头和链尾必须都是关键边)能构成多少棵新树,容斥完有 \(\sum\limits_{i=1}^{k}(-1)^{i+1}g_{i}\) 即为所求。
-
对于一条有 \(l\) 条关键边的链 \(S\) ,其对 \(g_{k}\) 的贡献为 \(\dbinom{l-2}{k-2}\prod\limits_{i=1}^{n}(du_{i}-1)! \times \prod\limits_{i \in S}\frac{1}{du_{i}-1}\) ,对答案的贡献为 \(\sum\limits_{k=2}^{l}(-1)^{k+1}\dbinom{l-2}{k-2}\prod\limits_{i=1}^{n}(du_{i}-1)! \times \prod\limits_{i \in S}\frac{1}{du_{i}-1}\) ,提出前面 \(\sum\limits_{k=2}^{l}(-1)^{k+1}\dbinom{l-2}{k-2}\) 的系数并改写后得到 \(\sum\limits_{k=0}^{l-2}(-1)^{k+2}\dbinom{l-2}{k}=\sum\limits_{k=0}^{l-2}(-1)^{k}\dbinom{l-2}{k}=[l-2=0]\) 。所以就可以只考虑 \(l=2\) 的情况了,其系数为 \(1\) 。
-
接着考虑容斥,设 \(f_{x}\) 表示以 \(x\) 为根的子树中恰好存在一个链的端点的贡献和,状态转移方程为 \(f_{x}=\begin{cases} -1+\frac{1}{du_{i}-1}\sum\limits_{y \in Son(x)}f_{y}-\frac{1}{du_{i}-1}\sum\limits_{y \in Son(x)}f_{y}=-1 & (fa_{x},x) 是关键边 \\ \frac{1}{du_{i}-1}\sum\limits_{y \in Son(x)}f_{y} & (fa_{x},x) 不是关键边 \end{cases}\) ,其中 \((fa_{x},x)\) 是关键边的三个式子分别对应只选 \((fa_{x},x)\) ,只选子树内部的边,两者都选三种情况。
-
统计答案时类似地合并链上的贡献即可。
点击查看代码
const ll p=1000000007; struct node { ll nxt,to,id; }e[200010]; ll head[200010],du[200010],jc[200010],vis[200010],inv[200010],f[200010],ans=0,cnt=0; void add(ll u,ll v,ll id) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; e[cnt].id=id; head[u]=cnt; } void dfs(ll x,ll fa,ll w) { ll sum=-w; f[x]=-w; ans+=w; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa) { dfs(e[i].to,x,vis[e[i].id]); f[x]=(f[x]+(w==0)*inv[du[x]-1]*f[e[i].to]%p)%p; ans=(ans-inv[du[x]-1]*sum%p*f[e[i].to]%p+p)%p; sum=(sum+f[e[i].to])%p; } } } int main() { // #define Isaac #ifdef Isaac freopen("traverse.in","r",stdin); freopen("traverse.out","w",stdout); #endif ll c,t,n,k,u,v,i,j; scanf("%lld%lld",&c,&t); jc[0]=jc[1]=inv[0]=inv[1]=1; for(i=2;i<=100000;i++) { jc[i]=jc[i-1]*i%p; inv[i]=(p-p/i)*inv[p%i]%p; } for(j=1;j<=t;j++) { ans=cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(du,0,sizeof(du)); memset(vis,0,sizeof(vis)); scanf("%lld%lld",&n,&k); for(i=1;i<=n-1;i++) { scanf("%lld%lld",&u,&v); add(u,v,i); add(v,u,i); du[u]++; du[v]++; } for(i=1;i<=k;i++) { scanf("%lld",&c); vis[c]=1; } dfs(1,0,0); for(i=1;i<=n;i++) { ans=ans*jc[du[i]-1]%p; } printf("%lld\n",ans); } return 0; }
luogu P7880 [Ynoi2006] rldcot
-
直接在区间虚树上数颜色不太好做。
-
点 \(x\) 会被询问区间 \([l,r]\) 的虚树包含当且仅当 \(x \in [l,r]\) 或存在 \(u,v \in [l,r]\) 且分别属于 \(x\) 的不同子树内。
-
不妨仅考虑包含 \(x\) 的一些极短区间/点对数,即 \(\forall y \in Son(x),u \in Subtree(y),u\) 在 \(Subtree(x)\) 中除 \(Subtree(y)\) 中的前驱 \(pre\) 后继 \(suf\) 构成的点对 \((pre,u),(u,suf)\),由树上启发式合并可知点对数规模是 \(O(n \log n)\) 。
-
扫描线的过程中记录每种颜色最后一次出现的位置即可。
点击查看代码
struct node { int nxt,to,w; }e[200010]; int head[200010],ans[200010],last[200010],cnt=0; ll d[200010]; vector<pair<int,int> >q[200010],c[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; } struct BIT { int c[200010]; int lowbit(int x) { return (x&(-x)); } void add(int x,int val) { for(int i=x;i>=1;i-=lowbit(i)) { c[i]+=val; } } int getsum(int n,int x) { int ans=0; for(int i=x;i<=n;i+=lowbit(i)) { ans+=c[i]; } return ans; } }T; struct DSU_On_Tree { ll dis[200010]; int siz[200010],fa[200010],son[200010],dfn[200010],out[200010],pos[200010],tot=0; set<int>s[200010]; void add_rt(int x,int rt) { s[rt].insert(x); } void work_rt(int x,int rt) { set<int>::iterator it=s[rt].upper_bound(x); if(*it!=0x7f7f7f7f) { c[*it].push_back(make_pair(x,dis[rt])); } it=--s[rt].lower_bound(x); if(*it!=0) { c[x].push_back(make_pair(*it,dis[rt])); } } void add_tree(int x,int rt) { for(int i=dfn[x];i<=out[x];i++) { add_rt(pos[i],rt); } } void work_tree(int x,int rt) { for(int i=dfn[x];i<=out[x];i++) { work_rt(pos[i],rt); } } void del_tree(int x) { s[x].clear(); } void dfs1(int x,int father) { tot++; dfn[x]=tot; pos[tot]=x; siz[x]=1; fa[x]=father; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=father) { d[e[i].to]=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]; } } out[x]=tot; } void dfs2(int x,int flag) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { dfs2(e[i].to,0); } } if(son[x]!=0) { dfs2(son[x],1); } swap(s[x],s[son[x]]); s[x].insert(0); s[x].insert(0x7f7f7f7f); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=son[x]&&e[i].to!=fa[x]) { work_tree(e[i].to,x); add_tree(e[i].to,x); } } add_rt(x,x); c[x].push_back(make_pair(x,dis[x])); if(flag==0) { del_tree(x); } } }D; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,w,l,r,i,j; scanf("%d%d",&n,&m); for(i=1;i<=n-1;i++) { scanf("%d%d%d",&u,&v,&w); add(u,v,w); add(v,u,w); } D.dfs1(1,0); sort(d+1,d+1+n); d[0]=unique(d+1,d+1+n)-(d+1); for(i=1;i<=n;i++) { D.dis[i]=lower_bound(d+1,d+1+d[0],D.dis[i])-d; } D.dfs2(1,1); for(i=1;i<=m;i++) { scanf("%d%d",&l,&r); q[r].push_back(make_pair(l,i)); } for(i=1;i<=n;i++) { for(j=0;j<c[i].size();j++) { if(c[i][j].first>last[c[i][j].second]) { T.add(last[c[i][j].second],-1); last[c[i][j].second]=c[i][j].first; T.add(last[c[i][j].second],1); } } for(j=0;j<q[i].size();j++) { ans[q[i][j].second]=T.getsum(n,q[i][j].first); } } for(i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; }
luogu P9963 [THUPC 2024 初赛] 前缀和
-
\(x_{i}\) 的实际意义可以理解为在一排每个灯都有 \(p\) 的概率被点亮的情况下两个点亮灯之间的距离。
-
再做一遍前缀和后有 \(y_{i}\) 可以视作第 \(i\) 盏灯点亮的位置,故 \((r-l+1)p\) 即为所求。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,l,r; double p; cin>>n>>p>>l>>r; cout<<p*(r-l+1)<<endl; return 0; }
luogu P11364 [NOIP2024] 树上查询
-
特判掉 \(k=1\) 的情况后有 \(\max\limits_{[l,l+k-1] \in [L,R]} \{ \min\limits_{i=l}^{l+k} \{ dep_{\operatorname{LCA}(i,i+1)} \} \}\) 即为所求。
-
然后让 \(r,k\) 都先减一避免对区间开闭的处理。
-
单调栈预处理出 \(dep_{\operatorname{LCA}(i,i+1)}\) 作为最小值后的极长区间 \([l_{i},r_{i}]\) 后对答案能产生贡献当且仅当 \(l_{i}<L \land r_{i} \ge L+k-1\) 或 \(L \le l_{i} \le R-k+1 \land r_{i}-l_{i}+1 \ge k\) ,分别对 \(L,k\) 进行扫描线即可。
点击查看代码
struct quality { int l,r,k,id; }p[500010],q[500010]; struct node { int nxt,to; }e[1000010]; int head[500010],siz[500010],fa[500010],top[500010],dep[500010],son[500010],ans[500010],cnt=0; stack<int>s; 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!=fa[x]) { 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 cmp1(quality a,quality b) { return a.l<b.l; } bool cmp2(quality a,quality b) { return a.k>b.k; } bool cmp3(quality a,quality b) { return a.r-a.l+1>b.r-b.l+1; } struct SMT { struct SegmentTree { int maxx; }tree[2000010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx); } void clear() { memset(tree,0,sizeof(tree)); } void build(int rt,int l,int r) { if(l==r) { tree[rt].maxx=dep[l]; 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].maxx=max(tree[rt].maxx,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].maxx; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans=max(ans,query(lson(rt),l,mid,x,y)); } if(y>mid) { ans=max(ans,query(rson(rt),mid+1,r,x,y)); } return ans; } }T; int main() { // #define Isaac #ifdef Isaac freopen("query.in","r",stdin); freopen("query.out","w",stdout); #endif int n,m,u,v,q_cnt=0,i,j; scanf("%d",&n); for(i=1;i<=n-1;i++) { scanf("%d%d",&u,&v); p[i].id=i; add(u,v); add(v,u); } dfs1(1,0); dfs2(1,1); for(i=1;i<=n-1;i++) { p[i].k=dep[lca(i,i+1)]; while(s.empty()==0&&p[s.top()].k>=p[i].k) { s.pop(); } p[i].l=(s.empty()==0)?s.top()+1:1; s.push(i); } while(s.empty()==0) { s.pop(); } for(i=n-1;i>=0;i--) { while(s.empty()==0&&p[s.top()].k>=p[i].k) { s.pop(); } p[i].r=(s.empty()==0)?s.top()-1:n-1; s.push(i); } T.build(1,1,n); scanf("%d",&m); for(i=1;i<=m;i++) { q_cnt++; scanf("%d%d%d",&q[q_cnt].l,&q[q_cnt].r,&q[q_cnt].k); if(q[q_cnt].k==1) { ans[i]=T.query(1,1,n,q[q_cnt].l,q[q_cnt].r); q_cnt--; } else { q[q_cnt].r--; q[q_cnt].k--; q[q_cnt].id=i; } } T.clear(); sort(p+1,p+n,cmp1); sort(q+1,q+1+q_cnt,cmp1); for(i=1,j=1;i<=q_cnt;i++) { for(;p[j].l<q[i].l&&j<=n-1;j++) { T.update(1,1,n,p[j].r,p[j].k); } ans[q[i].id]=max(ans[q[i].id],T.query(1,1,n,q[i].l+q[i].k-1,n)); } T.clear(); sort(p+1,p+n,cmp3); sort(q+1,q+1+q_cnt,cmp2); for(i=1,j=1;i<=q_cnt;i++) { for(;p[j].r-p[j].l+1>=q[i].k&&j<=n-1;j++) { T.update(1,1,n,p[j].l,p[j].k); } ans[q[i].id]=max(ans[q[i].id],T.query(1,1,n,q[i].l,q[i].r-q[i].k+1)); } for(i=1;i<=m;i++) { printf("%d\n",ans[i]); } return 0; }
luogu P4819 [中山市选] 杀人游戏
CF412D Giving Awards
luogu P3403 跳楼机
luogu P2371 [国家集训队] 墨墨的等式
luogu P8060 [POI2003] Sums
-
同余最短路的过程中手动建边即可。
点击查看代码
int dis[50010],vis[50010],a[50010],cnt=0; void dijkstra(int s,int n) { memset(dis,0x3f,sizeof(dis)); memset(vis,0,sizeof(vis)); priority_queue<pair<int,int> >q; dis[s]=0; q.push(make_pair(-dis[s],s)); while(q.empty()==0) { int x=q.top().second; q.pop(); if(vis[x]==0) { vis[x]=1; for(int i=1;i<=n;i++) { if(dis[(x+a[i])%a[1]]>dis[x]+a[i]) { dis[(x+a[i])%a[1]]=dis[x]+a[i]; q.push(make_pair(-dis[(x+a[i])%a[1]],(x+a[i])%a[1])); } } } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,b,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; } dijkstra(0,n); cin>>m; for(i=1;i<=m;i++) { cin>>b; cout<<((dis[b%a[1]]<=b)?"TAK":"NIE")<<endl; } return 0; }
luogu P2662 牛场围栏
12.22
闲话
- 早上有体活。吃完早饭到机房后 @wkh2008 突然提醒我们今天是不是得回班签选科通知书,因为已经过了教室查迟到的时间所以到班后被班主任 \(D\) 了,班主任说昨天他给 \(miaomiao\) 说了要提醒我们今天回班,但昨天晚上 \(field\) 没说这件事,反倒是 @Pursuing_OIer 不知道什么时候给我们说了一嘴。
- 上午听多校的讲 \(DP\) 及其优化,开讲前先是讲课人及其高中就读学校的一顿介绍。
- 临吃午饭的时候把全网关了。
- 下午又和 @CuFeO4 , @ccxswl , @wkh2008 被 \(miaomiao\) 叫了出去讨论最终我们是否还要接着在机房停课的问题。到了之后先 \(D\) 了下我们的 \(whk\) 成绩,说在机房听云课的学习方式我们可能不是都能接受,这个赛季进队基本是没戏了,可以准备下个赛季的备考了。建议我们先回班先学会儿 \(whk\) ,说了下我们如果要回班后的安排:每周的奥赛课时会少,上奥赛课时不打模拟赛,只写专题(一周两个左右);然后开出了他的筹码:周测的时候可以来机房,剩下高一一周也就打 \(2\) 场模拟赛,会尽可能减小停课和停课的差距,现在回班学 \(whk\) 不代表放弃我们(因自初中以来的“不参加集训等价于退出”想法占据较长时间,故真实性存疑),高二上学期一开学就可以让我们开始停课集训;要是现在接着停课的话,他有点担心我们现在的 \(whk\) 成绩和高二补课的时候能不能补完。让我们不要给自己心理压力,好好做下选择,经过一番劝说后让我们给家里打个电话问问家里的意见,在晚上下课前给 \(feifei\) 一个答复并让其转告 \(miaomiao\) 。因昨晚上已经给家里打了个电话商讨了这件事,遂没再给家里打电话,直接给 \(miaomiao\) 说要接着留着机房。
- 吃完晚饭后 \(feifei\) 找我说 \(miaomiao\) 说让我给家里打个电话,拿到手机后从家长口中得知 \(miaomiao\) 往家长群里发了今天给几个学生说了建议他们回去学 \(whk\) 之类的东西,而且貌似家长那边都拿到了三调的班级人员的理科年级排名,简单沟通后就把电话挂了,并找 \(feifei\) 说了下要继续停课集训。
做题纪要
SP41 WORDS1 - Play on Words
-
首字符向尾字符连一条有向边然后判断是否存在欧拉回路。
点击查看代码
int din[300],dout[300],vis[300],cnt=0; char s[10010]; vector<int>e[300]; void dfs(int x) { cnt--; vis[x]=1; for(int i=0;i<e[x].size();i++) { if(vis[e[x][i]]==0) { dfs(e[x][i]); } } } bool check(int n) { for(int i=1;i<=26;i++) { cnt+=(din[i]!=0||dout[i]!=0); } for(int i=1;i<=26;i++) { if(din[i]!=0||dout[i]!=0) { dfs(i); break; } } if(cnt!=0) { return false; } int sum=0,rt1=0,rt2=0; for(int i=1;i<=26;i++) { if(din[i]!=dout[i]) { sum++; rt1=(din[i]==dout[i]-1)?i:rt1; rt2=(din[i]-1==dout[i])?i:rt2; } } return ((sum==0)||(sum==2&&rt1!=0&&rt2!=0)); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,x,y,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; cnt=0; memset(din,0,sizeof(din)); memset(dout,0,sizeof(dout)); memset(vis,0,sizeof(vis)); for(i=1;i<=26;i++) { e[i].clear(); } for(i=1;i<=n;i++) { cin>>(s+1); x=s[1]-'a'+1; y=s[strlen(s+1)]-'a'+1; e[x].push_back(y); e[y].push_back(x); dout[x]++; din[y]++; } cout<<(check(n)==true?"Ordering is possible.":"The door cannot be opened.")<<endl; } return 0; }
CF521E Cycling City
- 多倍经验: luogu P7025 [NWRRC2017] Grand Test
- 详见 图论2(tarjan,2-SAT,欧拉路,竞赛图,图的性质,综合应用) CF521E Cycling City 。
CF19E Fairy
- 详见 2.图论 A CF19E Fairy 。
luogu P5631 最小mex生成树
-
值域上线段树分治即可。
点击查看代码
int n; pair<int,int>e[2000010]; struct quality { int id,fa,siz; }; struct DSU { int fa[1000010],siz[1000010]; int find(int x) { return (fa[x]==x)?x:find(fa[x]); } void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } void merge(int x,int y,stack<quality>&s) { x=find(x); y=find(y); if(x!=y) { s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); if(siz[x]<siz[y]) { swap(x,y); } fa[y]=x; siz[x]+=siz[y]; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().id]=s.top().fa; siz[s.top().id]=s.top().siz; s.pop(); } } }D; struct SMT { struct SegmentTree { vector<int>info; }tree[400010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { tree[rt].info.push_back(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); } } void solve(int rt,int l,int r) { stack<quality>s; int mid=(l+r)/2; for(int i=0;i<tree[rt].info.size();i++) { D.merge(e[tree[rt].info[i]].first,e[tree[rt].info[i]].second,s); } if(l==r) { if(D.siz[D.find(1)]==n) { printf("%d\n",l); exit(0); } } else { solve(lson(rt),l,mid); solve(rson(rt),mid+1,r); } D.split(s); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int m,w,i; scanf("%d%d",&n,&m); for(i=1;i<=m;i++) { scanf("%d%d%d",&e[i].first,&e[i].second,&w); if(w-1>=0) { T.update(1,0,100000,0,w-1,i); } if(w+1<=100000) { T.update(1,0,100000,w+1,m,i); } } D.init(n); T.solve(1,0,100000); return 0; }
luogu P9989 [Ynoi Easy Round 2023] TEST_69
-
考虑势能线段树。
-
当 \(a_{i}|x\) 即 \(x \equiv 0 \pmod{a_{i}}\) 时操作 \(1\) 无用,由同余性质规约到区间时若 \(\operatorname{lcm}\limits_{i=l}^{r}\{ a_{i} \} |x\) 则操作 \(1\) 无用。
点击查看代码
ll a[200010]; ll gcd(ll a,ll b) { return b?gcd(b,a%b):a; } ll lcm(ll a,ll b) { return min((__int128_t)a/gcd(a,b)*b,(__int128_t)2e18); } struct SMT { struct SegmentTree { ll lcm; uint sum; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(ll rt) { tree[rt].lcm=lcm(tree[lson(rt)].lcm,tree[rson(rt)].lcm); tree[rt].sum=tree[lson(rt)].sum+tree[rson(rt)].sum; } void build(ll rt,ll l,ll r) { if(l==r) { tree[rt].sum=tree[rt].lcm=a[l]; return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(ll rt,ll l,ll r,ll x,ll y,ll val) { if(val%tree[rt].lcm==0) { return; } if(l==r) { tree[rt].sum=tree[rt].lcm=gcd(tree[rt].lcm,val); return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } pushup(rt); } uint query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt].sum; } ll mid=(l+r)/2; uint ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),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,pd,l,r,x,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } T.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { cin>>x; T.update(1,1,n,l,r,x); } else { cout<<T.query(1,1,n,l,r)<<endl; } } return 0; }
luogu P4915 帕秋莉的魔导书
-
查询区间前缀和,动态开点线段树加标记永久化即可。
点击查看代码
const ll inf=1ll<<32; struct SMT { ll rt,rt_sum=0; struct SegmentTree { ll ls,rs,sum,lazy; }tree[100010<<6]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) ll build_rt() { rt_sum++; tree[rt_sum].sum=tree[rt_sum].lazy=0; return rt_sum; } void update(ll &rt,ll l,ll r,ll x,ll y,ll val) { rt=(rt==0)?build_rt():rt; tree[rt].sum+=val*(min(r,y)-max(l,x)+1); if(x<=l&&r<=y) { tree[rt].lazy+=val; return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } } ll query(ll rt,ll l,ll r,ll x,ll y) { if(rt==0) { return 0; } if(x<=l&&r<=y) { return tree[rt].sum; } ll mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),mid+1,r,x,y); } return ans+tree[rt].lazy*(min(r,y)-max(l,x)+1); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,pd,l,r,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>l>>r; T.update(T.rt,1,inf,l,inf,r); } for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { printf("%.4lf\n",1.0*T.query(T.rt,1,inf,l,r)/(r-l+1)); } else { T.update(T.rt,1,inf,l,inf,r); } } return 0; }
luogu P10928 走廊泼水节
-
由 \(Kruskal\) 算法推论可知最小生成树上 \((x,y,w)\) 这条边一定是在算法执行过程中连接 \(S_{x},S_{y}\) 两个集合的权值最小边,剩下的构造 \(w+1\) 即可。
点击查看代码
struct node { int from,to,w; }; vector<node>e; bool cmp(node a,node b) { return a.w<b.w; } struct DSU { int fa[6010],siz[6010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } int find(int x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,u,v,w,ans,i,j; cin>>t; for(j=1;j<=t;j++) { ans=0; e.clear(); cin>>n; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; e.push_back((node){u,v,w}); } D.init(n); sort(e.begin(),e.end(),cmp); for(i=0;i<e.size();i++) { u=D.find(e[i].from); v=D.find(e[i].to); ans+=(e[i].w+1)*(D.siz[u]*D.siz[v]-1); D.fa[u]=v; D.siz[v]+=D.siz[u]; } cout<<ans<<endl; } return 0; }
luogu P4180 [BJWC2010] 严格次小生成树
luogu P10931 闇の連鎖
-
若一条树边不在任意一个环里,则断掉任何一条附加边均可以;若一条树边仅在一个环里,在断掉这条边后只能断掉环上的那条非树边。
-
考虑非树边对树边的覆盖,树上差分维护。
点击查看代码
struct node { int nxt,to; }e[600010]; int head[600010],fa[600010],siz[600010],top[600010],dep[600010],son[600010],d[600010],cnt=0; 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); } } } } 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; } void dfs(int x) { for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { dfs(e[i].to); d[x]+=d[e[i].to]; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,ans=0,u,v,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; d[u]++; d[v]++; d[lca(u,v)]-=2; } dfs(1); for(i=2;i<=n;i++) { ans+=(d[i]==0)?m:(d[i]==1); } cout<<ans<<endl; return 0; }
CF1062E Company
-
区间 \([l,r]\) 的 \(\operatorname{LCA}\) 等于 \([l,r]\) 中 \(DFS\) 序最小和最大的两个点的 \(\operatorname{LCA}\) ,证明类似 \(DFS\) 序求 \(\operatorname{LCA}\) 简单分讨即可。
-
判断删哪个点即可。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[200010],fa[200010],siz[200010],top[200010],dep[200010],son[200010],dfn[200010],pos[200010],tot=0,cnt=0; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x) { siz[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa[x]) { dep[e[i].to]=dep[x]+1; dfs1(e[i].to); 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); } } } } 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; } struct SMT { struct SegmentTree { int maxx,minn; }tree[800010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(int rt) { tree[rt].maxx=max(tree[lson(rt)].maxx,tree[rson(rt)].maxx); 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].maxx=tree[rt].minn=dfn[l]; return; } int mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } int query_min(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt].minn; } int mid=(l+r)/2,ans=0x3f3f3f3f; if(x<=mid) { ans=min(ans,query_min(lson(rt),l,mid,x,y)); } if(y>mid) { ans=min(ans,query_min(rson(rt),mid+1,r,x,y)); } return ans; } int query_max(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt].maxx; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans=max(ans,query_max(lson(rt),l,mid,x,y)); } if(y>mid) { ans=max(ans,query_max(rson(rt),mid+1,r,x,y)); } return ans; } }T; int ask(int l,int r,int x,int n) { int minn=0x3f3f3f3f,maxx=0; if(x-1>=l) { minn=min(minn,T.query_min(1,1,n,l,x-1)); maxx=max(maxx,T.query_max(1,1,n,l,x-1)); } if(x+1<=r) { minn=min(minn,T.query_min(1,1,n,x+1,r)); maxx=max(maxx,T.query_max(1,1,n,x+1,r)); } return (minn!=0x3f3f3f3f&&maxx!=0)?dep[lca(pos[minn],pos[maxx])]:0; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l,r,u,v,i; cin>>n>>m; for(i=2;i<=n;i++) { cin>>fa[i]; add(fa[i],i); } dfs1(1); dfs2(1,1); T.build(1,1,n); for(i=1;i<=m;i++) { cin>>l>>r; u=pos[T.query_min(1,1,n,l,r)]; v=pos[T.query_max(1,1,n,l,r)]; if(ask(l,r,u,n)>=ask(l,r,v,n)) { cout<<u<<" "<<ask(l,r,u,n)<<endl; } else { cout<<v<<" "<<ask(l,r,v,n)<<endl; } } return 0; }
luogu P7492 [传智杯 #3 决赛] 序列
-
势能线段树,当区间 \(and\) 值按位与上 \(k\) 后仍等于 \(k\) 则及时退出。
点击查看代码
ll a[100010]; struct SMT { struct SegmentTree { ll ans,pre,suf,sum,val; SegmentTree operator + (const SegmentTree &another) const { SegmentTree tmp; tmp.ans=max(ans,max(another.ans,suf+another.pre)); tmp.pre=max(pre,sum+another.pre); tmp.suf=max(another.suf,suf+another.sum); tmp.sum=sum+another.sum; tmp.val=val&another.val; return tmp; } }tree[400010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void pushup(ll rt) { tree[rt]=tree[lson(rt)]+tree[rson(rt)]; } void build(ll rt,ll l,ll r) { if(l==r) { tree[rt].val=tree[rt].sum=a[l]; tree[rt].ans=tree[rt].pre=tree[rt].suf=max(tree[rt].sum,0ll); return; } ll mid=(l+r)/2; build(lson(rt),l,mid); build(rson(rt),mid+1,r); pushup(rt); } void update(ll rt,ll l,ll r,ll x,ll y,ll val) { if((tree[rt].val&val)==val) { return; } if(l==r) { tree[rt].val=tree[rt].sum=tree[rt].sum|val; tree[rt].ans=tree[rt].pre=tree[rt].suf=max(tree[rt].sum,0ll); return; } ll mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,val); } if(y>mid) { update(rson(rt),mid+1,r,x,y,val); } pushup(rt); } SegmentTree query(ll rt,ll l,ll r,ll x,ll y) { if(x<=l&&r<=y) { return tree[rt]; } ll 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 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 ll n,m,pd,l,r,x,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; } T.build(1,1,n); for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { cout<<T.query(1,1,n,l,r).ans<<endl; } else { cin>>x; T.update(1,1,n,l,r,x); } } return 0; }
luogu P3793 由乃救爷爷
-
直接开 \(ST\) 表的空间复杂度无法接受,考虑分块后 \(ST\) 表维护块间的答案。
-
询问区间不在同一块内则拆成散块的后缀、整块、散块的前缀三个部分统计答案;否则直接对散块暴力。
-
块长约取 \(\sqrt{\frac{n}{q}\log n} \sim \sqrt{n}\) ,此时时间复杂度为 \(O(\sqrt{nq\log n}) \sim O(q\sqrt{n})\) ,在数据随机下表现良好。
点击查看代码
namespace GenHelper { unsigned z1,z2,z3,z4,b; unsigned rand_() { b=((z1<<6)^z1)>>13; z1=((z1&4294967294U)<<18)^b; b=((z2<<2)^z2)>>27; z2=((z2&4294967288U)<<2)^b; b=((z3<<13)^z3)>>21; z3=((z3&4294967280U)<<7)^b; b=((z4<<3)^z4)>>12; z4=((z4&4294967168U)<<13)^b; return (z1^z2^z3^z4); } } void srand(unsigned x) {using namespace GenHelper; z1=x; z2=(~x)^0x233333333U; z3=x^0x1234598766U; z4=(~x)+51;} int read() { using namespace GenHelper; int a=rand_()&32767; int b=rand_()&32767; return a*32768+b; } struct ST { int f[22][7010]; void init(int n) { for(int j=1;j<=__lg(n);j++) { for(int i=1;i+(1<<j)-1<=n;i++) { f[j][i]=max(f[j-1][i],f[j-1][i+(1<<(j-1))]); } } } int query(int l,int r) { int t=__lg(r-l+1); return max(f[t][l],f[t][r-(1<<t)+1]); } }T; int a[20000010],pre[20000010],suf[20000010],klen,ksum; #define pos(x) ((int)(ceil(1.0*x/klen))) #define L(x) ((x-1)*klen+1) #define R(x) (min(x*klen,n)) void init(int n,int m) { klen=3000;//前者算出的 ST 表空间带 4~5 倍常数 ksum=n/klen; if(R(ksum)<n) { ksum++; } for(int i=1;i<=ksum;i++) { for(int j=L(i);j<=R(i);j++) { T.f[0][i]=max(T.f[0][i],a[j]); } pre[L(i)]=a[L(i)]; for(int j=L(i)+1;j<=R(i);j++) { pre[j]=max(pre[j-1],a[j]); } suf[R(i)]=a[R(i)]; for(int j=R(i)-1;j>=L(i);j--) { suf[j]=max(suf[j+1],a[j]); } } T.init(ksum); } int query(int l,int r) { int ans=0; if(pos(l)==pos(r)) { for(int i=l;i<=r;i++) { ans=max(ans,a[i]); } } else { ans=max(suf[l],pre[r]); if(pos(l)+1<=pos(r)-1) { ans=max(ans,T.query(pos(l)+1,pos(r)-1)); } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,s,l,r,i; ull ans=0; cin>>n>>m>>s; srand(s); for(i=1;i<=n;i++) { a[i]=read(); } init(n,m); for(i=1;i<=m;i++) { l=read()%n+1; r=read()%n+1; if(l>r) { swap(l,r); } ans+=query(l,r); } cout<<ans<<endl; return 0; }
[ARC023D] GCD区間
-
多倍经验: CF475D CGCDSSQ
点击查看代码
struct node { ll val,l,r; }g[100010]; ll a[100010],cnt_g; map<ll,ll>ans; 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,m,x,len=0,i,j; cin>>n>>m; 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; } } for(i=1;i<=m;i++) { cin>>x; cout<<ans[x]<<endl; } return 0; }
12.23
闲话
- 吃完早饭到机房后正赶上高二的打模拟赛, \(miaomiao\) 让我们上午保持安静。
- 下午放 @ppllxx_9G 的每日一歌《挪威的森林》。
- 晚上听多校的讲题前 \(miaomiao\) 过来说让我们注意写题的纪律,高二的先改模拟赛,高一的补专题,还有一堆任务没干怎么就开始玩了。
做题纪要
CF1175F The Number of Subpermutations
-
单调栈预处理出极长区间后启发式分裂。
-
具体地,设 \(last_{i}\) 表示 \(a_{i}\) 上一次出现的位置。固定最大值/长度 \(a_{mid}\) 后,以枚举左端点 \(l\) 为例,右端点 \(r=l+a_{mid}-1\) 合法当且仅当 \(\max\limits_{i=l}^{r}\{ last_{i} \}<l\) ,即 \(\max\limits_{i=1}^{r}\{ last_{i} \}<l\) 。
点击查看代码
ll a[300010],l[300010],r[300010],last[300010],pre[300010]; stack<ll>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]; pre[i]=max(pre[i-1],last[a[i]]); last[a[i]]=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++) { if(i-l[i]+1<=r[i]-i+1) { for(j=l[i];j<=i;j++) { ans+=(i<=j+a[i]-1&&j+a[i]-1<=r[i]&&pre[j+a[i]-1]<j); } } else { for(j=i;j<=r[i];j++) { ans+=(i>=j-a[i]+1&&j-a[i]+1>=l[i]&&pre[j]<j-a[i]+1); } } } cout<<ans<<endl; return 0; }
CF1156E Special Segments of Permutation
-
启发式分裂。
点击查看代码
ll a[200010],l[200010],r[200010],pos[200010]; stack<ll>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]; pos[a[i]]=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++) { if(i-l[i]+1<=r[i]-i+1) { for(j=l[i];j<=i;j++) { ans+=(i<=pos[a[i]-a[j]]&&pos[a[i]-a[j]]<=r[i]); } } else { for(j=i;j<=r[i];j++) { ans+=(i>=pos[a[i]-a[j]]&&pos[a[i]-a[j]]>=l[i]); } } } cout<<ans<<endl; return 0; }
luogu P3366 【模板】最小生成树
-
\(Boruvka\) 算法板子。
- 算法流程
- 初始时每个点都是一个独立的连通块。
- 每次迭代时遍历所有边 \((u,v,w)\) ,若 \(u,v\) 不在同一个连通块内,则用 \(w\) 更新本次迭代中 \(u,v\) 所在连通块的最小边。
- 若所有连通块都没有被更新最小边,说明已经得到了最小生成树;否则将有最小边的连通块的边加入最小生成树,并继续开始迭代。
- 简而言之,就是对于每个连通块求出到达其他连通块中权值最小的边,并对边按照从小到大合并。因此常用于求解完全图且边权具有一定性质的最小生成树。
- 因每次连边连通块数量都会减半,在忽略并查集的时间复杂度后总时间复杂度为 \(O(m \log n)\) 。
点击查看代码
vector<pair<int,int> >e[5010]; pair<int,int>g[5010]; void add(int u,int v,int w) { e[u].push_back(make_pair(v,w)); } struct DSU { int fa[5010],siz[5010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } int find(int x) { return (fa[x]==x)?x:fa[x]=find(fa[x]); } void merge(int x,int y) { x=find(x); y=find(y); if(x!=y) { fa[x]=y; siz[y]+=siz[x]; } } }D; int boruvka(int n) { int ans=0,flag=1,x,y; D.init(n); while(flag==1) { flag=0; fill(g+1,g+1+n,make_pair(0,0x3f3f3f3f)); for(int i=1;i<=n;i++) { x=D.find(i); for(int j=0;j<e[i].size();j++) { y=D.find(e[i][j].first); if(x!=y&&e[i][j].second<g[x].second) { g[x]=make_pair(y,e[i][j].second);//更新最小边 } } } for(int i=1;i<=n;i++) { x=D.find(i); if(g[x].first!=0&&D.find(x)!=D.find(g[x].first)) { D.merge(x,g[x].first); ans+=g[x].second; flag=1;//放到第 55 行后面也可以 } } } return D.siz[D.find(1)]==n?ans:-1; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,w,ans,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } ans=boruvka(n); if(ans==-1) { cout<<"orz"<<endl; } else { cout<<ans<<endl; } return 0; }
- 算法流程
CF888G Xor-MST
AT_keyence2019_e Connecting Cities
-
拆掉绝对值后得到 \(a_{i}+id,a_{i}-id,a_{j}+id,a_{j}-id\) 四个式子。
-
vector
启发式合并辅助线段树查询的常数过大,无法通过。上述做法的常数在于一条边会被计算两次,考虑优化。 -
不妨直接钦定向前连、向后连的贡献,顺次扫的过程中是容易维护的。做法正确性由最优解一定包含与其中可以保证。
点击查看代码
ll a[200010],b[200010],c[200010],f[200010]; pair<ll,ll>g[200010]; multiset<pair<ll,ll> >s; struct DSU { ll fa[200010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; } } 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; } } }D; ll boruvka(ll n) { D.init(n); ll ans=0,flag=1,x; while(flag==1) { flag=0; fill(g+1,g+1+n,make_pair(0,0x3f3f3f3f3f3f3f3f)); s.clear(); for(ll i=1;i<=n;i++) { f[i]=0x3f3f3f3f3f3f3f3f; s.insert(make_pair(f[i],i)); } for(ll i=1;i<=n;i++) { x=D.find(i); s.erase(make_pair(f[x],x)); if(s.empty()==0&&s.begin()->first+c[i]<g[x].second) { g[x].first=s.begin()->second; g[x].second=s.begin()->first+c[i]; } f[x]=min(f[x],b[i]); s.insert(make_pair(f[x],x)); } s.clear(); for(ll i=1;i<=n;i++) { f[i]=0x3f3f3f3f3f3f3f3f; s.insert(make_pair(f[i],i)); } for(ll i=n;i>=1;i--) { x=D.find(i); s.erase(make_pair(f[x],x)); if(s.empty()==0&&s.begin()->first+b[i]<g[x].second) { g[x].first=s.begin()->second; g[x].second=s.begin()->first+b[i]; } f[x]=min(f[x],c[i]); s.insert(make_pair(f[x],x)); } for(ll i=1;i<=n;i++) { x=D.find(i); if(g[x].first!=0&&D.find(x)!=D.find(g[x].first)) { D.merge(x,g[x].first); ans+=g[x].second; flag=1; } } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,d,i; cin>>n>>d; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]-i*d; c[i]=a[i]+i*d; } cout<<boruvka(n)<<endl; return 0; }
AT_cf17_final_j Tree MST
-
通过点分治把经过 \(rt\) 的路径 \(dis_{x,y}\) 拆成 \(dis_{x}+dis_{y}\) ,找到子树内点权最小的点后让其他点与其连边。
-
连的边的规模是 \(O(n \log n)\) ,再跑一遍 \(Kruskal\) 求解最小生成树即可。
-
实际实现时,可以不管是否经过 \(rt\) 的路径,而是可以直接连边,因为边权错误(比正确边权大)的边一定可以被后面正确边权的边替代。
点击查看代码
struct node { ll nxt,to,w; }e[400010]; ll head[400010],a[400010],cnt=0; vector<node>E; bool cmp(node a,node b) { return a.w<b.w; } 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 Divide_On_Tree { ll siz[400010],weight[400010],vis[400010],dis[400010],center,minn,pos; queue<ll>q; void init(ll n) { center=0; get_center(1,0,n); get_siz(center,0); divide(center); } void get_center(ll x,ll fa,ll n) { siz[x]=1; weight[x]=0; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_center(e[i].to,x,n); siz[x]+=siz[e[i].to]; weight[x]=max(weight[x],siz[e[i].to]); } } weight[x]=max(weight[x],n-siz[x]); if(weight[x]<=n/2) { center=x; } } void get_siz(ll x,ll fa) { siz[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_siz(e[i].to,x); siz[x]+=siz[e[i].to]; } } } void get_dis(ll x,ll fa) { q.push(x); if(dis[x]+a[x]<minn) { minn=dis[x]+a[x]; pos=x; } for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { dis[e[i].to]=dis[x]+e[i].w; get_dis(e[i].to,x); } } } void divide(ll x) { vis[x]=1; q.push(x); dis[x]=0; pos=x; minn=a[x]; for(ll i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { dis[e[i].to]=e[i].w; get_dis(e[i].to,x); } } while(q.empty()==0) { if(pos!=q.front()) { E.push_back((node){pos,q.front(),minn+a[q.front()]+dis[q.front()]}); } q.pop(); } for(ll i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { center=0; get_center(e[i].to,x,siz[e[i].to]); get_siz(center,0); divide(center); } } } }T; struct DSU { ll fa[400010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; } } ll find(ll x) { return fa[x]==x?x:fa[x]=find(fa[x]); } }D; ll kruskal(ll n) { D.init(n); sort(E.begin(),E.end(),cmp); ll ans=0,x,y; for(ll i=0;i<E.size();i++) { x=D.find(E[i].nxt); y=D.find(E[i].to); if(x!=y) { D.fa[x]=y; ans+=E[i].w; } } return ans; } 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;i++) { cin>>a[i]; } for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } T.init(n); cout<<kruskal(n)<<endl; return 0; }
luogu P3345 [ZJOI2015] 幻想乡战略游戏
-
补给点显然选择带权重心处,由带权重心换根 \(DP\) 转移方程可知 \(x\) 的子节点 \(y\) 更优当且仅当 \(2siz_{y}>siz_{x}\) 。
-
进一步可知带权重心只会有一个。
-
又因为度数 \(\le 20\) ,可以直接暴力点分树求出每个子节点作为根节点时(需要记录点分树上子节点是由原树上哪个节点而来的)的答案与当前节点进行比较。由带权重心的偏移可以看做是在点分树上的二分。
点击查看代码
struct node { ll nxt,to,w; }e[200010]; ll head[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; } struct LCA { ll siz[200010],fa[200010],dep[200010],son[200010],dis[200010],top[200010]; 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; 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 y) { return dis[x]+dis[y]-2*dis[lca(x,y)]; } }L; struct Divide_On_Tree { ll siz[200010],weight[200010],vis[200010],fa[200010],pos[200010],sum[200010],f[2][200010],center=0,root; vector<ll>g[200010]; void init(ll n) { center=0; get_center(1,0,n); get_siz(center,0); root=center; build(center); } void get_center(ll x,ll fa,ll n) { siz[x]=1; weight[x]=0; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_center(e[i].to,x,n); siz[x]+=siz[e[i].to]; weight[x]=max(weight[x],siz[e[i].to]); } } weight[x]=max(weight[x],n-siz[x]); if(weight[x]<=n/2) { center=x; } } void get_siz(ll x,ll fa) { siz[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_siz(e[i].to,x); siz[x]+=siz[e[i].to]; } } } void build(ll x) { vis[x]=1; for(ll i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { center=0; get_center(e[i].to,x,siz[e[i].to]); get_siz(center,0); fa[center]=x; pos[center]=e[i].to; g[x].push_back(center); build(center); } } } void update(ll x,ll y) { for(ll rt=x;rt!=0;rt=fa[rt]) { sum[rt]+=y; f[0][rt]+=y*L.get_dis(x,rt); if(fa[rt]!=0) { f[1][rt]+=y*L.get_dis(x,fa[rt]); } } } ll ask(ll x) { ll ans=f[0][x]; for(ll rt=x;fa[rt]!=0;rt=fa[rt]) { ans+=f[0][fa[rt]]-f[1][rt]+(sum[fa[rt]]-sum[rt])*L.get_dis(fa[rt],x); } return ans; } ll query(ll x) { ll ans=ask(x); for(ll i=0;i<g[x].size();i++) { if(ask(pos[g[x][i]])<ans) { return query(g[x][i]); } } return ans; } }D; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,u,v,w,i; cin>>n>>m; for(i=1;i<=n-1;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } L.dfs1(1,0); L.dfs2(1,1); D.init(n); for(i=1;i<=m;i++) { cin>>u>>v; D.update(u,v); cout<<D.query(D.root)<<endl; } return 0; }
luogu P5311 [Ynoi2011] 成都七中
-
限制条件等价于 \(x \to y\) 的路径上的最大值 \(\le r\) ,最小值 \(\ge l\) 。考虑点分树处理出每个节点到祖先节点的路径上的最大、最小编号的点。
-
同时包含 \(x\) 的连通块一定在点分树上 \([l,r]\) 中某个节点的子树内,暴力跳找到最浅的节点。最后再一次性处理即可。
-
数颜色数做法同 luogu P7880 [Ynoi2006] rldcot 。
点击查看代码
struct ask { int l,r,id; }; struct node { int nxt,to,w; }e[200010]; int head[200010],a[200010],ans[200010],last[200010],cnt=0; vector<ask>path[200010],c[200010],q[200010]; bool cmp(ask a,ask b) { return a.r<b.r; } void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } struct BIT { int c[200010]; int lowbit(int x) { return (x&(-x)); } void add(int x,int val) { for(int i=x;i>=1;i-=lowbit(i)) { c[i]+=val; } } int getsum(int n,int x) { int ans=0; for(int i=x;i<=n;i+=lowbit(i)) { ans+=c[i]; } return ans; } }T; struct Divide_On_Tree { int siz[200010],weight[200010],vis[200010],fa[200010],center; void init(int n) { center=0; get_center(1,0,n); get_siz(center,0); build(center); } void get_center(int x,int fa,int n) { siz[x]=1; weight[x]=0; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_center(e[i].to,x,n); siz[x]+=siz[e[i].to]; weight[x]=max(weight[x],siz[e[i].to]); } } weight[x]=max(weight[x],n-siz[x]); if(weight[x]<=n/2) { center=x; } } void get_siz(int x,int fa) { siz[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { get_siz(e[i].to,x); siz[x]+=siz[e[i].to]; } } } void dfs(int x,int fa,int minn,int maxx,int rt) { path[x].push_back((ask){minn,maxx,rt}); c[rt].push_back((ask){minn,maxx,a[x]}); for(int i=head[x];i!=0;i=e[i].nxt) { if(e[i].to!=fa&&vis[e[i].to]==0) { dfs(e[i].to,x,min(minn,e[i].to),max(maxx,e[i].to),rt); } } } void build(int x) { vis[x]=1; dfs(x,0,x,x,x); for(int i=head[x];i!=0;i=e[i].nxt) { if(vis[e[i].to]==0) { center=0; get_center(e[i].to,x,siz[e[i].to]); get_siz(center,x); fa[center]=x; build(center); } } } void update(int l,int r,int x,int id) { for(int i=0;i<path[x].size();i++)//由遍历顺序已经保证了从浅到深 { if(l<=path[x][i].l&&path[x][i].r<=r) { q[path[x][i].id].push_back((ask){l,r,id}); break; } } } }D; void query(int x,int n) { sort(c[x].begin(),c[x].end(),cmp); sort(q[x].begin(),q[x].end(),cmp); for(int i=0,j=0;i<q[x].size();i++) { for(;j<c[x].size()&&c[x][j].r<=q[x][i].r;j++) { if(c[x][j].l>last[c[x][j].id]) { T.add(last[c[x][j].id],-1); last[c[x][j].id]=c[x][j].l; T.add(last[c[x][j].id],1); } } ans[q[x][i].id]=T.getsum(n,q[x][i].l); } for(int i=0;i<c[x].size();i++) { T.add(last[c[x][i].id],-1); last[c[x][i].id]=0; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,l,r,x,i; cin>>n>>m; 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); } D.init(n); for(i=1;i<=m;i++) { cin>>l>>r>>x; D.update(l,r,x,i); } for(i=1;i<=n;i++) { query(i,n); } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
luogu P4782 【模板】2-SAT
-
\(2-SAT\) 板子。
- 在 \(2-SAT\) 问题中,常将限制条件转化为逻辑命题若 \(x_{i}=p\) 则 \(x_{j}=q\) ,其中 \(p,q \in \{ 0,1 \}\) 。
- 算法流程
- 判断是否合法
- 建立 \(2n\) 个节点的有向图,其中 \(\forall i \in [1,n],x_{i}\) 对应 \(i,i+n\) 两个顶点,并分别对应其取 \(0,1\) 的情况。
- 将上述命题转化到图上,即从 \(i+pn\) 向 \(j+qn\) 连一条有向边表示如果选择了 \(i+pn\) 则必须选 \(j+qn\) ,注意在实际应用时因算法需要(详见后续构造流程)只能在关系明确(强调逻辑的严格推导性)的前提下进行连边。
- 用 \(Tarjan\) 求出建出的图的强连通分量。
- 若 \(\exists i \in [i,n],i\) 和 \(i+n\) 在同一个强连通分量内,说明通过 \(x_{i}=0\) 能推导出 \(x_{i}=1\) 且通过 \(x_{i}=1\) 也能推导出 \(x_{i}=0\) ,而这显然是无法满足的。否则一定有解。
- 构造解
- 若强连通分量内的某个变量的取值一定后,就能顺序确定内部其他变量的取值,故可以先对原图进行缩点。
- 在有解的情况下,一个变量的取值会直接或间接地影响了其他变量的取值。而如果我们优先选择不影响其他变量取值的作为某变量的取值,则一定更容易构造一种方案。故在自上而下进行拓扑排序的过程中,对于一个变量的两个取值我们优先选择拓扑序较大的取值。
- 而另一种做法是依据 \(Tarjan\) 本质上是对原图做一遍 \(DFS\) ,所属强连通分量编号越小就越接近底部,即所属强连通分量编号对应逆拓扑序。然后就能根据编号大小进行染色了。
- 判断是否合法
-
本题中 \(x_{i}=p \lor x_{j}=q\) 能得到若 \(x_{i}=1-p\) 则 \(x_{j}=q\) 和若 \(x_{j}=1-q\) 则 \(x_{i}=p\) 。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[2000010],dfn[2000010],low[2000010],ins[2000010],col[2000010],scc_cnt=0,cnt=0,tot=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,x,y,a,b,flag=0,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>x>>a>>y>>b; add(x+(1-a)*n,y+b*n); add(y+(1-b)*n,x+a*n); } for(i=1;i<=2*n;i++) { if(dfn[i]==0) { tarjan(i); } } for(i=1;i<=n;i++) { if(col[i]==col[i+n]) { cout<<"IMPOSSIBLE"<<endl; flag=1; break; } } if(flag==0) { cout<<"POSSIBLE"<<endl; for(i=1;i<=n;i++) { cout<<(col[i+n]<col[i])<<" "; } } return 0; }
12.24
闲话
- 下午放 @Pursuing_OIer 的每日一歌《我不曾忘记》。
- 晚上听完多校的讲题后 \(huge\) 问我们是不是数学的一堆基础东西还不是很会,比如拉格朗日差值、有限微积分之类的,问我们暑假讲的时候有没有学懂,众人表示当时就没怎么听,而且考的也不是基础知识点。临下课前 \(huge\) 说明天准备奥赛生的合班,侯操时直接听班主任安排,吃完早饭就先不用来机房了,估计应该是奥赛生先搬宿舍再是整个年级的分班。
做题纪要
luogu P10969 Katu Puzzle
-
分讨 \(6\) 种情况进行连边。
- \(a \land b=0:a+n \to b,b+n \to a\) 。
- \(a \land b=1:a \to a+n,b \to b+n\) 。
- \(a \lor b=0:a+n \to a,b+n \to b\) 。
- \(a \lor b=1:a \to b+n,b \to a+n\) 。
- \(a \bigoplus b=0:a \to b,b \to a,a+n \to b+n,b+n \to a+n\) 。
- \(a \bigoplus b=1:a \to b+n,b \to a+n,a+n \to b,b+n \to a\) 。
点击查看代码
struct node { int nxt,to; }e[60010]; int head[210],dfn[210],low[210],ins[210],col[210],scc_cnt=0,tot=0,cnt=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } bool check(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { return false; } } return true; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,a,b,c,i; string pd; cin>>n>>m; for(i=1;i<=m;i++) { cin>>a>>b>>c>>pd; if(pd=="AND") { if(c==0) { add(a+n,b); add(b+n,a); } else { add(a,a+n); add(b,b+n); } } if(pd=="OR") { if(c==0) { add(a+n,a); add(b+n,b); } else { add(a,b+n); add(b,a+n); } } if(pd=="XOR") { if(c==0) { add(a,b); add(b,a); add(a+n,b+n); add(b+n,a+n); } else { add(a,b+n); add(b,a+n); add(a+n,b); add(b+n,a); } } } for(i=1;i<=2*n;i++) { if(dfn[i]==0) { tarjan(i); } } cout<<(check(n)==true?"YES":"NO")<<endl; return 0; }
luogu P4171 [JSOI2010] 满汉全席
luogu P5782 [POI2001] 和平委员会
-
适当映射后跑 \(2-SAT\) 。
点击查看代码
struct node { int nxt,to; }e[40010]; int head[16010],dfn[16010],low[16010],ins[16010],col[16010],tot=0,scc_cnt=0,cnt=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,flag=0,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,(v%2==0)?v-1:v+1); add(v,(u%2==0)?u-1:u+1); } for(i=1;i<=2*n;i++) { if(dfn[i]==0) { tarjan(i); } } for(i=1;i<=n;i++) { if(col[2*i-1]==col[2*i]) { flag=1; cout<<"NIE"<<endl; break; } } if(flag==0) { for(i=1;i<=n;i++) { cout<<(col[2*i-1]<col[2*i]?2*i-1:2*i)<<endl; } } return 0; }
UVA1391 Astronauts
-
分讨两个宇航员年龄与平均年龄的大小关系。
点击查看代码
struct node { int nxt,to; }e[400010]; int head[200010],dfn[200010],low[200010],ins[200010],col[200010],a[200010],tot=0,scc_cnt=0,cnt=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } bool check(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { return false; } } return true; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,sum=0,i; while(cin>>n>>m) { if(n==0&&m==0) { break; } else { cnt=tot=scc_cnt=sum=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); for(i=1;i<=n;i++) { cin>>a[i]; sum+=a[i]; } for(i=1;i<=m;i++) { cin>>u>>v; if((a[u]*n>=sum&&a[v]*n>=sum)||(a[u]*n<sum&&a[v]*n<sum)) { add(u,v+n); add(v,u+n); add(u+n,v); add(v+n,u); } else { add(u+n,v);//不能同时为 1 add(v+n,u); } } for(i=1;i<=2*n;i++) { if(dfn[i]==0) { tarjan(i); } } if(check(n)==true) { for(i=1;i<=n;i++) { cout<<((col[i+n]<col[i])?"C":(a[i]*n>=sum?"A":"B"))<<endl; } } else { cout<<"No solution."<<endl; } } } return 0; }
UVA11294 Wedding
-
用 \(0/1\) 分别表示丈夫与新郎同侧,妻子与新郎同侧,然后跑 \(2-SAT\) 。
点击查看代码
struct node { int nxt,to; }e[20010]; int head[70],dfn[70],low[70],ins[70],col[70],tot=0,scc_cnt=0,cnt=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } bool check(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { return false; } } return true; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,i; char a,b; while(cin>>n>>m) { if(n==0&&m==0) { break; } else { cnt=tot=scc_cnt=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); add(1,1+n);//新娘作为妻子和新娘同侧,新郎作为丈夫和新郎同侧 for(i=1;i<=m;i++) { cin>>u>>a>>v>>b; u++; v++; add(u+(a=='h')*n,v+(1-(b=='h'))*n); add(v+(b=='h')*n,u+(1-(a=='h'))*n); } for(i=1;i<=2*n;i++) { if(dfn[i]==0) { tarjan(i); } } if(check(n)==true) { for(i=2;i<=n;i++) { cout<<i-1<<(col[i+n]<col[i]?"w":"h")<<" "; } cout<<endl; } else { cout<<"bad luck"<<endl; } } } return 0; }
[ARC069F] Flags
-
多倍经验: UVA1146 Now or later
-
考虑二分答案后用 \(2-SAT\) 进行判定,暴力连边的时间复杂度为 \(O(n^2 \log V)\) ,无法接受。
-
排序后线段树优化建图即可。
点击查看代码
pair<int,int>a[100010]; struct SMT_Q_BG { int dfn[100010],low[100010],ins[100010],col[100010],id[100010],tot=0,scc_cnt=0,rt_sum; stack<int>s; vector<int>e[100010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void init(int n) { rt_sum=n*2; build(1,1,2*n,n); } void clear(int n) { memset(dfn,0,sizeof(dfn)); tot=scc_cnt=0; for(int i=1;i<=2*n;i++) { e[i].clear(); } } void build(int rt,int l,int r,int n) { if(l==r) { e[rt+rt_sum].push_back(a[l].second>n?a[l].second-n:a[l].second+n); return; } int mid=(l+r)/2; build(lson(rt),l,mid,n); build(rson(rt),mid+1,r,n); e[rt+rt_sum].push_back(lson(rt)+rt_sum); e[rt+rt_sum].push_back(rson(rt)+rt_sum); } void update(int rt,int l,int r,int x,int y,int pos) { if(x<=l&&r<=y) { e[pos].push_back(rt+rt_sum); return; } int mid=(l+r)/2; if(x<=mid) { update(lson(rt),l,mid,x,y,pos); } if(y>mid) { update(rson(rt),mid+1,r,x,y,pos); } } void tarjan(int x) { tot++; dfn[x]=low[x]=tot; ins[x]=1; s.push(x); for(int i=0;i<e[x].size();i++) { if(dfn[e[x][i]]==0) { tarjan(e[x][i]); low[x]=min(low[x],low[e[x][i]]); } else { if(ins[e[x][i]]==1) { low[x]=min(low[x],dfn[e[x][i]]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } bool sat(int n) { for(int i=1;i<=10*n;i++) { if(dfn[i]==0) { tarjan(i); } } for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { return false; } } return true; } }T; bool check(int mid,int n) { T.clear(n); int pos; for(int i=1;i<=2*n;i++) { pos=lower_bound(a+1,a+1+2*n,make_pair(a[i].first-mid+1,0))-a; if(1<=pos&&pos<=i-1) { T.update(1,1,2*n,pos,i-1,a[i].second); } pos=lower_bound(a+1,a+1+2*n,make_pair(a[i].first+mid,0))-a-1; if(i+1<=pos&&pos<=2*n) { T.update(1,1,2*n,i+1,pos,a[i].second); } } return T.sat(n); } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,l=0,r=1000000000,mid,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i].first>>a[i+n].first; a[i].second=i; a[i+n].second=i+n; } sort(a+1,a+1+2*n); T.init(n); while(l<=r) { mid=(l+r)/2; if(check(mid,n)==true) { l=mid+1; ans=mid; } else { r=mid-1; } } cout<<ans<<endl; return 0; }
luogu P5332 [JSOI2019] 精准预测
-
把 \((x,t),\lnot(x,t)\) 分别看做一个点,表示 \(x\) 在 \(t\) 时刻活着或死亡,建边方式为 \(\begin{cases} (x,t) \to (x,t-1) \\ \lnot(x,t-1) \to \lnot(x,t) \end{cases}\) 。对边离散化后点和边的规模为 \(O(n+m)\) 。
-
问题转化为求解以 \((x,T+1)\) 为起点,用 \(n-1\) 减去死亡的人的数量即可。
-
因保证预测全部有解和建边的时间先后关系,所以建出的图一定是一个 \(DAG\) 。考虑
bitset
记录到达的点对后又需要进行压位,和 UOJ 605. 【UER #9】知识网络 一样做就行了。点击查看代码
int c[100010],t[100010],x[100010],y[100010],ans[100010],dead[100010],vis[300010],col[300010],cnt=0,siz=10000; vector<int>e[300010],d[50010]; map<int,int>id[50010][2]; bitset<10000>s[300010],tmp; void add(int u,int v) { e[u].push_back(v); } void dfs(int x,int l,int r) { vis[x]=1; s[x].reset(); if(l<=col[x]&&col[x]<=r)//x 对应的节点一定死亡 { s[x][col[x]-l]=1; } for(int i=0;i<e[x].size();i++) { if(vis[e[x][i]]==0) { dfs(e[x][i],l,r); } s[x]|=s[e[x][i]];//拓扑的过程中仍需要自下而上更新 } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int tim,n,m,pos,i,j,l,r; cin>>tim>>n>>m; for(i=1;i<=m;i++) { cin>>c[i]>>t[i]>>x[i]>>y[i]; d[x[i]].push_back(t[i]); } for(i=1;i<=n;i++) { d[i].push_back(tim+1); sort(d[i].begin(),d[i].end()); d[i].erase(unique(d[i].begin(),d[i].end()),d[i].end()); for(j=0;j<d[i].size();j++) { cnt++; id[i][0][d[i][j]]=cnt; cnt++; id[i][1][d[i][j]]=cnt; } col[id[i][0][tim+1]]=i; for(j=0;j+1<d[i].size();j++) { add(id[i][0][d[i][j]],id[i][0][d[i][j+1]]); add(id[i][1][d[i][j+1]],id[i][1][d[i][j]]); } } for(i=1;i<=m;i++) { pos=*lower_bound(d[y[i]].begin(),d[y[i]].end(),t[i]+(c[i]==0)); add(id[x[i]][c[i]][t[i]],id[y[i]][0][pos]); add(id[y[i]][1][pos],id[x[i]][1-c[i]][t[i]]); } for(l=1,r=min(l+siz-1,n);l<=n;l=r+1,r=min(l+siz-1,n)) { memset(vis,0,sizeof(vis)); tmp.reset(); for(i=1;i<=n;i++) { if(vis[id[i][1][tim+1]]==0) { dfs(id[i][1][tim+1],l,r); } } for(i=l;i<=r;i++) { if(s[id[i][1][tim+1]][i-l]==1) { dead[i]=1; tmp[i-l]=1;//死亡的人即使到达不了也一定不会被统计到 } } for(i=1;i<=n;i++) { ans[i]+=(tmp|s[id[i][1][tim+1]]).count(); } } for(i=1;i<=n;i++) { cout<<(dead[i]==0)*(n-1-ans[i])<<" "; } return 0; }
luogu P3513 [POI2011] KON-Conspiracy
-
等价于求将原图分成一个团(同谋者)和一个独立集(后勤组织)的方案数。合法的构造方案一定是在原有的方案上只更改某个人的阵营或在两个阵营中各选一个人并对调其阵营。
-
设 \(x_{i}=0/1\) 表示第 \(i\) 个人在同谋者/后勤组织,先 \(2-SAT\) 得到一组构造方案,然后对其进行调整。
-
能够成功更改某个人的阵营当且仅当将这个人放到对面阵营后没有与其产生矛盾的点且所属阵营大小 \(\ge 2\) ;能够成功在两个阵营中各选一个人并对调其阵营分为两种情况,一种是选出的两个人放到对面阵营后均没有与其产生矛盾的点,另一种是一个人仅与对方产生矛盾而对方没有矛盾的点(由构造已知不可能互相有矛盾)。分别 \(O(n^{2})\) 统计即可。
点击查看代码
struct node { int nxt,to; }e[25000010]; int head[10010],dfn[10010],low[10010],ins[10010],col[10010],tmp[2],tot=0,scc_cnt=0,cnt=0; bool dis[5010][5010]; pair<int,int>con[10010]; stack<int>s; vector<int>pos[2]; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } int check(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { return 0; } pos[(col[i+n]<col[i])].push_back(i); } int ans=(pos[0].size()!=0&&pos[1].size()!=0); for(int i=0;i<pos[0].size();i++) { for(int j=0;j<pos[1].size();j++) { if(dis[pos[0][i]][pos[1][j]]==0) { con[pos[0][i]].first++; con[pos[0][i]].second=pos[1][j]; } else { con[pos[1][j]].first++; con[pos[1][j]].second=pos[0][i]; } } } for(int i=1;i<=n;i++) { ans+=((con[i].first==0&&pos[col[i+n]<col[i]].size()>=2)||(con[i].first==1&&con[con[i].second].first==0)); tmp[col[i+n]<col[i]]+=(con[i].first==0); } return ans+tmp[0]*tmp[1]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,k,v,i,j; scanf("%d",&n); for(i=1;i<=n;i++) { scanf("%d",&k); for(j=1;j<=k;j++) { scanf("%d",&v); dis[i][v]=1; } for(j=1;j<=n;j++) { if(i!=j) { add(i+(1-dis[i][j])*n,j+dis[i][j]*n); } } } for(i=1;i<=n*2;i++) { if(dfn[i]==0) { tarjan(i); } } printf("%d\n",check(n)); return 0; }
luogu P3007 [USACO11JAN] The Continental Cowngress G
-
判断无解后 \(O(nm)\) 判定。
点击查看代码
struct node { int nxt,to; }e[8010]; int head[2010],dfn[2010],low[2010],ins[2010],col[2010],tot=0,scc_cnt=0,cnt=0; stack<int>s; 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; ins[x]=1; 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],dfn[e[i].to]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],low[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } void dfs(int x) { ins[x]=1; for(int i=head[x];i!=0;i=e[i].nxt) { if(ins[e[i].to]==0) { dfs(e[i].to); } } } bool check(int x,int n) { memset(ins,0,sizeof(ins)); dfs(x); for(int i=1;i<=n;i++) { if(ins[i]==1&&ins[i+n]==1) { return false; } } return true; } void print(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { cout<<"IMPOSSIBLE"<<endl; return; } } for(int i=1;i<=n;i++) { cout<<((check(i,n)==true)?(check(i+n,n)==true?"?":"N"):"Y"); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,x,y,i; char c1,c2; cin>>n>>m; for(i=1;i<=m;i++) { cin>>x>>c1>>y>>c2; add(x+(1-(c1=='Y'))*n,y+(c2=='Y')*n); add(y+(1-(c2=='Y'))*n,x+(c1=='Y')*n); } for(i=1;i<=n*2;i++) { if(dfn[i]==0) { tarjan(i); } } print(n); return 0; }
CF27D Ring Road 2
-
若两条边相交且不包含,则需要放到异侧。
点击查看代码
struct node { int nxt,to; }e[40010]; int head[210],dfn[210],low[210],ins[210],col[210],u[210],v[210],tot=0,scc_cnt=0,cnt=0; stack<int>s; 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; ins[x]=1; 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],dfn[e[i].to]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],low[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; } } } void print(int n) { for(int i=1;i<=n;i++) { if(col[i]==col[i+n]) { cout<<"Impossible"<<endl; return; } } for(int i=1;i<=n;i++) { cout<<(col[i+n]<col[i]?"i":"o"); } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,i,j; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u[i]>>v[i]; if(u[i]>v[i]) { swap(u[i],v[i]); } } for(i=1;i<=m;i++) { for(j=1;j<=m;j++) { if(u[i]<u[j]&&u[j]<v[i]&&v[i]<v[j]) { add(i,j+m); add(i+m,j); add(j+m,i); add(j,i+m); } } } for(i=1;i<=m*2;i++) { if(dfn[i]==0) { tarjan(i); } } print(m); return 0; }
CF1239D Catowice City
-
在最终的解中,编号为 \(i\) 要么选入人集合,要么选入猫集合。故可以用 \(0/1\) 分别表示。
-
难点在于 \(j,p \ge 1\) 的限制。
-
由于猫两两之间互不认识,故只有人之间的必选关系。对于强连通分量内的点要么全选人,要么全选猫。
-
优先选择出度为 \(0\) 的强连通分量让其全选人,不妨取编号为 \(1\) 的强连通分量即可。
点击查看代码
struct node { int nxt,to; }e[1000010]; int head[1000010],dfn[1000010],low[1000010],ins[1000010],siz[1000010],col[1000010],cnt=0,tot=0,scc_cnt=0; stack<int>s; 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; ins[x]=1; 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]); } else { if(ins[e[i].to]==1) { low[x]=min(low[x],dfn[e[i].to]); } } } if(dfn[x]==low[x]) { scc_cnt++; int tmp=0; while(x!=tmp) { tmp=s.top(); s.pop(); ins[tmp]=0; col[tmp]=scc_cnt; siz[scc_cnt]++; } } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,m,u,v,i,j; scanf("%d",&t); for(j=1;j<=t;j++) { scanf("%d%d",&n,&m); fill(e+1,e+1+cnt,(node){0,0}); fill(head+1,head+1+n,0); fill(dfn+1,dfn+1+n,0); fill(siz+1,siz+1+n,0); cnt=scc_cnt=tot=0; for(i=1;i<=m;i++) { scanf("%d%d",&u,&v); if(u!=v) { add(u,v); } } for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } if(scc_cnt==1) { printf("No\n"); } else { printf("Yes\n"); printf("%d %d\n",siz[1],n-siz[1]); for(i=1;i<=n;i++) { if(col[i]==1) { printf("%d ",i); } } printf("\n"); for(i=1;i<=n;i++) { if(col[i]!=1) { printf("%d ",i); } } printf("\n"); } } return 0; }
12.25
闲话
- 侯操时德育主任说了下今天上午的安排:早预备前一切照常,早预备时原班主任在班内召开小班会,公示学生去向,然后搬教室,在 \(9:20\) 前在新教室做好并清点人数,然后在 \(9:40\) 时去搬宿舍。期间若教室桌椅不够则需要去滏阳楼的 \(1,3,4\) 楼的空教室搬,特别强调 \(2\) 楼的奥赛教室和奥赛教研室的桌椅不能动。他和年级主任又强调了下搬教室、宿舍期间的纪律和注意安全。准备回教室时班主任把我们叫到一边说让女生去教室正常上早读,男生拿着卫生工具去打扫滏阳楼 \(2 \sim 4\) 楼的宿舍。到那里后,在宿舍内发现了大量前人的馈赠,包括但不限于猫鼠的卡贴粘在了柜子上、体育赛事相关海报、《明日方舟》和《蔚蓝档案》的游戏海报、小说海报、航天明信片。收拾完又回班上了约 \(10 \min\) 早读。
- 吃完早饭后来机房拿了个包,坐电梯的时候遇到了 \(field\) 并进行了简单交流。早预备时班主任放了下新宿舍安排(将原一个宿舍 \(12\) 个人拆成了一个宿舍 \(8\) 个人,上下铺不动)后就让男生去搬宿舍,女生去守着滏阳楼 \(2\) 楼的奥赛教室和教研室。收拾宿舍的东西因早就准备好了,所以两趟就搬完了。搬完后闲逛了会儿,并得知我们仍在原教学楼上课,教室不用再搬到这边了。回班后发现新教室在原教室对面的楼上,原教室变成了班里临时上课用的奥赛教室,故我们可以有 \(2\) 个柜子,但以后回班要多爬一层楼了。
- 收拾完教室就直接来机房了,听多校的讲字符串。
- 下午放我的每日一歌《奔赴光芒》。 \(huge\) 还问这是游戏还是动漫,我们都玩这个吗。 \(huge\) 说我们不能只喊“加把劲”,要把它落实到行动中,和其他人时间上的差距学校会尽可能帮我们解决,但其他的就要靠我们自己了。
- 晚上 \(field\) 让我们在晚三的时候收拾一下明天补课要用的书。而且因为周四没有奥赛课所以以后预计都在周四进行补课。
做题纪要
BZOJ4771 七彩树
-
对深度开一棵主席树,然后查询子树内部的答案。难点在于怎么进行区间数颜色,本质上是在做一个树链的并。
-
若没有深度的限制,可以将同一个颜色的点按照 \(DFS\) 序排序后,每个点给自己的位置贡献 \(1\) ,相邻两个点的 \(\operatorname{LCA}\) 处贡献 \(-1\) 。
- 由 CF1062E Company 相关结论可知做法的正确性。
-
但是现在有深度的限制,考虑如何去消除这个影响以及扩展上面的做法。深度的限制实际上是在限制某个点是否加入,类似的我们可以得到处理方法:每个点 \(x\) 给自己的位置贡献 \(1\) ,前驱 \(pre\) 和 \(u\) 的 \(\operatorname{LCA}\) 贡献 \(-1\) ,后继 \(suf\) 和 \(u\) 的 \(\operatorname{LCA}\) 贡献 \(-1\) ,\(pre\) 和 \(suf\) 的 \(\operatorname{LCA}\) 贡献 \(1\) 。
-
主席树维护权值和即可。
点击查看代码
struct node { int nxt,to; }e[100010]; int head[100010],a[100010],fa[100010],siz[100010],dep[100010],son[100010],top[100010],dfn[100010],pos[100010],out[100010],tot=0,cnt=0; vector<int>col[100010]; set<int>s[100010]; set<int>::iterator it,pre,nxt; void add(int u,int v) { cnt++; e[cnt].nxt=head[u]; e[cnt].to=v; head[u]=cnt; } void dfs1(int x) { son[x]=0; siz[x]=1; dep[x]=dep[fa[x]]+1; col[dep[x]].push_back(x); for(int i=head[x];i!=0;i=e[i].nxt) { dfs1(e[i].to); 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!=son[x]) { dfs2(e[i].to,e[i].to); } } } out[x]=tot; } 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; } struct PDS_SMT { int root[100010],rt_sum=0; struct SegmentTree { int ls,rs,sum; }tree[100010<<7]; #define lson(rt) (tree[rt].ls) #define rson(rt) (tree[rt].rs) void clear() { rt_sum=0; } int build_rt() { rt_sum++; lson(rt_sum)=rson(rt_sum)=tree[rt_sum].sum=0; return rt_sum; } void update(int pre,int &rt,int l,int r,int pos,int val) { rt=build_rt(); tree[rt]=tree[pre]; tree[rt].sum+=val; if(l==r) { return; } int 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); } } int query(int rt,int l,int r,int x,int y) { if(x<=l&&r<=y) { return tree[rt].sum; } int mid=(l+r)/2,ans=0; if(x<=mid) { ans+=query(lson(rt),l,mid,x,y); } if(y>mid) { ans+=query(rson(rt),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 int t,n,m,x,d,ans,i,j,k; scanf("%d",&t); for(k=1;k<=t;k++) { scanf("%d%d",&n,&m); fill(e+1,e+1+cnt,(node){0,0}); fill(head+1,head+1+n,0); tot=cnt=ans=0; T.clear(); for(i=1;i<=n;i++) { scanf("%d",&a[i]); s[i].clear(); col[i].clear(); } for(i=2;i<=n;i++) { scanf("%d",&fa[i]); add(fa[i],i); } dfs1(1); dfs2(1,1); for(i=1;i<=n;i++) { T.root[i]=T.root[i-1]; for(j=0;j<col[i].size();j++) { s[a[col[i][j]]].insert(dfn[col[i][j]]); it=s[a[col[i][j]]].lower_bound(dfn[col[i][j]]); T.update(T.root[i],T.root[i],1,n,dfn[col[i][j]],1); if(it!=s[a[col[i][j]]].begin()) { pre=prev(it); T.update(T.root[i],T.root[i],1,n,dfn[lca(col[i][j],pos[*pre])],-1); } nxt=s[a[col[i][j]]].upper_bound(dfn[col[i][j]]); if(nxt!=s[a[col[i][j]]].end()) { T.update(T.root[i],T.root[i],1,n,dfn[lca(col[i][j],pos[*nxt])],-1); } if(it!=s[a[col[i][j]]].begin()&&nxt!=s[a[col[i][j]]].end()) { T.update(T.root[i],T.root[i],1,n,dfn[lca(pos[*pre],pos[*nxt])],1); } } } for(i=1;i<=m;i++) { scanf("%d%d",&x,&d); x^=ans; d^=ans; ans=T.query(T.root[min(n,dep[x]+d)],1,n,dfn[x],out[x]); printf("%d\n",ans); } } return 0; }
luogu P7515 [省选联考 2021 A 卷] 矩阵游戏
luogu P6628 [省选联考 2020 B 卷] 丁香之路
luogu P5840 [COCI2015] Divljak
-
对 \(\{ S \}\) 建出 \(AC\) 自动机后考虑在 \(fail\) 树上进行操作。
-
如果询问的是出现次数就跟 CF163E e-Government 一样做了,但本题询问的是树链的并,按照 \(DFS\) 序排序后树上差分即可。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[2000010],fa[2000010],siz[2000010],dep[2000010],son[2000010],top[2000010],dfn[2000010],out[2000010],pos[2000010],tot=0,cnt=0,zero; char s[2000010]; 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; son[x]=zero; 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; tot++; dfn[x]=tot; pos[tot]=x; if(son[x]!=zero) { 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); } } } out[x]=tot; } 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; } struct BIT { int c[2000010]; 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; } } int getsum(int x) { int ans=0; for(int i=x;i>=1;i-=lowbit(i)) { ans+=c[i]; } return ans; } }B; struct ACAM { int trie[2000010][30],fail[2000010],ed[2000010],rt_sum=0; vector<int>g; int val(char x) { return x-'a'+1; } void insert(char s[],int len,int idx) { int x=0; for(int i=1;i<=len;i++) { if(trie[x][val(s[i])]==0) { rt_sum++; trie[x][val(s[i])]=rt_sum; } x=trie[x][val(s[i])]; } ed[idx]=x; } void build() { queue<int>q; for(int i=1;i<=26;i++) { if(trie[0][i]!=0) { fail[trie[0][i]]=0; add(0,trie[0][i]); q.push(trie[0][i]); } } while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=1;i<=26;i++) { if(trie[x][i]==0) { trie[x][i]=trie[fail[x]][i]; } else { fail[trie[x][i]]=trie[fail[x]][i]; add(fail[trie[x][i]],trie[x][i]); q.push(trie[x][i]); } } } } void update(char s[],int len) { g.clear(); int x=0; for(int i=1;i<=len;i++) { x=trie[x][val(s[i])]; B.add(tot,dfn[x],1); g.push_back(dfn[x]); } sort(g.begin(),g.end()); for(int i=0;i+1<g.size();i++) { B.add(tot,dfn[lca(pos[g[i]],pos[g[i+1]])],-1); } } int query(int x) { return B.getsum(out[ed[x]])-B.getsum(dfn[ed[x]]-1); } }A; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,pd,x,i; cin>>n; for(i=1;i<=n;i++) { cin>>(s+1); A.insert(s,strlen(s+1),i); } A.build(); zero=A.rt_sum+1; dfs1(0,zero); dfs2(0,0); cin>>m; for(i=1;i<=m;i++) { cin>>pd; if(pd==1) { cin>>(s+1); A.update(s,strlen(s+1)); } else { cin>>x; cout<<A.query(x)<<endl; } } return 0; }
CF713C Sonya and Problem Wihtout a Legend
luogu P4597 序列 sequence
-
由 Slope Trick 更新方式,我们一定保证了最后的决策点在原数列中出现过,且每次更新时的堆顶一定也在原数列中出现过。
-
观察到 Slope Trick 本质上是在维护前缀子问题的决策点,故在得到整个问题的最后一个决策点时可以利用凸包的性质倒推回去,写一个后缀 \(\min\) 即可,因此保证了一定在原数列中出现过。
点击查看代码
priority_queue<ll>q; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,x,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>x; if(q.empty()==0&&q.top()>x) { ans+=q.top()-x; q.pop(); q.push(x); } q.push(x); } cout<<ans<<endl; return 0; }
luogu P2893 [USACO08FEB] Making the Grade G
-
两种情况取 \(\min\) 。
点击查看代码
ll a[2010]; priority_queue<ll>q; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans1=0,ans2=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; if(q.empty()==0&&q.top()>a[i]) { ans1+=q.top()-a[i]; q.pop(); q.push(a[i]); } q.push(a[i]); } while(q.empty()==0) { q.pop(); } for(i=n;i>=1;i--) { if(q.empty()==0&&q.top()>a[i]) { ans2+=q.top()-a[i]; q.pop(); q.push(a[i]); } q.push(a[i]); } cout<<min(ans1,ans2)<<endl; return 0; }
12.26
闲话
- 跑操的位置变成了揽月楼前,花费了一些时间寻找本班跑操位置。
- 因为这一周班里基本没怎么正经上课,所以补课很快就补完了。 \(miaomiao\) 说以后暂定每周只补一天课,让我们每周只少打一场模拟赛,不会跟高二的进度差距太大,而且 @Charlie_ljk 等人跟我们的进度差距也不会太大。
- 看了眼新学号是 \(52\) 号,还没 @wkh2008 学号高。
- 下午放 @jijidawang 的每日一歌《一沙一世界》。
- 机房多了几盆盆栽。
做题纪要
luogu P3604 美好的每一天
-
将字符集出现次数状压做前缀异或和后要求至多有一位为 \(1\) 。离散化后莫队开桶维护即可。
点击查看代码
struct ask { int l,r,id; }q[60010]; int a[60010],d[1630010],to[60010][26],cnt[1630010],pos[60010],L[60010],R[60010],ans[60010],klen,ksum; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?((pos[a.l]%2==1)?(a.r<b.r):(a.r>b.r)):(a.l<b.l); } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void add(int x,int &sum) { sum+=cnt[a[x]]; cnt[a[x]]++; for(int i=0;i<=25;i++) { sum+=cnt[to[x][i]]; } } void del(int x,int &sum) { cnt[a[x]]--; sum-=cnt[a[x]]; for(int i=0;i<=25;i++) { sum-=cnt[to[x][i]]; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l=1,r=0,sum=0,i,j; char c; cin>>n>>m; n++; for(i=2;i<=n;i++) { cin>>c; a[i]=a[i-1]^(1<<(c-'a')); } for(i=1;i<=n;i++) { d[0]++; d[d[0]]=a[i]; for(j=0;j<=25;j++) { to[i][j]=a[i]^(1<<j); d[0]++; d[d[0]]=to[i][j]; } } sort(d+1,d+1+d[0]); d[0]=unique(d+1,d+1+d[0])-(d+1); for(i=1;i<=n;i++) { a[i]=lower_bound(d+1,d+1+d[0],a[i])-d; for(j=0;j<=25;j++) { to[i][j]=lower_bound(d+1,d+1+d[0],to[i][j])-d; } } for(i=1;i<=m;i++) { cin>>q[i].l>>q[i].r; q[i].id=i; q[i].r++; } init(n,m); sort(q+1,q+1+m,q_cmp); for(i=1;i<=m;i++) { while(l>q[i].l) { l--; add(l,sum); } while(r<q[i].r) { r++; add(r,sum); } while(l<q[i].l) { del(l,sum); l++; } while(r>q[i].r) { del(r,sum); r--; } ans[q[i].id]=sum; } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
[ABC217H] Snuketoon
luogu P4331 [BalticOI 2004] Sequence 数字序列
-
将 \(a_{i},b_{i}\) 都先减去 \(i\) ,转化为单调不降问题后和 luogu P4597 序列 sequence 一样做即可。
点击查看代码
ll a[1000010],b[1000010]; priority_queue<ll>q; 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]; a[i]-=i; if(q.empty()==0&&q.top()>a[i]) { ans+=q.top()-a[i]; q.pop(); q.push(a[i]); } q.push(a[i]); b[i]=q.top(); } cout<<ans<<endl; for(i=n-1;i>=1;i--) { b[i]=min(b[i],b[i+1]); } for(i=1;i<=n;i++) { cout<<b[i]+i<<" "; } return 0; }
CF1534G A New Beginning
-
将切比雪夫距离转换成曼哈顿距离,有新坐标为 \((\frac{x_{i}+y_{i}}{2},\frac{x_{i}-y_{i}}{2})\) ,因带一个 \(\frac{1}{2}\) 的常数不妨提出来得到 \((x_{i}'=x_{i}+y_{i},y_{i}'=x_{i}-y_{i})\) 最后统一乘起来。
-
此时切比雪夫坐标系里 \((x,y)\) 向上/右走分别对应曼哈顿坐标系里走到 \((x+1,y+1)/(x+1,y-1)\) ,即走路斜率对应 \(1/-1\) 。
-
将所有点按照横坐标排序后,设 \(f_{i,j}\) 表示处理第 \(i\) 个点时纵坐标为 \(j\) 时的最小花费,状态转移方程为 \(f_{i,j}=\min\limits_{k=j-(x_{i}'-x_{i-1}')}^{j+(x_{i}'-x_{i-1}')}\{ f_{i-1,k} \} +|j-y_{i}'|\) ,使用 Slope Trick 优化即可,做法和 [ABC217H] Snuketoon 基本类似。
点击查看代码
pair<ll,ll>a[800010]; priority_queue<ll,vector<ll>,less<ll> >l; priority_queue<ll,vector<ll>,greater<ll> >r; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,ans=0,x,y,lazyl=0,lazyr=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>x>>y; a[i].first=x+y; a[i].second=x-y; l.push(0); r.push(0); } sort(a+1,a+1+n); for(i=1;i<=n;i++) { lazyl-=a[i].first-a[i-1].first; lazyr+=a[i].first-a[i-1].first; if(a[i].second<l.top()+lazyl) { ans+=(l.top()+lazyl)-a[i].second; r.push(l.top()+lazyl-lazyr); l.pop(); l.push(a[i].second-lazyl); l.push(a[i].second-lazyl); } else { if(a[i].second>r.top()+lazyr) { ans+=a[i].second-(r.top()+lazyr); l.push(r.top()+lazyr-lazyl); r.pop(); r.push(a[i].second-lazyr); r.push(a[i].second-lazyr); } else { l.push(a[i].second-lazyl); r.push(a[i].second-lazyr); } } } cout<<ans/2<<endl; return 0; }
12.27
闲话
- 侯操地点又改成了格物楼门口前,花费了一些时间寻找侯操地点。侯操时得知年级部对奥赛生的宿舍要进行突击检查,所以把宿舍长都带回去进行整改提升了。
- 上午有美国师生交流团来学校访问,年级找了几个实验班中的优秀学生前去一起开会。
- 吃完早饭到机房后 \(miaomiao\) 让我们写字符串专题,但我们鉴于动态规划专题基本没怎么写所以集体继续写动态规划专题,而高二那边则集体写字符串专题。
- 中午回宿舍后发现因早上整改提升需要,把部分宿舍内的爬楼车、拉杆箱(包)、大包统一放到了旁边的空宿舍。
- 冲刺省选的倒计时展牌已经贴在外面楼道里了。
- 下午 \(feifei\) 把我们拉进了多校的 \(Vjudge\) 团队里。 \(miaomiao\) 过来转的时候才得知我们今天没写字符串专题,故让我们晚上讲题的时候分开讲。
- 晚上讲题的时候 \(miaomiao\) 问我们原班的柜子里还有没有我们的东西,因为一些原因原班的柜子又不给我们用了,让我们课间的时候回去把书收拾一下。毕竟中午回宿舍后听说已经有人把整列柜子搬走了。
做题纪要
luogu P4322 [JSOI2016] 最佳团体
luogu P8392 [BalticOI 2022 Day1] Uplifting Excursion
luogu P10532 [XJTUPC2024] 筛法
-
推式子,有 \(\begin{aligned} & \sum\limits_{i=1}^{n}\sum\limits_{j=1}^{n} \left\lfloor \frac{n}{\max(i,j)} \right\rfloor [\gcd(i,j)=1] \\ &=-n+2\sum\limits_{i=1}^{n}\sum\limits_{j=1}^{i} \left\lfloor \frac{n}{i} \right\rfloor [\gcd(i,j)=1] \\ &=-n+2\sum\limits_{i=1}^{n} \left\lfloor \frac{n}{i} \right\rfloor \varphi(i) \\ &=-n+2\sum\limits_{i=1}^{n}\sum\limits_{d|i}\varphi(d) \\ &=-n+2\sum\limits_{i=1}^{n}i \\ &=n^{2} \end{aligned}\) 。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n; cin>>n; cout<<n*n<<endl; return 0; }
luogu P3592 [POI2015] MYJ
luogu P6563 [SBCOI2020] 一直在你身旁
12.28
闲话
- 早上起床后感觉有点上火。
- 上午 \(7:45 \sim 12:15\) 打学校 \(OJ\) 模拟赛。
- 因中午吃饭前 @xrlong 和 @ppllxx_9G 忘关显示器了,被 \(huge\) 拍照保留证据。下午到机房后 \(huge\) 让他俩上去选歌,放的是《经过》。然后讲题。
- 准备上体活前 \(field\) 说明天高二的写动态规划专题,高一的写字符串专题,在明天晚上 \(7:00\) 把题讨论一下。
- 体活的时候去吃饭的路上因和 @GGrun 一起坐电梯被 \(field\) 肘了。
- 晚上 \(huge\) 问高二的再给一天能不能把字符串专题写完,但碍于题目前置知识点过多,遂让高二的调了几个题给我们做,我们也调几个题给他们做,而且讨论时间挪到了明天上午 \(11:00\) 。还说现在我们写题就看题过于简单或者过于难就不写了,代码还得多写;让我们先以统一安排的任务为主,然后再去做自己的安排。
做题纪要
[AGC026D] Histogram Coloring
QOJ 1173. Knowledge Is..
QOJ 838. Horrible Cycles
CF1372E Omkar and Last Floor
12.29
闲话
- 早上有体活。
- 吃完晚饭回机房的时候正好碰见 \(field\) 从电梯出来,问我今天题能不能讨论,我说字符串专题估计能讨论三四个;回到机房后 \(field\) 说讨论时间又挪到了明天晚上。
- 晚上临下课前 \(field\) 问 @wkh2008 我们宿舍住在几楼。然后等回到宿舍后就发现他来查宿了,还进了我们宿舍跟我们说之前因这里很长时间没有学生来住,所以被学校用来当老师宿舍了,一个宿舍住两个人,他还在我们所住的宿舍住过小半年呢。
做题纪要
luogu P1117 [NOI2016] 优秀的拆分
luogu P5410 【模板】扩展 KMP/exKMP(Z 函数)
-
\(exkmp / Z\) 函数板子。
- 字符串 \(s\) 下标从 \(1\) 开始,并设 \(n=|s|\) 。
- \(exkmp\) 算法可以在 \(O(n)\) 的时间复杂度内求出 \(\forall i \in [1,n],z_{i}=\operatorname{LCP}(s,s_{i \sim n})\) ,其中 \(z_{1}=n\) 。
- 顺序枚举 \(i \in [2,n]\) 的过程中,类似马拉车的思想,我们维护 \(s\) 最靠右的匹配子串 \([l,r]\) 。
- 当 \(i \le r\) 时,由定义可知 \(s_{1 \sim r-l+1}=s_{l \sim r}\) 。若 \(z_{i-l+1}<r-i+1\) 则后续不可能再继续扩展且可以直接继承 \(z_{i}=z_{i-l+1}\) ;否则无法判断超出的部分是否后面能一并得到扩展,故有 \(z_{i}=r-i+1\) 然后暴力进行扩展。
- 当 \(i>r\) 时,仍暴力进行扩展。
- 最终若 \(i+z_{i}-1>r\) 则 \(\begin{cases} l \gets i \\ r \gets i+z_{i}-1 \end{cases}\) 。
- 文本串 \(s\) 和模式串 \(t\) 匹配时可以将这两个串用分隔符拼起来并求出 \(\{ z \}\) 后得到答案;也可以先对 \(s\) 求出 \(z_{s}\) 后类似上面的过程分讨继承关系进行更新(此时 \(i \in [1,n]\) )。
点击查看代码
ll z[20000010],p[20000010]; char s[20000010],t[20000010]; void Z(char s[],ll n) { z[1]=n; for(ll i=2,l=0,r=0;i<=n;i++) { for(z[i]=(i<=r)?min(z[i-l+1],r-i+1):0;i+z[i]<=n&&s[i+z[i]]==s[z[i]+1];z[i]++); if(i+z[i]-1>r) { l=i; r=i+z[i]-1; } } } void exkmp(char s[],ll n,char t[],ll m) { Z(s,n); for(ll i=1,l=0,r=0;i<=m;i++) { for(p[i]=(i<=r)?min(z[i-l+1],r-i+1):0;i+p[i]<=m&&t[i+p[i]]==s[p[i]+1];p[i]++); if(i+p[i]-1>r) { l=i; r=i+p[i]-1; } } } ll val(ll a[],ll n) { ll ans=0; for(ll i=1;i<=n;i++) { ans^=i*(a[i]+1); } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,m,i; cin>>(t+1)>>(s+1); n=strlen(s+1); m=strlen(t+1); exkmp(s,n,t,m); cout<<val(z,n)<<endl; cout<<val(p,m)<<endl; return 0; }
QOJ 8701.Border
luogu P3449 [POI2006] PAL-Palindromes
-
\(s,t\) 拼接后是回文串当且仅当 \(s+t=t+s\) ,哈希移项后判断。
点击查看代码
const ll mod=1000003579,base=13331; char s[2000010]; map<ll,ll>f; map<ll,ll>::iterator it; 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; } ll ask_hash(char s[],ll len) { ll ans=0; for(ll i=1;i<=len;i++) { ans=(ans*base%mod+s[i])%mod; } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,len,ans=0,i; cin>>n; for(i=1;i<=n;i++) { cin>>len>>(s+1); f[ask_hash(s,len)*qpow((qpow(base,len,mod)-1+mod)%mod,mod-2,mod)%mod]++; } for(it=f.begin();it!=f.end();it++) { ans+=(it->second)*(it->second); } cout<<ans<<endl; return 0; }
牛客周赛 Round 74 A 彩虹糖的梦
-
顺序结构。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int a,b,c,d,e,f,g; cin>>a>>b>>c>>d>>e>>f>>g; cout<<min({a,b,c,d,e,f,g})<<endl; return 0; }
牛客周赛 Round 74 B 球格模型(简单版)
-
先放主对角线上的元素。
点击查看代码
vector<int>a[1000010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,k,i,j; cin>>n>>m>>k; if(max(n,m)<=k) { for(i=1;i<=n;i++) { a[i].resize(m+10); } if(n<=m) { for(i=1;i<=n;i++) { k--; a[i][i]=1; } for(i=n+1;i<=m;i++) { k--; a[1][i]=1; } } else { for(i=1;i<=m;i++) { k--; a[i][i]=1; } for(i=m+1;i<=n;i++) { k--; a[i][1]=1; } } a[1][1]+=k; for(i=1;i<=n;i++) { for(j=1;j<=m;j++) { cout<<a[i][j]<<" "; } cout<<endl; } } else { cout<<"-1"<<endl; } return 0; }
牛客周赛 Round 74 C 小数字
-
无脑开根到 \(2\) 后再执行 \(-1\) 操作。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,m,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n>>m; for(;n>2&&m>=1;m--) { n=ceil(sqrt(n)); } cout<<n-m<<endl; } return 0; }
牛客周赛 Round 74 D 预知
-
若除了最大值之外都是 \(1\) 则最大值减一即为所求,否则最大值即为所求。
点击查看代码
int a[200010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,maxx,sum,i,j; cin>>t; for(j=1;j<=t;j++) { cin>>n; maxx=sum=0; for(i=1;i<=n;i++) { cin>>a[i]; sum+=(a[i]==1); maxx=max(maxx,a[i]); } if(n==1) { cout<<-1<<endl; } else { if(maxx==1) { cout<<0<<endl; } else { cout<<maxx-(sum==n-1)<<endl; } } } return 0; }
牛客周赛 Round 74 F 最大最小路
-
容斥完有 \(|\{ x \to y | 1\le x<y \le n \}| \\ -|\{ x \to y | 1\le x<y \le n,\min\limits_{z \in (x \to y) } \{ c_{z} \}>a \}| \\ -|\{ x \to y | 1\le x<y \le n,\max\limits_{z \in (x \to y) } \{ c_{z} \}<b \}| \\ +|\{ x \to y | 1\le x<y \le n,\min\limits_{z \in (x \to y) } \{ c_{z} \}>a,\max\limits_{z \in (x \to y) } \{ c_{z} \}<b \}|\) 即为所求。
点击查看代码
ll c[500010]; pair<ll,ll>e[500010]; struct DSU { ll fa[500010],siz[500010]; void init(ll n) { for(ll i=1;i<=n;i++) { fa[i]=i; siz[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; siz[y]+=siz[x]; } } }D; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll n,a,b,ans=0,i; cin>>n>>a>>b; for(i=1;i<=n;i++) { cin>>c[i]; } ans=n*(n-1)/2; D.init(n); for(i=1;i<=n-1;i++) { cin>>e[i].first>>e[i].second; if(c[e[i].first]>a&&c[e[i].second]>a) { D.merge(e[i].first,e[i].second); } } for(i=1;i<=n;i++) { ans-=(D.fa[i]==i)*D.siz[i]*(D.siz[i]-1)/2; } D.init(n); for(i=1;i<=n-1;i++) { if(c[e[i].first]<b&&c[e[i].second]<b) { D.merge(e[i].first,e[i].second); } } for(i=1;i<=n;i++) { ans-=(D.fa[i]==i)*D.siz[i]*(D.siz[i]-1)/2; } D.init(n); for(i=1;i<=n-1;i++) { if(min(c[e[i].first],c[e[i].second])>a&&max(c[e[i].first],c[e[i].second])<b) { D.merge(e[i].first,e[i].second); } } for(i=1;i<=n;i++) { ans+=(D.fa[i]==i)*D.siz[i]*(D.siz[i]-1)/2; } cout<<ans<<endl; return 0; }
牛客周赛 Round 74 E 而后单调
-
有解当且仅当 \(\{ a \}\) 无重复元素且离散化后存在一个长度为 \(m\) 的区间值域连续,和 luogu P1420 最长连号 一样做即可。
点击查看代码
int a[200010],b[200010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,n,m,flag,sum,i,j,k; cin>>t; for(j=1;j<=t;j++) { flag=0; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=a[i]; } sort(b+1,b+1+n); b[0]=unique(b+1,b+1+n)-(b+1); if(b[0]==n) { for(i=1;i<=n;i++) { a[i]=lower_bound(b+1,b+1+n,a[i])-b; } for(i=1,sum=0;i<=n;i++) { sum=(a[i]-a[i-1]==1)?(sum+1):1; flag|=(sum>=m); } for(i=1,sum=0;i<=n;i++) { sum=(a[i]-a[i-1]==-1)?(sum+1):1; flag|=(sum>=m); } cout<<((flag==1)?"YES":"NO")<<endl; } else { cout<<"NO"<<endl; } } return 0; }
12.30
闲话
- 早上起床后听见高二所在的大操场在放《相信自己》。
- 下午放 \(huge\) 的每日一歌《夜空中最亮的星》,说以后要是我们不给他推歌的话就放这个了,也省得四处找了;高二的组织了动态规划专题的讨论。
- 吃完晚饭后 \(feifei\) 给我们发了学校给老师发的坚果,说是他也给我们发福利。坚果是企业先赞助给了学校,然后学校给每个老师都发了一箱,估计等我们高三的时候也会每人发一箱或发火腿肠。
- 晚上临下课的时候 \(feifei\) 说给我们说点让我们高兴的事情--明天放假,在发现我们没什么反应后又说怎么感觉我们都不是很高兴。然后说了下明天的安排,高二的吃完午饭后要进行补课,直接来机房就行,体育课被取消了;因他认为 \(11:45\) 下课所以需要正常打模拟赛,高一的少打 \(15 \min\) 。
- 回宿舍后得知了是 \(11:45\) 出校,恼。
做题纪要
luogu P11485 「Cfz Round 5」Non-breath Oblige
-
按位拆 \(\begin{cases} 1 \to 1 \\ 1 \to 0 \\ 0 \to 1 \\ 0 \to 0 \end{cases}\) 的贡献,归约后有 \((2^{n}-1) \bigoplus s+(2^{n}-1) \bigoplus t\) 即为所求。
点击查看代码
int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif ll testcase,n,s,t,i; cin>>testcase; for(i=1;i<=testcase;i++) { cin>>n>>s>>t; cout<<(s!=t)*((((1<<n)-1)^s)+(((1<<n)-1)^t))<<endl; } return 0; }
QOJ 8079.Range Periodicity Query
[ABC248D] Range Count Query
-
二分。
点击查看代码
int a[200010]; vector<int>pos[200010]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l,r,x,i; cin>>n; for(i=1;i<=n;i++) { cin>>a[i]; pos[a[i]].push_back(i); } cin>>m; for(i=1;i<=m;i++) { cin>>l>>r>>x; cout<<(upper_bound(pos[x].begin(),pos[x].end(),r)-lower_bound(pos[x].begin(),pos[x].end(),l))<<endl; } return 0; }
[ABC242G] Range Pairing Query
-
莫队。
点击查看代码
struct ask { int l,r,id; }q[1000010]; int a[100010],cnt[100010],pos[100010],L[100010],R[100010],ans[1000010],klen,ksum; bool q_cmp(ask a,ask b) { return (pos[a.l]==pos[b.l])?((pos[a.l]%2==1)?(a.r<b.r):(a.r>b.r)):(a.l<b.l); } void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } } } void add(int x,int &sum) { sum-=cnt[a[x]]/2; cnt[a[x]]++; sum+=cnt[a[x]]/2; } void del(int x,int &sum) { sum-=cnt[a[x]]/2; cnt[a[x]]--; sum+=cnt[a[x]]/2; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l=1,r=0,sum=0,i; 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",&q[i].l,&q[i].r); q[i].id=i; } init(n,m); sort(q+1,q+1+m,q_cmp); for(i=1;i<=m;i++) { while(l>q[i].l) { l--; add(l,sum); } while(r<q[i].r) { r++; add(r,sum); } while(l<q[i].l) { del(l,sum); l++; } while(r>q[i].r) { del(r,sum); r--; } ans[q[i].id]=sum; } for(i=1;i<=m;i++) { cout<<ans[i]<<endl; } return 0; }
[ABC237G] Range Sort Query
-
先后赋予 \(\ge x\) 和 \(<x\) 的数与 \(>x\) 和 \(\le x\) 的数不同的权值,用手动推平实现排序。
点击查看代码
int a[200010],b[200010],c[200010]; struct ODT { struct node { int l,r; mutable int col; bool operator < (const node &another) const { return l<another.l; } }; set<node>s; void init(int n,int a[]) { for(int i=1;i<=n;i++) { s.insert((node){i,i,a[i]}); } } set<node>::iterator split(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it!=s.end()&&it->l==pos) { return it; } it--; if(it->r<pos) { return s.end(); } int l=it->l,r=it->r,col=it->col; s.erase(it); s.insert((node){l,pos-1,col}); return s.insert((node){pos,r,col}).first; } void assign(int l,int r,int col) { set<node>::iterator itr=split(r+1),itl=split(l); s.erase(itl,itr); s.insert((node){l,r,col}); } int query(int l,int r) { set<node>::iterator itr=split(r+1),itl=split(l); int ans=0; for(set<node>::iterator it=itl;it!=itr;it++) { ans+=it->col*(it->r-it->l+1); } return ans; } int ask(int pos) { set<node>::iterator it=s.lower_bound((node){pos,0,0}); if(it!=s.end()&&it->l==pos) { return it->col; } it--; return it->col; } }O[2]; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,x,pd,l,r,i,j; cin>>n>>m>>x; for(i=1;i<=n;i++) { cin>>a[i]; b[i]=(a[i]>=x); c[i]=(a[i]>x); } O[0].init(n,b); O[1].init(n,c); for(i=1;i<=m;i++) { cin>>pd>>l>>r; if(pd==1) { for(j=0;j<=1;j++) { pd=O[j].query(l,r); O[j].assign(l,r-pd,0); O[j].assign(r-pd+1,r,1); } } else { for(j=0;j<=1;j++) { pd=O[j].query(l,r); O[j].assign(l,l+pd-1,1); O[j].assign(l+pd,r,0); } } } for(i=1;i<=n;i++) { if(O[0].ask(i)==1&&O[1].ask(i)==0) { cout<<i<<endl; } } return 0; }
luogu P5048 [Ynoi2019 模拟赛] Yuno loves sqrt technology III
-
先预处理出整块间的区间众数的出现次数。查询时先得到整块的答案。
-
考虑散块中的元素能否进一步将答案扩大,使用
vector
存储出现位置和第几次出现。 -
块长取 \(\frac{n}{\sqrt{m}}\) ,时间复杂度为 \(O(n\sqrt{m})\) 。
点击查看代码
int a[500010],f[800][800],pos[500010],L[500010],R[500010],id[500010],cnt[500010],klen,ksum; vector<int>idx[500010]; void init(int n,int m) { klen=n/sqrt(m)+1; ksum=n/klen; for(int i=1;i<=ksum;i++) { L[i]=R[i-1]+1; R[i]=R[i-1]+klen; } if(R[ksum]<n) { ksum++; L[ksum]=R[ksum-1]+1; R[ksum]=n; } for(int i=1;i<=ksum;i++) { for(int j=L[i];j<=R[i];j++) { pos[j]=i; } int ans=0; for(int j=i;j<=ksum;j++) { for(int k=L[j];k<=R[j];k++) { cnt[a[k]]++; ans=max(ans,cnt[a[k]]); } f[i][j]=ans; } memset(cnt,0,sizeof(cnt)); } } int query(int l,int r) { int ans=0; if(pos[l]==pos[r]) { for(int i=l;i<=r;i++) { cnt[a[i]]++; ans=max(ans,cnt[a[i]]); } for(int i=l;i<=r;i++) { cnt[a[i]]--; } } else { ans=f[pos[l]+1][pos[r]-1]; for(int i=l;i<=R[pos[l]];i++) { for(;id[i]+ans<idx[a[i]].size()&&idx[a[i]][id[i]+ans]<=r;ans++); } for(int i=L[pos[r]];i<=r;i++) { for(;id[i]-ans>=0&&idx[a[i]][id[i]-ans]>=l;ans++); } } return ans; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,l,r,ans=0,i; cin>>n>>m; for(i=1;i<=n;i++) { cin>>a[i]; idx[a[i]].push_back(i); id[i]=idx[a[i]].size()-1; } init(n,m); for(i=1;i<=m;i++) { cin>>l>>r; l^=ans; r^=ans; ans=query(l,r); cout<<ans<<endl; } return 0; }
CF666E Forensic Examination
CF1037H Security
12.31
闲话
- 侯操位置又因为量化进行了调整。德育主任说了下今天上午的安排,包括但不限于取消早预备进行补课,中途课件由 \(10 \min\) 调整为 \(5 \min\) ,于 \(10:40 \sim 11:30\) 在班级内部举行元旦联欢会。然后说本次放假返校禁止携带行李箱和爬楼车,只允许携带方便折叠的行李袋。反正我返校的时候爬楼车是一定要带的,不然集训再搬宿舍就只能徒手搬了,准备和年级老师硬碰硬,大不了放机房去。
- 好奇不让我们提前放假而是在班里举行元旦联欢会。
- 好奇不让我们提前放假而是在班里举行元旦联欢会。
- 吃完早饭到机房后 \(miaomiao\) 说高一的不用打模拟赛了,听年级的安排,到时间后就回班玩去,高二的自己选择打或不打模拟赛。
- 到 \(10:40\) 后因为不想回班联欢完再到机房拿手机,遂直接留在机房直到 \(11:30\) 下课,然后就放假了。
- 晚上 \(8:00\) 进行体育选课,通过一些手段抢到了羽毛球。
做题纪要
UVA10829 L-Gap Substrings
-
同 luogu P1117 [NOI2016] 优秀的拆分 ,考虑枚举
UVU
串跨过的两个关键点。 -
统计答案时将 \(lcp+lcs-len\) 加入贡献。
点击查看代码
int n; char s[50010]; struct SA { struct ST { int fminn[50010][20]; void init(int n,int a[]) { memset(fminn,0x3f,sizeof(fminn)); for(int i=1;i<=n;i++) { fminn[i][0]=a[i]; } for(int j=1;j<=log2(n);j++) { for(int i=1;i+(1<<j)-1<=n;i++) { fminn[i][j]=min(fminn[i][j-1],fminn[i+(1<<(j-1))][j-1]); } } } int query(int l,int r) { int t=log2(r-l+1); return min(fminn[l][t],fminn[r-(1<<t)+1][t]); } }T; int sa[50010],rk[100010],oldrk[100010],id[50010],cnt[50010],key[50010],height[50010]; int val(char x) { return (int)x; } void counting_sort(int n,int m) { memset(cnt,0,sizeof(cnt)); for(int i=1;i<=n;i++) { cnt[key[i]]++; } for(int i=1;i<=m;i++) { cnt[i]+=cnt[i-1]; } for(int i=n;i>=1;i--) { sa[cnt[key[i]]]=id[i]; cnt[key[i]]--; } } void init(char s[],int len) { memset(sa,0,sizeof(sa)); memset(rk,0,sizeof(rk)); int m=127,tot=0,num=0; for(int i=1;i<=len;i++) { rk[i]=val(s[i]); id[i]=i; key[i]=rk[id[i]]; } counting_sort(len,m); for(int w=1;tot!=len;w<<=1,m=tot) { num=0; for(int i=len;i>=len-w+1;i--) { num++; id[num]=i; } for(int i=1;i<=len;i++) { if(sa[i]>w) { num++; id[num]=sa[i]-w; } } for(int i=1;i<=len;i++) { key[i]=rk[id[i]]; } counting_sort(len,m); for(int i=1;i<=len;i++) { oldrk[i]=rk[i]; } tot=0; for(int i=1;i<=len;i++) { tot+=(oldrk[sa[i]]!=oldrk[sa[i-1]]||oldrk[sa[i]+w]!=oldrk[sa[i-1]+w]); rk[sa[i]]=tot; } } for(int i=1,j=0;i<=len;i++) { j-=(j>=1); while(s[i+j]==s[sa[rk[i]-1]+j]) { j++; } height[rk[i]]=j; } T.init(len,height); } int lcp(int x,int y) { if(rk[x]>rk[y]) { swap(x,y); } return (x==y)?n-x+1:T.query(rk[x]+1,rk[y]); } }F,B; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int t,g,ans,len,lcp,lcs,i,j,k; cin>>t; for(k=1;k<=t;k++) { ans=0; cin>>g>>(s+1); n=strlen(s+1); F.init(s,n); reverse(s+1,s+1+n); B.init(s,n); for(len=1;len<=n/2;len++) { for(i=len,j=i+len+g;j<=n;i+=len,j+=len) { lcp=min(len,F.lcp(i,j)); lcs=min(len,B.lcp(n-i+1,n-j+1)); ans+=max(lcp+lcs-len,0); } } cout<<"Case "<<k<<": "<<ans<<endl; } return 0; }
luogu P6328 我是仙人掌
-
\(BFS\) 预处理出全源最短路后设 \(f_{i,j}\) 表示从 \(i\) 出发经过不超过 \(j\) 条路径能到达的点集,使用
bitset
加速位运算即可。点击查看代码
int dis[1010],vis[1010]; vector<int>e[1010]; bitset<1010>f[1010][1010],tmp; void add(int u,int v) { e[u].push_back(v); } void bfs(int s,int n) { memset(vis,0,sizeof(vis)); memset(dis,0x3f,sizeof(dis)); queue<int>q; dis[s]=0; vis[s]=1; q.push(s); while(q.empty()==0) { int x=q.front(); q.pop(); for(int i=0;i<e[x].size();i++) { if(vis[e[x][i]]==0) { dis[e[x][i]]=dis[x]+1; vis[e[x][i]]=1; q.push(e[x][i]); } } } for(int i=1;i<=n;i++) { if(dis[i]!=0x3f3f3f3f) { f[s][dis[i]][i]=1; } } for(int i=1;i<=n;i++) { f[s][i]|=f[s][i-1]; } } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,u,v,k,i,j; scanf("%d%d%d",&n,&m,&q); for(i=1;i<=m;i++) { scanf("%d%d",&u,&v); add(u,v); add(v,u); } for(i=1;i<=n;i++) { bfs(i,n); } for(i=1;i<=q;i++) { scanf("%d",&k); tmp.reset(); for(j=1;j<=k;j++) { scanf("%d%d",&u,&v); tmp|=f[u][min(v,n)]; } printf("%d\n",tmp.count()); } return 0; }
luogu P5236 【模板】静态仙人掌
-
多倍经验: luogu P10932 Freda的传呼机
-
圆方树板子。
- 仙人掌图是指每条边在不超过一个简单环中的无向图,常使用圆方树将图转化成树然后类似基环树一样去处理。
- 点双连通分量是指不含割点的图,特别地,孤立点也是一个点双连通分量(一般需要特殊处理)。除割点外其他的点至多属于一个点双连通分量,每条边属于恰好一个点双连通分量。
- 在圆方树中,原图中的每个点对应一个 圆点 ,每个点双对应一个 方点 。每一个点双连通分量,它对应的方点向这个点双连通分量中的每个点连边从而形成菊花图。若干个菊花图通过原图中的割点与方点连接在一起。
- 狭义圆方树和广义圆方树的主要区别在于是否存在一条边连接两个圆点,即对原图上只有一条边连接的两个顶点的处理不同。
- 确定 \(u \to v\) 的边权时,若两点都是圆点则对应与原图中的边权,若 \(u\) 是方点则边权为 \(v\) 到 \(u\) 所在点双连通分量的最短路,否则权值为 \(0\) (不存在这种情况)。
-
建出圆方树后先求出 \(u,v\) 的 \(\operatorname{LCA}=rt\) 。若 \(rt\) 是圆点,这两点间的距离即为所求。否则需要找到 \(rt\) 的两个儿子 \(x,y\) 使 \(u,v\) 分别在其子树内,并加上 \(x \to y\) 的最短路长度。
-
在得知深度后倍增是容易找到 \(x,y\) 的,树剖做法详见 SP14943 DRTREE - Dynamically-Rooted Tree 。
-
在本题中不需要判孤立点。
点击查看代码
struct node { int nxt,to,w; }e[40010]; int head[10010],dfn[10010],low[10010],sum[20010],fa[20010],siz[20010],dep[20010],dis[20010],top[20010],son[20010],tot=0,v_dcc=0,cnt=0; pair<int,int>last[10010]; vector<pair<int,int> >g[20010]; 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 build(int u,int v,int w) { v_dcc++; for(int x=v;x!=last[u].first;x=last[x].first) { sum[x]=w; w+=last[x].second; } sum[v_dcc]=sum[u];//把整个环的边权和给方点 sum[u]=0; for(int x=v;x!=last[u].first;x=last[x].first) { g[v_dcc].push_back(make_pair(x,min(sum[x],sum[v_dcc]-sum[x]))); g[x].push_back(make_pair(v_dcc,min(sum[x],sum[v_dcc]-sum[x]))); } } void tarjan(int x,int fa) { tot++; dfn[x]=low[x]=tot; for(int i=head[x];i!=0;i=e[i].nxt) { if(dfn[e[i].to]==0) { last[e[i].to]=make_pair(x,e[i].w); tarjan(e[i].to,x); low[x]=min(low[x],low[e[i].to]); } else { if(e[i].to!=fa)//狭义圆方树中圆点间的连边需要这个特判 { low[x]=min(low[x],dfn[e[i].to]); } } if(dfn[x]<low[e[i].to])//一条边连接的两个圆点间正常连边 { g[x].push_back(make_pair(e[i].to,e[i].w)); g[e[i].to].push_back(make_pair(x,e[i].w)); } } for(int i=head[x];i!=0;i=e[i].nxt) { if(last[e[i].to].first!=x&&dfn[x]<dfn[e[i].to])//寻找非树边后建方点 { build(x,e[i].to,e[i].w); } } } void dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; for(int i=0;i<g[x].size();i++) { if(g[x][i].first!=father) { dis[g[x][i].first]=dis[x]+g[x][i].second; dfs1(g[x][i].first,x); siz[x]+=siz[g[x][i].first]; son[x]=(siz[g[x][i].first]>siz[son[x]])?g[x][i].first: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].first!=fa[x]&&g[x][i].first!=son[x]) { dfs2(g[x][i].first,g[x][i].first); } } } } 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 dirson(int u,int rt) { while(top[u]!=top[rt]) { if(fa[top[u]]==rt) { return top[u]; } u=fa[top[u]]; } return son[rt]; } int query(int u,int v,int n) { int rt=lca(u,v),x,y; if(rt<=n) { return dis[u]+dis[v]-2*dis[rt]; } else { x=dirson(u,rt); y=dirson(v,rt); if(sum[x]>sum[y]) { swap(x,y); } return dis[u]+dis[v]-dis[x]-dis[y]+min(sum[y]-sum[x],sum[rt]+sum[x]-sum[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,i; cin>>n>>m>>q; for(i=1;i<=m;i++) { cin>>u>>v>>w; add(u,v,w); add(v,u,w); } v_dcc=n; tarjan(1,1); dfs1(1,0); dfs2(1,1); for(i=1;i<=q;i++) { cin>>u>>v; cout<<query(u,v,n)<<endl; } return 0; }
luogu P4320 道路相遇
-
等价于圆方树上两点间圆点个数,即 \(\left\lfloor \dfrac{dis_{u,v}}{2} \right\rfloor+1\) 。
点击查看代码
struct node { int nxt,to; }e[2000010]; int head[500010],dfn[500010],low[500010],siz[1000010],son[1000010],fa[1000010],dep[1000010],top[1000010],tot=0,cnt=0,v_dcc=0; vector<int>g[1000010]; stack<int>s; 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(low[e[i].to]==dfn[x]) { 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 dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; 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 dis(int x,int y) { return dep[x]+dep[y]-2*dep[lca(x,y)]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,q,i; cin>>n>>m; for(i=1;i<=m;i++) { cin>>u>>v; add(u,v); add(v,u); } v_dcc=n; for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } dfs1(1,0); dfs2(1,1); cin>>q; for(i=1;i<=q;i++) { cin>>u>>v; cout<<dis(u,v)/2+1<<endl; } return 0; }
BZOJ3331 压力
- 详见 2.图论 AK BZOJ3331 压力 。
UVA1464 Traffic Real Time Query System
-
枚举四个端点取 \(\max\) ,注意询问时不算两个端点所以要 \(-2\) 。
点击查看代码
struct node { int nxt,to; }e[200010]; int head[10010],dfn[10010],low[10010],siz[20010],son[20010],fa[20010],dep[20010],top[20010],tot=0,cnt=0,v_dcc=0; vector<int>g[20010]; stack<int>s; pair<int,int>E[100010]; 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(low[e[i].to]==dfn[x]) { 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 dfs1(int x,int father) { siz[x]=1; fa[x]=father; dep[x]=dep[father]+1; 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 dis(int x,int y) { return dep[x]+dep[y]-2*dep[lca(x,y)]; } int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,u,v,q,i; while(cin>>n>>m) { if(n==0&&m==0) { break; } else { cnt=tot=0; memset(e,0,sizeof(e)); memset(head,0,sizeof(head)); memset(dfn,0,sizeof(dfn)); memset(son,0,sizeof(son)); memset(siz,0,sizeof(siz)); for(i=1;i<=2*n;i++) { g[i].clear(); } for(i=1;i<=m;i++) { cin>>u>>v; E[i]=make_pair(u,v); add(u,v); add(v,u); } v_dcc=n; for(i=1;i<=n;i++) { if(dfn[i]==0) { tarjan(i); } } for(i=1;i<=n;i++) { if(siz[i]==0) { dfs1(i,0); dfs2(i,i); } } cin>>q; for(i=1;i<=q;i++) { cin>>u>>v; cout<<max({dis(E[u].first,E[v].first)/2-1,dis(E[u].first,E[v].second)/2-1,dis(E[u].second,E[v].first)/2-1,dis(E[u].second,E[v].second)/2-1,})<<endl; } } } return 0; }
luogu P5227 [AHOI2013] 连通图
-
线段树分治。
点击查看代码
int ans[100010]; pair<int,int>e[200010]; vector<int>pos[200010]; struct quality { int x,fa,siz; }; struct DSU { int fa[100010],siz[100010]; void init(int n) { for(int i=1;i<=n;i++) { fa[i]=i; siz[i]=1; } } int find(int x) { return fa[x]==x?x:find(fa[x]); } void merge(int x,int y,stack<quality>&s) { x=find(x); y=find(y); if(x!=y) { s.push((quality){x,fa[x],siz[x]}); s.push((quality){y,fa[y],siz[y]}); if(siz[x]<siz[y]) { swap(x,y); } fa[y]=x; siz[x]+=siz[y]; } } void split(stack<quality>&s) { while(s.empty()==0) { fa[s.top().x]=s.top().fa; siz[s.top().x]=s.top().siz; s.pop(); } } }D; struct SMT { struct SegmentTree { vector<int>info; }tree[400010]; #define lson(rt) (rt<<1) #define rson(rt) (rt<<1|1) void update(int rt,int l,int r,int x,int y,int id) { if(x<=l&&r<=y) { tree[rt].info.push_back(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); } } void solve(int rt,int l,int r,int n) { stack<quality>s; for(int i=0;i<tree[rt].info.size();i++) { D.merge(e[tree[rt].info[i]].first,e[tree[rt].info[i]].second,s); } if(l==r) { ans[l]=(D.siz[D.find(1)]==n); } else { int mid=(l+r)/2; solve(lson(rt),l,mid,n); solve(rson(rt),mid+1,r,n); } D.split(s); } }T; int main() { // #define Isaac #ifdef Isaac freopen("in.in","r",stdin); freopen("out.out","w",stdout); #endif int n,m,q,k,x,i,j; cin>>n>>m; for(i=1;i<=m;i++) { cin>>e[i].first>>e[i].second; pos[i].push_back(0); } cin>>q; for(i=1;i<=q;i++) { cin>>k; for(j=1;j<=k;j++) { cin>>x; pos[x].push_back(i); } } for(i=1;i<=m;i++) { pos[i].push_back(q+1); for(j=1;j<pos[i].size();j++) { if(pos[i][j]-1>=pos[i][j-1]+1) { T.update(1,1,q,pos[i][j-1]+1,pos[i][j]-1,i); } } } D.init(n); T.solve(1,1,q,n); for(i=1;i<=q;i++) { cout<<(ans[i]==1?"Connected":"Disconnected")<<endl; } return 0; }
本文来自博客园,作者:hzoi_Shadow,原文链接:https://www.cnblogs.com/The-Shadow-Dragon/p/18620530,未经允许严禁转载。
版权声明:本作品采用 「署名-非商业性使用-相同方式共享 4.0 国际」许可协议(CC BY-NC-SA 4.0) 进行许可。