two
每个阶段删掉 (Red/Blue) 一定是交替的
显然如果一个边满足只有一个点在 (dfn) 对应的区间里面
那么把对面的树的边的 (dfn) 放到自己这边,每次删除的边放到自己这边搞区间删除
对边的两个端点的 (dfn) 搞一个双射,每次删除一定是区间里面的一个前缀或者后缀,所以线段树维护即可
为了简化代码量,使用了 (std::merge)
这个函数是归并,需要 (st_1,ed_1,st_1,ed_2,back\_inserter()) 五个参数
#include<bits/stdc++.h>
using namespace std;
#define int long long
#define For(i,a,b) for(register int i=a;i<=b;++i)
#define Down(i,a,b) for(register int i=a;i>=b;i--)
#define reg register
inline int min(int x,int y){return x<y?x:y;}
inline int max(int x,int y){return x>y?x:y;}
inline void swap(int &x,int &y){int t=x; x=y; y=t; return ;}
namespace yspm{
inline int read(){
int res=0,f=1; char k;
while(!isdigit(k=getchar())) if(k=='-') f=-1;
while(isdigit(k)) res=res*10+k-'0',k=getchar();
return res*f;
}
vector<int> ans,tans;
const int N=2e5+10;
struct Tree{
struct node{int nxt,to;}e[N];
int head[N],in[N],out[N],fa[N],tim,np[N<<2],ns[N<<2],cnt;
vector<pair<int,int> > pre[N<<2],suf[N<<2];
bool fl[N];
inline void add(int u,int v){
e[++cnt].to=v; e[cnt].nxt=head[u];
return head[u]=cnt,void();
}
inline void dfs(int x,int fat){in[x]=++tim; for(reg int i=head[x];i;i=e[i].nxt) dfs(e[i].to,x); out[x]=tim; return ;}
inline void build(int p,int l,int r){
if(l==r){
sort(pre[p].begin(),pre[p].end()); sort(suf[p].begin(),suf[p].end());
np[p]=(int)pre[p].size()-1; ns[p]=0; return ;
}int mid=(l+r)>>1; build(p<<1,l,mid); build(p<<1|1,mid+1,r);
merge(pre[p<<1].begin(),pre[p<<1].end(),pre[p<<1|1].begin(),pre[p<<1|1].end(),back_inserter(pre[p]));
merge(suf[p<<1].begin(),suf[p<<1].end(),suf[p<<1|1].begin(),suf[p<<1|1].end(),back_inserter(suf[p]));
np[p]=(int)pre[p].size()-1; ns[p]=0; return ;
}
inline void ins(int p,int st,int ed,int pos,int v1,int v2,bool fl){
if(st==ed) return (!fl)?pre[p].push_back(make_pair(v2,v1)):suf[p].push_back(make_pair(v2,v1)),void();
int mid=(st+ed)>>1; if(pos<=mid) ins(p<<1,st,mid,pos,v1,v2,fl); else ins(p<<1|1,mid+1,ed,pos,v1,v2,fl);
return ;
}
inline void find(int p,int l,int r,int st,int ed){
if(st<=l&&r<=ed){
while(np[p]>=0&&pre[p][np[p]].first>ed){
if(!fl[pre[p][np[p]].second]) fl[pre[p][np[p]].second]=1,tans.push_back(pre[p][np[p]].second);
--np[p];
}
while(ns[p]<suf[p].size()&&suf[p][ns[p]].first<st){
if(!fl[suf[p][ns[p]].second]) fl[suf[p][ns[p]].second]=1,tans.push_back(suf[p][ns[p]].second);
++ns[p];
} return ;
}int mid=(l+r)>>1; if(st<=mid) find(p<<1,l,mid,st,ed); if(ed>mid) find(p<<1|1,mid+1,r,st,ed); return ;
}
}T[2];
int a[N],b[N],n,fl;
signed main(){
freopen("1.in","r",stdin);
n=read(); for(reg int i=2;i<=n;++i) a[i]=read(),T[0].add(a[i],i); for(reg int i=2;i<=n;++i) b[i]=read(),T[1].add(b[i],i);
T[1].dfs(1,0); T[0].dfs(1,0);
for(reg int i=2;i<=n;++i){
T[0].ins(1,1,n,min(T[0].in[b[i]],T[0].in[i]),i,max(T[0].in[b[i]],T[0].in[i]),0);
T[0].ins(1,1,n,max(T[0].in[b[i]],T[0].in[i]),i,min(T[0].in[b[i]],T[0].in[i]),1);
T[1].ins(1,1,n,min(T[1].in[a[i]],T[1].in[i]),i,max(T[1].in[a[i]],T[1].in[i]),0);
T[1].ins(1,1,n,max(T[1].in[a[i]],T[1].in[i]),i,min(T[1].in[a[i]],T[1].in[i]),1);
} T[0].build(1,1,n); T[1].build(1,1,n); int x=read(); ans.push_back(++x); T[1].fl[x]=1;
while(ans.size()){
if(fl) puts("Red"); else puts("Blue");
for(auto p:ans) printf("%lld ",p-1),T[fl].find(1,1,n,T[fl].in[p],T[fl].out[p]); puts("");
ans=tans; tans.clear(); fl^=1; sort(ans.begin(),ans.end());// assert(ans.size()==2);
} return 0;
}
}
signed main(){return yspm::main();}
sum
因为互质,所以每个质因子只会被选一次,那么有以下结论:
((1)) 每个数最多有两个质因子
((2)) 如果有两个质因子,必然是一个大于 (sqrt n) 另一个小于之
感性理解是 (trivial) 的一下
这样的话对每个 (le sqrt n) 的质因子,从 (S) 连流量 (1),费用 (0)
每个 (>sqrt n) 的质因子,连到 (T),流量费用同上
质因子之间如果选择两个优于选择一个那么就连流量 (1),费用 (f(x,y)-f(x)-f(y))
跑最大费用可行流即可
这个就是 (spfa) 改成最长路,如果当前 (d[T]le 0) 就不增广了
bracket
写了仨半小时,点分没还原就爆零了
因为是树上的路径,那么考虑合并两个子树的路径信息
把括号转成 (±1),对于一个前缀,如果其最大值是路径最大/小值,那么最大/小值的出现次数是其 (f(x,y)) 的值
那么按照 ( exttt{SDOI2016模式字符串}) 的思想,维护到 (x) 和从 (x) 出发的值
自贡献可以在处理儿子的时候减掉,和一般的点分是一样的
发现 (f(x,y)=k,f(y,z)=l) 可以贡献给 (f(x,z)=k+l)
对于当前点分子树的信息卷积的时候分着每个和为 (0) 的数组卷即可
实现的时候注意 (dfs) 的时候不能直接修改信息,因为每次统计是一个链,回溯时候要统计的是子树的,需要记一下 (pre)
Dp记录
求
枚举最后的选择的集合大小 (sz)
设 (f[i][j][k]) 为考虑了(i) 个数,选择了 (j) 个,所有的和 (mod sz) 为 (k) 的方案
(Theta(n^4)) 能过的