2017.10.24
今天主要进行了一些模板的复习。主要是树剖lca方面。感觉虽然是学过的东西,不写的话还是很容易忘的。
商务旅行
某首都城市的商人要经常到各城镇去做生意,他们按自己的路线去做,目的是为了更好的节约时间。
假设有N个城镇,首都编号为1,商人从首都出发,其他各城镇之间都有道路连接,任意两个城镇之间如果有直连道路,在他们之间行驶需要花费单位时间。该国公路网络发达,从首都出发能到达任意一个城镇,并且公路网络不会存在环。
你的任务是帮助该商人计算一下他的最短旅行时间。
输入文件中的第一行有一个整数N,1<=n<=30 000,为城镇的数目。下面N-1行,每行由两个整数a 和b (1<=a, b<=n; a<>b)组成,表示城镇a和城镇b有公路连接。在第N+1行为一个整数M,下面的M行,每行有该商人需要顺次经过的各城镇编号。
在输出文件中输出该商人旅行的最短时间。
5
1 2
1 5
3 5
4 5
4
1
3
2
5
7
挺裸的lca问题。多的不解释。以前用tarjan写了,今天树剖打了一遍。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; int n,u,v,son[100010],fa[100010],top[100010],d[100010],head[100010],vis[100010],siz[100010],cnt,m,a,b,ans; struct edge{ int v,next; }E[150010]; void add(int u,int v){ E[++cnt].v=v; E[cnt].next=head[u]; head[u]=cnt; } void dfs1(int x,int dep){ d[x]=dep; siz[x]=1; for(int i=head[x];i;i=E[i].next){ int v=E[i].v; if(v==fa[x])continue; fa[v]=x; dfs1(v,dep+1); siz[x]+=siz[v]; if(siz[v]>siz[son[x]])son[x]=v; } } void dfs2(int x,int tp){ top[x]=tp; if(son[x])dfs2(son[x],tp); for(int i=head[x];i;i=E[i].next){ int v=E[i].v; if(v==fa[x]||v==son[x])continue; else dfs2(v,v); } } int query(int a,int b){ while(top[a]!=top[b]){ if(d[top[a]]<d[top[b]])swap(a,b); a=fa[top[a]]; } return d[a]<d[b]?a:b; } int main(){ scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v);add(v,u); } dfs1(1,1); dfs2(1,1); scanf("%d",&m); m--; scanf("%d",&a); while(m--){ scanf("%d",&b); int tmp=query(a,b); ans+=d[a]+d[b]-2*d[tmp]; a=b; } printf("%d\n",ans); }
tarjan版:
#include<cstring> #include<algorithm> #include<cmath> #include<cstdio> #include <vector> using namespace std; int n,sum,x,fx,fy,m,l,r,y,a,b,ecnt,tot,head[150010],fa[150010],vis[150010],cnt; int s,t,lin[150010][2],d[150010],need,k[150100]; int ans; vector <int> ask[100100]; struct edge{ int a,b,next; }E[150010]; void add(int a,int b){ E[++ecnt].a=a; E[ecnt].b=b; E[ecnt].next=head[a]; head[a]=ecnt; } int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);} void Tarjan(int x,int dp){ d[x]=dp; for(int i=head[x];i;i=E[i].next){ if(!d[E[i].b]){ Tarjan(E[i].b,dp+1); fa[E[i].b]=x; } } int sum=ask[x].size(); for(int i=0;i<sum;i++){ int y=ask[x][i]; if(vis[y]){ int tt=find(y); ans+=d[x]+d[y]-d[tt]*2; } } vis[x]=1; return; } int main(){ scanf("%d",&n); for(int i=1;i<n;i++){ scanf("%d%d",&a,&b); add(a,b); add(b,a); } scanf("%d",&m); for(int i=1;i<=n;i++)fa[i]=i; for(int i=1;i<=m;i++){ scanf("%d",&y); ask[x].push_back(y),ask[y].push_back(x); x=y; } d[1]=0; Tarjan(1,1); printf("%d",ans); return 0; }
bzoj1787 紧急集合
Description
Input
Output
Sample Input
1 2
2 3
2 4
4 5
5 6
4 5 6
6 3 1
2 4 4
6 6 6
Sample Output
5 2
2 5
4 1
6 0
HINT
处理树后,枚举每两个节点求lca再让第三个点连接的情况一共三种。感觉写的有点麻烦了。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int d[500010],siz[500010],head[500010],fa[500010],son[500010],top[500010],cnt,n,m,u,v,a,b,c; struct edge{ int v,next; }E[1000010]; void add(int u,int v){ E[++cnt].v=v; E[cnt].next=head[u]; head[u]=cnt; } void dfs(int x,int dep){ siz[x]=1; d[x]=dep; for(int i=head[x];i;i=E[i].next){ int v=E[i].v; if(v==fa[x])continue; fa[v]=x; dfs(v,dep+1); siz[x]+=siz[v]; if(siz[v]>siz[son[x]])son[x]=v; } } void DFS(int x,int tp){ top[x]=tp; if(son[x])DFS(son[x],tp); for(int i=head[x];i;i=E[i].next){ int v=E[i].v; if(v==fa[x]||v==son[x])continue; DFS(v,v); } } int query(int x,int y){ while(top[x]!=top[y]){ if(d[top[x]]<d[top[y]])swap(x,y); x=fa[top[x]]; } return d[x]<d[y]?x:y; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<n;i++){ scanf("%d%d",&u,&v); add(u,v);add(v,u); } dfs(1,1); DFS(1,1); for(int i=1;i<=m;i++){ scanf("%d%d%d",&a,&b,&c); int l1,l2,l3,t1,t2,t3,l,ans1,ans2,ans3,ans; l1=query(a,b);l2=query(b,c);l3=query(a,c); t1=query(l1,c);t2=query(l2,a);t3=query(l3,b); ans1=d[a]+d[b]-d[l1]*2; ans1+=d[l1]+d[c]-d[t1]*2; ans2=d[b]+d[c]-d[l2]*2; ans2+=d[l2]+d[a]-d[t2]*2; ans3=d[a]+d[c]-d[l3]*2; ans3+=d[l3]+d[b]-d[t3]*2; ans=min(ans1,min(ans2,ans3)); if(ans==ans1)l=l1; else if(ans==ans2)l=l2; else l=l3; printf("%d %d\n",l,ans); } }
还有一个树剖练习:codevs4633[Mz]树链剖分练习
给定一棵结点数为n的树,初始点权均为0,有依次q个操作,每次操作有三个参数a,b,c,当a=1时,表示给b号结点到c号结点路径上的所有点(包括b,c,下同)权值都增加1,当a=2时,表示询问b号结点到c号结点路径上的所有点权值之和。
第一行,一个正整数n。
接下来n-1行,每行一对正整数x,y,表示x号结点和y号结点之间有一条边。
第n+1行,一个正整数q。
最后q行,每行一组正整数a,b,c,表示操作的三个参数。b和c可能相等。
保证数据都是合法的。
若干行,每行一个非负整数表示答案。
5
1 2
2 3
1 4
2 5
5
1 4 5
2 1 5
1 1 3
2 5 3
2 4 3
3
4
6
共有10个测试点,对于第i个测试点,当1<=i<=4时,n=q=10^i,当5<=i<=10时,n=q=10000*i。
#include<cstdio> #include<algorithm> #include<cstring> using namespace std; #define maxn 100010 #define lson o<<1,l,m #define rson o<<1|1,m+1,r int siz[maxn],tree[maxn],deep[maxn],head[maxn],fa[maxn],son[maxn]; int sum[maxn<<2],add[maxn<<2]; int n,ord,top[maxn],ecnt; struct edge{ int u,v,next; }E[maxn<<1]; void added(int u,int v) { E[++ecnt].u=u; E[ecnt].v=v; E[ecnt].next=head[u]; head[u]=ecnt; } void dfs(int x) { siz[x]=1; for(int i=head[x] ; i ; i=E[i].next ) { int v=E[i].v; if(fa[x]==v)continue; deep[v]=deep[x]+1; fa[v]=x; dfs(v); siz[x]+=siz[v]; if(siz[son[x]]<siz[v])son[x]=v; } } void dfs2(int x,int tp) { tree[x]=++ord;top[x]=tp; if(son[x])dfs2(son[x],tp); for(int i=head[x] ; i ; i=E[i].next ) { int v=E[i].v; if(fa[x]==v||son[x]==v)continue; dfs2(v,v); } } void pushup(int o){sum[o]=sum[o<<1]+sum[o<<1|1];} void pushdown(int o,int x) { if(add[o]) { add[o<<1]+=add[o];add[o<<1|1]+=add[o]; sum[o<<1]+=add[o]*(x-(x>>1));sum[o<<1|1]+=add[o]*(x>>1); add[o]=0; } } void update(int o,int l,int r,int ql,int qr) { if(ql<=l&&r<=qr) { sum[o]+=r-l+1; add[o]++; return ; } int m=(l+r)>>1; pushdown(o,r-l+1); if(ql<=m)update(lson,ql,qr); if(qr>m)update(rson,ql,qr); pushup(o); return ; } /* int lca(int x,int y) { while(top[x]!=top[y]) { if(deep[top[x]]<deep[top[y]])swap(x,y); x=fa[top[x]]; } return deep[x]<deep[y]?x:y; } */ void do_add(int x,int y) { int fx=top[x],fy=top[y]; while(fx!=fy) { if(deep[fx]<deep[fy]){swap(fx,fy);swap(x,y);} update(1,1,n,tree[fx],tree[x]); x=fa[fx];fx=top[x]; } if(deep[x]>deep[y])swap(x,y); update(1,1,n,tree[x],tree[y]); return ; } int query(int o,int l,int r,int ql,int qr) { if(ql<=l&&r<=qr)return sum[o]; pushdown(o,r-l+1); int m=(l+r)>>1; int ret(0); if(ql<=m)ret+=query(lson,ql,qr); if(qr>m)ret+=query(rson,ql,qr); return ret; } int que(int x,int y) { int fx=top[x],fy=top[y]; int ret(0); while(fx!=fy) { if(deep[fx]<deep[fy]){swap(fx,fy);swap(x,y);} ret+=query(1,1,n,tree[fx],tree[x]); x=fa[fx];fx=top[x]; } if(deep[x]>deep[y])swap(x,y); ret+=query(1,1,n,tree[x],tree[y]); return ret; } int main() { int a,b,c,q; scanf("%d",&n); for(int i=1 ; i<n ; ++i ) { scanf("%d%d",&a,&b); added(a,b);added(b,a); } dfs(1);dfs2(1,1); scanf("%d",&q); while(q--) { scanf("%d%d%d",&a,&b,&c); if(a==1)do_add(b,c); else printf("%d\n",que(b,c)); } return 0; }
也是个板子题。
以及一道匈牙利
codevs2776 寻找代表元
广州二中苏元实验学校一共有n个社团,分别用1到n编号。
广州二中苏元实验学校一共有m个人,分别用1到m编号。每个人可以参加一个或多个社团,也可以不参加任何社团。
每个社团都需要选一个代表。谦哥希望更多的人能够成为代表。
第一行输入两个数n和m。
以下n行每行若干个数,这些数都是不超过m的正整数。其中第i行的数表示社团i的全部成员。每行用一个0结束。
输出最多的能够成为代表的人数。
4 4
1 2 0
1 2 0
1 2 0
1 2 3 4 0
3
各个测试点1s
数据范围
n,m<=200
把人和社团分开编号在建边就行。
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; #define N 203 int n,m,x,cnt=0; int head[2*N],ex[2*N]; bool vis[2*N]; struct edge{ int v,next; }E[100010]; void add(int u,int v){ E[++cnt].v=v; E[cnt].next=head[u]; head[u]=cnt; } bool find(int x){ vis[x]=1; for(int i=head[x];i;i=E[i].next){ int v=E[i].v; if(!vis[v]){ vis[v]=1; if(!ex[v]||find(ex[v])){ ex[v]=x; ex[x]=v; return true; } } } return false; } int match(){ int ans=0; for(int i=1;i<=n;i++){ memset(vis,0,sizeof(vis)); if(find(i))ans++; } return ans; } int main(){ scanf("%d%d",&n,&m); for(int i=1;i<=n;i++){ while(1){ scanf("%d",&x); if(x){ add(i,x+200);//分开编号 add(x+200,i); } else break; } } printf("%d\n",match()); return 0; }
复习模板有希望啊~