【BZOJ3362-3365】USACO水题四连A
【BZOJ3362】[Usaco2004 Feb]Navigation Nightmare 导航噩梦
Description
Input
Output
Sample Input
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6 1
1 4 3
2 6 6
Sample Output
-1
10
HINT
在时刻1,约翰知道1到6的距离为13;在时刻3,1到4的距离仍然不知道;在时刻6,位置6向北3个距离,向西7个距离于位置2,所以距离为10.
题解:BZOJ题意实在捉鸡,本人强行自编题目描述
题中由于给了所有点间有且只有一条路径,所以就是棵树,即m=n-1
然后看着像个树形DP,但是不能离线,但我们发现点和点之间的关系具有可传递性(即可加性),所以很好地符合了加权并查集的性质。那么我们只需要先按时间排序,然后用双标记的加权并查集来维护就好了
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int n,m; int f[40010],x[40010],dx[40010],y[40010],dy[40010],rx[40010],ry[40010],ans[40010]; char str[5]; struct QUERY { int qa,qb,org,tim; }q[40010]; bool cmp(QUERY a,QUERY b) { return a.tim<b.tim; } int find(int a) { if(f[a]==a) return a; int t=f[a]; f[a]=find(f[a]),dx[a]+=dx[t],dy[a]+=dy[t]; return f[a]; } int z(int a) { return a>0?a:-a; } int main() { scanf("%d%d",&n,&m); int i,j,a,b; for(i=1;i<=m;i++) { scanf("%d%d%d%s",&x[i],&y[i],&a,str); if(str[0]=='E') rx[i]=a; if(str[0]=='W') rx[i]=-a; if(str[0]=='N') ry[i]=a; if(str[0]=='S') ry[i]=-a; } scanf("%d",&m); for(i=1;i<=n;i++) f[i]=i; for(i=1;i<=m;i++) scanf("%d%d%d",&q[i].qa,&q[i].qb,&q[i].tim),q[i].org=i; sort(q+1,q+m+1,cmp); for(i=j=1;i<=m;i++) { for(;j<=q[i].tim;j++) { a=find(x[j]),b=find(y[j]); f[a]=b,dx[a]=rx[j]+dx[y[j]]-dx[x[j]],dy[a]=ry[j]+dy[y[j]]-dy[x[j]]; } if(find(q[i].qa)!=find(q[i].qb)) ans[q[i].org]=-1; else ans[q[i].org]=z(dx[q[i].qa]-dx[q[i].qb])+z(dy[q[i].qa]-dy[q[i].qb]); } for(i=1;i<=m;i++) printf("%d\n",ans[i]); return 0; }
【BZOJ3363】[Usaco2004 Feb]Cow Marathon 奶牛马拉松
Description
Input
Output
Sample Input
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
Sample Output
最长的马拉松路线从2通过4,1,6,3到5;总长为:20+3+12+9+7=52
题解:求树的直径:先以1为根DFS找出深度最大的点,然后再以这个点为根找出深度最大的,就是直径
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int maxn=40010; int n,m,cnt,maxx; int to[maxn<<1],next[maxn<<1],head[maxn],val[maxn<<1],s[maxn]; char str[5]; void add(int a,int b,int c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } void dfs(int x,int fa) { maxx=(s[maxx]<s[x])?x:maxx; for(int i=head[x];i!=-1;i=next[i]) if(to[i]!=fa) s[to[i]]=s[x]+val[i],dfs(to[i],x); } int main() { scanf("%d%d",&n,&m); int i,j,a,b,c; memset(head,-1,sizeof(head)); for(i=1;i<=m;i++) scanf("%d%d%d%s",&a,&b,&c,str),add(a,b,c),add(b,a,c); dfs(1,0),s[maxx]=0,dfs(maxx,0); printf("%d",s[maxx]); return 0; }
【BZOJ3364】[Usaco2004 Feb]Distance Queries 距离咨询
Description
Input
Output
Sample Input
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
3
1 6
1 4
2 6
Sample Output
3
36
农场2到农场6有20+3+13=36的距离
题解:倍增LCA
#include <cstdio> #include <cstring> #include <iostream> using namespace std; const int maxn=40010; int n,m,cnt,ans; int to[maxn<<1],next[maxn<<1],head[maxn],val[maxn<<1],f[maxn][25],s[maxn],dep[maxn]; char str[5]; void add(int a,int b,int c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } void dfs(int x) { for(int i=head[x];i!=-1;i=next[i]) if(to[i]!=f[x][0]) f[to[i]][0]=x,dep[to[i]]=dep[x]+1,s[to[i]]=s[x]+val[i],dfs(to[i]); } int main() { scanf("%d%d",&n,&m); int i,j,a,b,c; memset(head,-1,sizeof(head)); for(i=1;i<=m;i++) scanf("%d%d%d%s",&a,&b,&c,str),add(a,b,c),add(b,a,c); dfs(1); for(j=1;(1<<j)+1<=n;j++) for(i=1;i<=n;i++) f[i][j]=f[f[i][j-1]][j-1]; scanf("%d",&m); for(i=1;i<=m;i++) { scanf("%d%d",&a,&b); ans=s[a]+s[b]; if(dep[a]<dep[b]) swap(a,b); for(j=20;j>=0;j--) if(dep[a]-(1<<j)>=dep[b]) a=f[a][j]; if(a==b) { printf("%d\n",ans-2*s[a]); continue; } for(j=20;j>=0;j--) if(f[a][j]!=f[b][j]) a=f[a][j],b=f[b][j]; ans-=2*s[f[a][0]]; printf("%d\n",ans); } return 0; }
【BZOJ3365】[Usaco2004 Feb]Distance Statistics 路程统计
Description
Input
Output
Sample Input
1 6 13 E
6 3 9 E
3 5 7 S
4 1 3 N
2 4 20 W
4 7 2 S
10
Sample Output
有五对道路之间的距离小于10
1-4,距离为3
4-7,距离为2
1-7,距离为5
3-5,距离为7
3-6,距离为9
题意:同POJ1741,同BZ1468
题解:点分治,第一次写感觉自己代码常数∞∞∞
谈谈点分治吧~
我们对以x节点为根的子树进行分治,只考虑经过x的路径,然后递归计算不经过x的路径,所以现在问题是怎么求x的子树中有多少条长度≤K的路径
我们先求出x子树中所有点到x的深度dep[i],那么我们要的就是dep[i]+dep[j]≤K的(i,j)对数,然后呢?
我们采用双指针法来实现这个过程。我们先将dep排序,用两个指针l,r从区间两边向中间平移,始终保证dep[l]+dep[r]≤K,那么(l,r]中的点到l的路径长度就都≤K,直接更新答案,单次计算时间复杂度O(nlogn)
但问题又出现了,这样计算可能导致(i,j)的路径不经过x也可能被算进去,于是我们依次算出x的儿子的子树中(i,j)的对数(此时dep[i]还是i到x的深度),然后用ans减去这些重复的对数就好了
这样,我们层层递归下去,如果给你的树比较平滑,那么总的时间复杂度应该不会太高;但假如给你的树是一条链,那么是时间复杂度直接O(n*nlogn),连暴力都不如
于是我们不能以1为根来搞,而是要以树的重心为根(树的重心指:删去这个点后,使所有连通块的点个数的最大值最小),并且每次递归都要重新找出子树中的重心向下搜(我原来只找了整棵树的重心。。。),这样每个点至多出现在log个点的子树中,才能保证时间复杂度为O(nlogn*logn)
为什么改了那么多遍感觉常数还是std的214748647倍~~~
#include <cstdio> #include <cstring> #include <algorithm> #include <iostream> using namespace std; const int maxn=40010; char str[5]; int n,k,cnt,root,maxx,ans,tot; int to[maxn<<1],next[maxn<<1],head[maxn],val[maxn<<1],dep[maxn<<1],p[maxn<<1],siz[maxn],vis[maxn]; void getroot(int x,int fa) { int i,tmp=0; siz[x]=1; for(i=head[x];i!=-1;i=next[i]) { if(to[i]==fa||vis[to[i]]) continue; getroot(to[i],x); siz[x]+=siz[to[i]]; tmp=max(tmp,siz[to[i]]); } if(max(tot-siz[x],tmp)<maxx) maxx=max(tot-siz[x],tmp),root=x; } void getdep(int x,int fa) { p[++p[0]]=dep[x]; for(int i=head[x];i!=-1;i=next[i]) { if(to[i]==fa||vis[to[i]]) continue; dep[to[i]]=dep[x]+val[i],getdep(to[i],x); } } int solve(int x) { p[0]=0,getdep(x,0); int ret=0,l=1,r=p[0]; sort(p+1,p+p[0]+1); while(l<r) { while(l<r&&p[l]+p[r]>k) r--; ret+=r-l,l++; } return ret; } void dfs(int x) { int i; vis[x]=1,dep[x]=0,ans+=solve(x); for(i=head[x];i!=-1;i=next[i]) { if(vis[to[i]]) continue; dep[to[i]]=val[i],ans-=solve(to[i]); maxx=1<<30,tot=siz[to[i]],getroot(to[i],x); dfs(root); } } void add(int a,int b,int c) { to[cnt]=b,val[cnt]=c,next[cnt]=head[a],head[a]=cnt++; } int main() { int i,a,b,c; scanf("%d%d",&n,&a); memset(head,-1,sizeof(head)); tot=n,root=1,maxx=1<<30; for(i=1;i<n;i++) { scanf("%d%d%d%s",&a,&b,&c,str); add(a,b,c),add(b,a,c); } scanf("%d",&k); getroot(root,0); dfs(root); printf("%d\n",ans); return 0; }
| 欢迎来原网站坐坐! >原文链接<