2016 UESTC Training for Data Structures
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2016 UESTC Training for Data Structures相关的知识,希望对你有一定的参考价值。
2016 UESTC Training for Data Structures
A - 卿学姐与公主
题意:
有两个操作一个是单点造成攻击,第二个是求区间最大值。
题解:
0.裸的线段树操作,不过zkw线段树显然更好。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 1000010; int A,n,q,s,t,x,p,M = 1,o; int T[N<<2]; void update(int p,int x){ for(T[p+=M] += x,p >>= 1;p;p >>= 1)T[p] = max(T[p<<1],T[p<<1|1]); } int query(int s,int t){ int ans = -1; for(s = s-1+M,t = t+1+M;s^t^1;s >>= 1,t >>= 1){ if(~s&1)ans = max(ans,T[s^1]); if( t&1)ans = max(ans,T[t^1]); } return ans; } int main(){ scanf("%d%d",&n,&q); while(M<=n+1)M <<= 1; while(q--){ scanf("%d",&o); if(o == 1)scanf("%d%d",&p,&x),update(p,x); if(o == 2)scanf("%d%d",&s,&t),printf("%d\n",query(s,t)); } return 0; }
B - 卿学姐与基本法
题意:
有n(<=100000000)个人,第一个操作就是卿学姐给他们普及基本法,第二个操作就是询问一个区间里还有多少人没有被普及基本法。
题解:
0.数据太大需要离散,离散之后就是裸的线段树啊。
1.离散后需要注意有Bug,离散之后有些区间是空的比如[1,2] [5,9]离散之后[3,4]这一段就会被忽略,添加右端点,然后令线段树的叶子表示左闭右开的区间。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 100100; struct node{ int add,l,r,sum; }tree[N<<4]; #define lson k<<1,l,mid #define rson k<<1|1,mid+1,r int x[N<<2], n,m,cnt; struct ques{int l,r,o;}q[N]; void maintain(int k){tree[k].sum = tree[k<<1].sum + tree[k<<1|1].sum;} void pushdown(int k){ if(!tree[k].add)return; tree[k<<1].sum = 0,tree[k<<1|1].sum = 0; tree[k].add = 0,tree[k<<1].add = 1,tree[k<<1|1].add = 1; } int query(int k,int l,int r,int ql,int qr){ if(ql <= l && qr >= r)return tree[k].sum; pushdown(k); int mid = l+r>>1; if(qr <= mid)return query(lson,ql,qr); else if(ql > mid)return query(rson,ql,qr); else return query(lson,ql,mid)+query(rson,mid+1,qr); } void update(int k,int l,int r,int ql,int qr){ if(ql <= l && qr >= r){ tree[k].add = 1; tree[k].sum = 0; return; } pushdown(k); int mid = (l+r)>>1; if(qr <= mid)update(lson,ql,qr); else if(ql > mid)update(rson,ql,qr); else update(lson,ql,mid),update(rson,mid+1,qr); maintain(k); } void build(int k,int l,int r){ if(l == r){ tree[k].sum = x[l+1]-x[l]; return; } int mid = l+r>>1; build(lson),build(rson); maintain(k); } int main(){ scanf("%d%d",&n,&m); for(int i = 1;i <= m;++i){ scanf("%d%d%d",&q[i].o,&q[i].l,&q[i].r); x[++cnt] = q[i].l,x[++cnt] = q[i].r,x[++cnt] = q[i].r+1; } sort(x+1,x+cnt+1); cnt = unique(x+1,x+1+cnt)-x-1; x[++cnt] = n+1; build(1,1,cnt); for(int i = 1;i <= m;++i){ int l = lower_bound(x+1,x+cnt+1,q[i].l)-x; int r = lower_bound(x+1,x+cnt+1,q[i].r)-x; if(q[i].o == 1)update(1,1,cnt,l,r); else printf("%d\n",query(1,1,cnt,l,r)); } return 0; }
C - 卿学姐与诡异村庄
题意:
一个村庄有n(<=100000)个人,分为好人和坏人,他们有各自的言论,说某人是好人还是坏人,好人说真话,坏人说假话。
题解:
0.这是一道裸的类别并查集,如果有多个种类那么就开多倍的数组,来表示不同种类。
1.令fa[1~n]表示好人,fa[n+1~2*n]表示坏人,A对B言论是有两种情况的,A是好人/坏人,如果能够满足所有的言论,最后得到的一定是两个集合并且每个人的角色相反。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 100010; int n,fa[N<<1],y,c,fx,fy,flag = 1,X,Y; int find(int x){ return x == fa[x] ? x : find(fa[x]); } int main(){ scanf("%d",&n); for(int i = 1;i <= 2*n;++i)fa[i] = i; for(int x = 1;x <= n;++x){ scanf("%d%d",&y,&c); X = x+n,Y = y+n; if(c == 1){ fx = find(x),fy = find(y),fa[fx] = fy; fx = find(X),fy = find(Y),fa[fx] = fy; } if(c == 2){ fx = find(X),fy = find(y),fa[fx] = fy; fx = find(x),fy = find(Y),fa[fx] = fy; } } for(int i = 1;i <= n;++i)if(find(i) == find(i+n))flag = 0; flag ? puts("Time to show my power") : puts("One face meng bi"); return 0; }
D - 卿学姐与魔法
题意:
给出长度为n(<=100000)的两个数组一个为A,一个为B,从A和B中各选出一个数有n*n种方法然后组成新数,求n个最小的新数。
题解:
0.因为要求最小的新数,新数是由A,B两数组中各一个数组成的,那么不妨采用贪心的策略,每次都选最小的两个数。
1.最暴力的方法是先把B数组排序,与A每一个元素匹配,始终记录n个最小值,比较求得最小值在进行替换(O(n2logn))
2.
①其实很容易发现比较新数的时候会和A中的每一个元素匹配感觉可以优化,既然是贪心那么就可以使用优先队列维护新数。
②首先还是对B进行排序,把B的第一个(贪心)数字和A中的每一元素加起来压入队列中,每次只需要取top(),但是取出来之后需要压入新的数啊,所以还需要把top()的关于B数组组成部分的位置记录下来,因为B数组是排序之后为递增的,贪心的策略那么下一个新数就可以推出来为top()-B[p]+B[p+1]。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <queue> #include <algorithm> using namespace std; const int N = 1000010; struct node{ int w,k; bool operator < (const node& rhs)const{return w > rhs.w;} }; priority_queue<node>q; int a[N],b[N],n,ans[N]; int main(){ scanf("%d",&n); for(int i = 1;i <= n;++i)scanf("%d",&a[i]); for(int i = 1;i <= n;++i)scanf("%d",&b[i]); sort(b+1,b+n+1); for(int i = 1;i <= n;++i)q.push(node{a[i]+b[1],1}); for(int i = 1;i <= n;++i){ node p = q.top();q.pop(); ans[i] = p.w; if(p.k < n)q.push(node{p.w-b[p.k]+b[p.k+1],p.k+1}); } sort(ans+1,ans+1+n); for(int i = 1;i <= n;++i)printf("%d\n",ans[i]); return 0; }
E - 卿学姐与城堡的墙
题意:
给出n(<=200000)条直线(和解析式相关的k,b|y = kx+b),然后给出x = u,x = v的两条竖直的直线,问这在n条直线选择两条直线的交点的横坐标在[u,v]范围里的方案数。
0≤|k|,|b|,|u|,|v|≤1000000000
题解:
0.数据太大需要离散。
1.首先不难发现每一条直线都会与x = u,x = v两条直线相交,可以处理出每条直线与x=u,x=v的交点,并且两个交点是相连的。
2.很显然如果两条直线要有交点,那么这两条直线与x=u,x=v的交点的纵坐标必定不同步。
也就是说令l1,l2与x=u x=v的交点分别是(u,y1),(u,y2),(v,y3),(v,y4)如果y1>y2那么必定y3<y4,如果y1<y2那么必定y3>y4
3.所以按照与x=u的交点的纵坐标从小到大排序,那么就是一个求逆序对的问题啊!这里的逆序还包括了两两相等的情况。
4.因为存在交点位于在x=u这一条直线上,为了不重不漏,所以在此基础上按照与x=v的交点的纵坐标从大到小排序。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 200010; #define LL long long #define lowbit(i) i&-i LL k,b,ans; LL n,u,v,f[N<<2],x[N<<2],cnt; struct node{ LL ux,vx; bool operator < (const node &rhs)const{ if(ux == rhs.ux)return vx > rhs.vx; return ux < rhs.ux; } }c[N]; LL query(LL x){ LL sum = 0; for(int i = x;i >= 1;i -= lowbit(i)){ sum += f[i]; } return sum; } void update(LL x,LL val){ for(int i = x;i <= N;i += lowbit(i)){ f[i] += val; } } int main(){ cin>>n>>u>>v; for(int i = 1;i <= n;++i){ cin>>k>>b; c[i].ux = k*u+b,c[i].vx = k*v+b; x[++cnt] = c[i].vx; } sort(x+1,x+cnt+1); cnt = unique(x+1,x+cnt+1)-x-1; for(int i = 1;i <= n;++i){ c[i].vx = lower_bound(x+1,x+cnt+1,c[i].vx)-x; } sort(c+1,c+n+1); for(int i = 1;i <= n;++i){ ans += query(cnt)-query(c[i].vx-1); update(c[i].vx,1); } cout<<ans<<endl; return 0; }
F - 郭大侠与“有何贵干?”
题意:
给出n(<=100000)个长方体的左下角右上角的坐标(x1,y1,z1),(x2,y2,z2)(1<=x,y<=1000000000,1<=z<=3),求这些长方体刚好覆盖k次的体积。
题解:
0.数据太大需要离散。
1.知道求恰好覆盖k次的面积需要扫描线套线段树,但是求恰好覆盖k次的体积?难道要用二维线段树套扫描线?可以发现z的数据范围是在[1,3]所以就可以把立体图形拆成两个平面图形也就是求两次覆盖k次的面积。
2.不知道怎么求覆盖面积的去问度娘吧~
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1000010; struct Tree{ int l,r,cover,sum[20]; }tree[N<<3]; #define clr(a,b) memset(a,b,sizeof(a)) int hase[N],cnt,LUcnt,LPcnt,n,K,pre; struct Line{ int lx,rx,y,flag; bool operator < (const Line& rhs)const{return y < rhs.y;} void push(int lx,int rx,int y,int flag){ this->lx = lx,this->rx = rx,this->y = y,this->flag = flag; } }lineu[N<<2],linep[N<<2]; void build(int k,int l,int r){ tree[k].sum[0] = hase[r]-hase[l]; for(int i = 1;i <= 15;++i)tree[k].sum[i] = 0; tree[k].l = l,tree[k].r = r,tree[k].cover = 0; if(l+1 == r)return; int mid = (l+r)>>1; build(k<<1,l,mid); build(k<<1|1,mid,r); } void processup(int k){ int l = tree[k].l,r = tree[k].r; if(l+1 == r){ clr(tree[k].sum,0); tree[k].sum[min(K+1,tree[k].cover)] = hase[r]-hase[l]; return; } clr(tree[k].sum,0); for(int i = 0;i <= K+1;++i) tree[k].sum[min(K+1,i+tree[k].cover)] = tree[k<<1].sum[i]+tree[k<<1|1].sum[i]; } void update(int k,int l,int r,int flag){ if(l == r)return; if(tree[k].l == l && tree[k].r == r){ tree[k].cover += flag; } else{ int mid = (tree[k].l+tree[k].r)>>1; if(r <= mid)update(k<<1,l,r,flag); else if(l >= mid)update(k<<1|1,l,r,flag); else update(k<<1,l,mid,flag),update(k<<1|1,mid,r,flag); } processup(k); } int main(){ scanf("%d%d",&n,&K); for(int i = 1;i <= n;++i){ int x1,x2,y1,y2,z1,z2; scanf("%d%d%d%d%d%d",&x1,&y1,&z1,&x2,&y2,&z2); hase[++cnt] = x1,hase[++cnt] = x2; if(z1 == 1 && z2 == 2){ lineu[++LUcnt].push(x1,x2,y1,1); lineu[++LUcnt].push(x1,x2,y2,-1); } if(z1 == 1 && z2 == 3){ lineu[++LUcnt].push(x1,x2,y1,1); lineu[++LUcnt].push(x1,x2,y2,-1); linep[++LPcnt].push(x1,x2,y1,1); linep[++LPcnt].push(x1,x2,y2,-1); } if(z1 == 2 && z2 == 3){ linep[++LPcnt].push(x1,x2,y1,1); linep[++LPcnt].push(x1,x2,y2,-1); } } sort(hase+1,hase+1+cnt); cnt = unique(hase+1,hase+1+cnt)-hase-1; sort(linep+1,linep+1+LPcnt),sort(lineu+1,lineu+1+LUcnt); for(int i = 1;i <= LUcnt;++i){ lineu[i].lx = lower_bound(hase+1,hase+cnt+1,lineu[i].lx)-hase; lineu[i].rx = lower_bound(hase+1,hase+cnt+1,lineu[i].rx)-hase; } for(int i = 1;i <= LPcnt;++i){ linep[i].lx = lower_bound(hase+1,hase+cnt+1,linep[i].lx)-hase; linep[i].rx = lower_bound(hase+1,hase+cnt+1,linep[i].rx)-hase; } long long res = 0; build(1,0,cnt); pre = lineu[1].y; for(int i = 1;i <= LUcnt;++i){ res += 1LL*(lineu[i].y-pre)*tree[1].sum[K]; update(1,lineu[i].lx,lineu[i].rx,lineu[i].flag),pre = lineu[i].y; } build(1,0,cnt); pre = linep[1].y; for(int i = 1;i <= LPcnt;++i){ res += 1LL*(linep[i].y-pre)*tree[1].sum[K]; update(1,linep[i].lx,linep[i].rx,linep[i].flag),pre = linep[i].y; } cout<<res<<endl; return 0; }
G - 郭大侠与阴阳家
题意:
给出平面上n(<=2000)个点的横纵坐标(<=1000000000),求这些点组成不同平行四边形的个数。
题解:
0.数据太大需要离散,这n个点中可能会出现重复的点,所以点需要去重。
1.对于平行四边形的判定条件是对边平行且相等,那么只需要预处理出n*n条直线的长度和斜率即可,找到两条直线符合即可,答案最后需要/2,因为平行四边形有四条边,当找到两条直线就知道会产生一个平行四边形,但是对于平行四边形另外两条边也会重复添加一个。
2.但是也有可能出现多条直线共线的情况,那么就判断是否与坐标轴交点相同,如果相同那么这种情况就不可取。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> #include <cstdlib> #include <cmath> using namespace std; const int N = 2010; const int inf = 1000010000; int n,flag[N],cnt,ans; const double eps = 1e-9; struct node{ int x,y; bool operator < (const node &rhs)const{ if(x == rhs.x)return y<rhs.y; return x < rhs.x; } bool operator ==(const node &rhs)const{return x == rhs.x && y == rhs.y;} }p[N*N]; struct lineregister{ double k,b,l; bool operator < (const lineregister &rhs)const{ if(abs(k-rhs.k) < eps)return l < rhs.l; return k < rhs.k; } }line[N*N]; int same(double x,double y){ if(abs(x-y) < eps)return 1;return 0; } int main(){ scanf("%d",&n); for(int i = 1;i <= n;++i){ scanf("%d%d",&p[i].x,&p[i].y); } sort(p+1,p+n+1); n = unique(p+1,p+n+1)-p-1; for(int i = 1;i < n;++i){ for(int j = i+1;j <= n;++j){ ++cnt; int dy = p[j].y-p[i].y,dx = p[j].x-p[i].x; if(dx == 0){ line[cnt].k = inf; line[cnt].b = p[i].x; } if(dx != 0){ line[cnt].k = dy*1.0/dx; line[cnt].b = p[i].y*1.0-line[cnt].k*p[i].x; } line[cnt].l = dx*dx+dy*dy; } } sort(line+1,line+cnt+1); for(int i = 1;i < cnt;++i){ for(int j = i+1;j <= cnt;++j){ if(!same(line[i].k,line[j].k) || !same(line[i].l,line[j].l))break; if(!same(line[i].b,line[j].b))++ans; } } cout<<ans/2<<endl; return 0; }
H - 郭大侠与英雄学院
题意:
给出n*m(n*m<=1000000)的矩阵,构造出一个新的矩阵使得每一行每一列的数字的与原来矩阵中每一行每一列的数字的相对大小不变,求这个最小权值的矩阵。
题解:
0.最朴实的想法就是把每个点的权值行列三个值记录下来,然后按照权值排序,这个值就为max(这一行的最大值maxx[i],这一列的最大值maxy[j])+1,然后更新maxx,maxy
1.但是同行同列会出现相同的数字怎么办?那么就把相同数字的都提取出来一起处理,把同行同列的数字看做一个集合,那么这个集合的最大值就为max(这个集合的集合行的最大值,
这个集合的集合列的最大值)+1,然后更新这个集合行集合列的最大值。这个集合怎么维护呢?我去!并查集啊~
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <algorithm> using namespace std; const int N = 1000010; struct node{ int x,y,u; bool operator < (const node& rhs)const{return u < rhs.u;} }c[N]; int maxx[N],maxy[N],px[N],py[N],n,m,fa[N],ans[N]; int find(int x){ return fa[x] == x ? x : fa[x] = find(fa[x]); } void Uni(int x,int y){ int fx = find(x),fy = find(y);if(fx != fy)fa[fx] = fy; } int main(){ scanf("%d%d",&n,&m); for(int i = 0;i < n*m;++i){ fa[i] = i; scanf("%d",&c[i].u); c[i].x = i/m,c[i].y = i%m; } sort(c,c+m*n); int pre = -1; for(int i = 0;i < m*n;++i){ if(i != n*m-1 && c[i].u == c[i+1].u)continue; for(int k = pre+1;k <= i;++k){ int ps = c[k].x*m+c[k].y; px[c[k].x] = ps,py[c[k].y] = ps; } for(int k = pre+1;k <= i;++k){ int ps = c[k].x*m+c[k].y; Uni(px[c[k].x],ps); Uni(py[c[k].y],ps); } for(int k = pre+1;k <= i;++k){ int ps = c[k].x*m+c[k].y; int pa = find(ps); ans[pa] = max(ans[pa],max(maxx[c[k].x],maxy[c[k].y])+1); } for(int k = pre+1;k <= i;++k){ int ps = c[k].x*m+c[k].y; maxx[c[k].x] = max(maxx[c[k].x],ans[find(ps)]); maxy[c[k].y] = max(maxy[c[k].y],ans[find(ps)]); } pre = i; } for(int i = 0;i < m*n;++i){ printf("%d ",ans[find(i)]); if(i%m == m-1)puts(""); } return 0; }
I - 郭大侠与线上游戏
题意:
有n个操作(1<=n<=1000000),就是维护一个队列,分为三种操作1.向这个队列push1个数 2.pop掉这个队列的一个数 3.求这个队列的中位数(中位数的定义为该队列升序排序后第k/2+1个的数的值,k为这个队列的大小)
题解:
0.哦凑!这个题的方法太多了!
1.第一个就是用treep嘛,思维简单。
2.第二个把数给离散了过后,可以用一个树状数组,维护比这个值小的数的个数,最后二分中位数的位置即可。
3.更为简单的就是用set,把数压入set就会自动排序,全程为维护中位数的指针。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> #include <queue> #include <set> using namespace std; set <int> s; set<int>::iterator it,p; queue <int> q; int n,o,x; int main(){ scanf("%d",&n); for(int i = 1;i <= n;++i){ scanf("%d",&o); if(o == 1){ scanf("%d",&x); q.push(x); s.insert(x); if(s.size() == 1){it = s.begin();continue;} if(x < *it && (s.size()-1)%2 == 0)it--; if(x > *it && (s.size()-1)%2 == 1)it++; } else if(o == 2){ x = q.front();q.pop(); p = s.find(x); if(*p > *it && s.size()%2 == 0)it--; if(*p < *it && s.size()%2 == 1)it++; if(*p== *it && s.size()%2 == 0)it--; if(*p== *it && s.size()%2 == 1)it++; s.erase(x); } else if(o == 3){ if(s.size() == 1)it = s.begin(); printf("%d\n",*it); } } return 0; }
J - 郭大侠与Rabi-Ribi
题意:
有n(<=100000)只兔子,每只兔子带有v[i]的权值并且在1~t[i]的时间里在地面,郭××要击晕兔子带回家,求最大的权值,击晕一只兔子需要1的时间。
题解:
0.暴力的方法先按照时间从小到大排序后,对于在同一时间消失的兔子取权值最大值一直累加上去.
1.优先队列的方法显然更优,先按照时间从小到大排序,维护全局时间,如果遇到某一只兔子权值更大并且之前没有选他,那么就把之前权值最小的那只兔子T出去,再加入这一只兔子,优先队列维护的就是权值小的兔子,然后更新答案。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <queue> #include <algorithm> using namespace std; const int N = 100010; int n; int ans,T; struct node{ int t,w; bool operator < (const node &rhs)const{return w > rhs.w;} }x[N]; bool cmp(node a,node b){return a.t<b.t;} priority_queue<node> q; int main(){ scanf("%d",&n); for(int i = 1;i <= n;++i)scanf("%d",&x[i].t); for(int i = 1;i <= n;++i)scanf("%d",&x[i].w); sort(x+1,x+n+1,cmp); q.push(x[1]);T++,ans = x[1].w; for(int i = 2;i <= n;++i){ if(x[i].t <= T && x[i].w>q.top().w){ node p = q.top();q.pop();q.push(x[i]); ans = ans-p.w+x[i].w; } if(x[i].t > T){ q.push(x[i]); ans += x[i].w,++T; } } printf("%d\n",ans); return 0; }
K - 郭大侠与甲铁城
题意:
给出一个长度为n(<=100000)的序列,q个询问关于[l,r]这个区间里不同数字的个数。
题解:
0.因为只有询问操作,所以考虑离线的数据结构。
1.这不就是裸的线段树么?首先按照每个询问的r从小到大排序,如果对于一个位置的数字之前没有出现过,那么就在[1,i]这个区间+1,如果这个位置上的数字之前出现过的话,令这个数字之前出现过的位置为pre,那么就使[1,pre]这个区间-1,[1,i]这个区间+1,遇到询问就记录答案即为[l,r]这个区间的值。
2.仔细想了想这些操作树桩数组不就可以完成嘛?好吧就是离线树状数组了~
代码:
#include <cstdio> #include <iostream> #include <algorithm> using namespace std; const int N = 1000010; #define lowbit(i) i&-i struct qcregister{ int l,r,ID; bool operator < (const qcregister& rhs)const{return r<rhs.r;} }qc[N]; int n,q,a[N],pre[N],ans[N],c[N]; int query(int x){ int sum = 0; for(int i = x;i >= 1;i -= lowbit(i))sum += c[i]; return sum; } void update(int x,int val){ for(int i = x;i <= N;i += lowbit(i))c[i] += val; } int main(){ scanf("%d%d",&n,&q); for(int i = 1;i <= n;++i)scanf("%d",&a[i]); for(int i = 1;i <= q;++i){ scanf("%d%d",&qc[i].l,&qc[i].r),qc[i].ID = i; } sort(qc+1,qc+1+q); int ID = 1; for(int i = 1;i <= n;++i){ if(!pre[a[i]])update(i,1); else update(pre[a[i]],-1),update(i,1); pre[a[i]] = i; while(ID <= q && i == qc[ID].r){ ans[qc[ID].ID] = query(qc[ID].r)-query(qc[ID].l-1); ++ID; } } for(int i = 1;i <= q;++i)printf("%d\n",ans[i]); return 0; }
L - 郭大侠与苦恼
题意:
给出一棵树,令p[u,v]为u点直接到v点的这一条路径上的所有点点的编号的异或值,如果p[u,v] = 0,那么u,v称为一对好朋友,求树上好朋友的对数(不重复计数(u,v),(v,u)算1)。
题解:
0.假设u->v的路径为u->a1->a2->v,那么p[u,v]=u^a1^a2^v.
为了方便和节省从固定的点开始出发p[1,u] = 1^c1^c2^u,p[1,v] = 1^c1^c2^a1^a2^v,发现了p[u,v] = p[1,u]^p[1,v]那么就从1开始dfs.
1.定义p[u][x]表示多次到达u点异或值为x时出现的次数,如果p[v]中存在xv^u使得p[u]中也含有这个值,那么用乘法原理求好朋友数
很容易证明如果xu = 1^c1^c2^u,xv = 1^c1^c2^u^v 如果u^v == 0,xv^u = 1^c1^c2^u = xu;
2.底层更新完了的时候,就把底层的p向上传递,也就是把p[v]合并到p[u],也就实现了v与上面的所有点的好朋友判断,因为p需要用到map,所以称为map的启发式合并。
代码:
#include <cstdio> #include <iostream> #include <cstring> #include <vector> #include <map> #include <algorithm> using namespace std; const int N = 100010; int n; long long ans; map <int,int> p[N]; vector <int> edge[N]; void Union(int u,int v){ if(p[u].size()<p[v].size())swap(p[u],p[v]); map<int,int>::iterator it; while(p[v].size()){ it = p[v].begin(); p[u][it->first] += it->second; p[v].erase(it); } } void dfs(int u,int fa,int x){ p[u][x]++; for(int i = 0;i < edge[u].size();++i){ int v = edge[u][i]; if(v == fa)continue; dfs(v,u,x^v); map<int,int>::iterator it; for(it = p[v].begin();it != p[v].end();++it){ int val = it->first^u; if(p[u].count(val))ans += p[u][val]*it->second; } Union(u,v); } } int main(){ scanf("%d",&n); for(int i = 1;i < n;++i){ int u,v; scanf("%d%d",&u,&v); edge[u].push_back(v); edge[v].push_back(u); } dfs(1,-1,1); cout<<ans<<endl; return 0; }
M - 卿学姐失恋了Ⅱ
题意:
有3个柱子20个盘子从大到小的编号为为1~20,给出这20个盘子一开始所在的柱子的编号,问能否在规定的时间里让这20个盘子在同一根柱子上?
题解:
0.优先考虑大盘子,对于最大的盘子可以不动,从大到小如果盘子已经在目标出就不用移动。
1.但是只要有一个盘子x目前不在目标处,那么它就应该移到目标处,它现在身处s[x],要移动到target,先用一步把x移动到目标柱子上,然后把比他小的盘子都从6-s[x]-target那根柱子上移动到target,根据老版本汉洛塔的规律用的时间为2x-1-1,所以总共用的时间2x-1。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; int s[25],m; int hanoi(int x,int pos){ while(s[x] == pos && x)--x; if(!x)return 0; return hanoi(x-1,6-s[x]-pos)+(1<<x-1); } int main(){ scanf("%d",&m); for(int i = 20;i >= 1;--i)scanf("%d",&s[i]); hanoi(19,s[20]) <= m ? puts("YES") : puts("NO"); return 0; }
N - 秋实大哥搞算数
题意:
给出一个算式求出最终结果(只包含‘+‘ ‘-‘ ‘*‘ ‘/‘)。
题解:
0.第一次输入的时候应该先处理‘*‘和‘/‘因为优先级别最高,当符号为‘*‘或‘/‘就需要处理当前输入的数和前一个被输的数,因为要查找后输入的数据,那么栈就满足后入栈先出栈的性质。所以就用栈来维护就好了。
1.最后处理‘+‘和‘-‘的时候,就要把栈的东西全部倒过来,用新栈就好了,O(∩_∩)O~
代码:
#include <cstdio> #include <stack> #include <cstring> #include <iostream> #include <algorithm> using namespace std; #define ll long long int main(){ int T;ll x,y;char c; scanf("%d",&T); while(T--){ stack <ll> num0,num1; stack<char>str0,str1; scanf("%lld",&x),num0.push(x); c = getchar(); while(c==‘+‘ || c==‘-‘ || c==‘*‘ || c==‘/‘ ){ scanf("%lld",&x); if(c == ‘*‘)y = num0.top(),num0.pop(),num0.push(x*y); if(c == ‘/‘)y = num0.top(),num0.pop(),num0.push(y/x); if(c == ‘+‘ || c == ‘-‘)str0.push(c),num0.push(x); c = getchar(); } while(!num0.empty())num1.push(num0.top()),num0.pop(); while(!str0.empty())str1.push(str0.top()),str0.pop(); while(!str1.empty()){ c = str1.top(),str1.pop(); x = num1.top(),num1.pop(); y = num1.top(),num1.pop(); if(c == ‘+‘)num1.push(x+y); if(c == ‘-‘)num1.push(x-y); } cout<<num1.top()<<endl; } return 0; }
O - 卿学姐种美丽的花
题意:
卿学姐有n(<=1000000)个花坛,一开始每个花坛都有Ai朵花,卿学姐有两种操作,1.在第i个花坛种下y朵花,i+1个花坛种下y-1,i+2个花坛种下y-2朵花,直到到达边界或者种花为0 2.询问某个花坛中有多少朵花?
题解:
0.题目给出的修改是一个等差数列,套路就来了,线段树啊!就维护差分,这样的话种花的操作就是一个区间修改和一个单点修改,最后询问花的朵数,就是一个前缀和。
1.仔细一想这些操作树状数组不就可以搞定了么?
代码:
P - 浑身难受
题意:
给出abcd,给出一个长度为n(<=2000)的数组p,并且p的值不重复并且都小于n,要求在p中从到右选择4个数满足abcd的大小关系。
题解:
0.abcd一共有24种情况,但是像1234和(4321并且把p数组倒过来)是等价的,所以只有12种情况。
1.当1和4没有挨一起的时候,那么就暴力枚举2和3的位置,用两个树桩数组维护个数。
2.当1和4挨在一起的时候,那么就暴力枚举2,4的位置,用两个树桩数组维护个数。
3.但是2413和2314两种情况好像就没法讨论,就可以用容斥啊,S2413 = Sx4x3-S1423 | S2314 = Sx3x4-S1324
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <algorithm> using namespace std; const int N = 2010; int n,A[5],x,p[N]; #define ll long long #define lowbit(i) i&-i struct BITregister{ ll c[N]; void clear(){memset(c,0,sizeof(c));} void update(int x,int v){ for(int i = x;i <= N;i += lowbit(i))c[i] += v; } ll query(int x){ ll ret = 0; for(int i = x;i >= 1;i -= lowbit(i))ret += c[i]; return ret; } }bit[2]; void reverse(int *p){ for(int i = 1;i*2 <= n;++i)swap(p[i],p[n+1-i]); } ll S1234(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 2 pk = 3 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = n-1;k >= i+1;--k){ bit[1].update(p[k+1],1); if(p[i] < p[k])ret += bit[0].query(p[i])*(n-k-bit[1].query(p[k])); } } return ret; } ll S1324(){ ll ret = 0; for(int i = 2;i <= n-2;++i){// pi = 3 pk = 2 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = n-1;k >= i+1;--k){ bit[1].update(p[k+1],1); if(p[i] > p[k])ret += bit[0].query(p[k])*(n-k-bit[1].query(p[i])); } } return ret; } ll S1243(){ ll ret = 0; for(int i = 2;i <= n-2;++i){// pi = 2 pk = 3 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] < p[k])ret += bit[0].query(p[i])*(k-i-1-bit[1].query(p[k])); } } return ret; } ll S1423(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 4 pk = 2 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = n-1;k >= i+1;--k){ bit[1].update(p[k+1],1); if(p[i] > p[k])ret += bit[0].query(p[k])*(bit[1].query(p[i])-bit[1].query(p[k])); } } return ret; } ll S1432(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 4 pk = 2 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] > p[k])ret += bit[0].query(p[k])*(bit[1].query(p[i])-bit[1].query(p[k])); } } return ret; } ll S1342(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 3 pk = 2 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] > p[k])ret += bit[0].query(p[k])*(k-i-1-bit[1].query(p[i])); } } return ret; } ll S2143(){ ll ret = 0; for(int i = n-1;i >= 3;--i){// pi = 4 pk = 2 bit[0].update(p[i+1],1); bit[1].clear(); for(int k = i-2;k >= 1;--k){ bit[1].update(p[k+1],1); if(p[i] > p[k])ret += (bit[0].query(p[i])-bit[0].query(p[k]))*bit[1].query(p[k]); } } return ret; } ll S2413(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 4 pk = 3 x4x3-1423 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] > p[k])ret += bit[0].query(p[k])*bit[1].query(p[k]); } } bit[0].clear(); return ret-S1423(); } ll S2314(){ ll ret = 0; for(int i = 2;i <= n-2;++i){//pi = 3 pk = 4 x3x4-1324 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] < p[k])ret += bit[0].query(p[i])*bit[1].query(p[i]); } } bit[0].clear(); return ret-S1324(); } ll S2134(){ ll ret = 0; for(int i = n-1;i >= 3;--i){//pi = 3 pk = 2 bit[0].update(p[i+1],1); bit[1].clear(); for(int k = i-2;k >= 1;--k){ bit[1].update(p[k+1],1); if(p[i] > p[k])ret += (n-i-bit[0].query(p[i]))*bit[1].query(p[k]); } } return ret; } ll S3124(){ ll ret = 0; for(int i = n-1;i >= 3;--i){// pi = 2 pk = 3 bit[0].update(p[i+1],1); bit[1].clear(); for(int k = i-2;k >= 1;--k){ bit[1].update(p[k+1],1); if(p[i] < p[k])ret += (n-i-bit[0].query(p[k]))*(bit[1].query(p[i])); } } return ret; } ll S3214(){ ll ret = 0; for(int i = 2;i <= n-2;++i){// pi = 2 pk = 4 bit[0].update(p[i-1],1); bit[1].clear(); for(int k = i+2;k <= n;++k){ bit[1].update(p[k-1],1); if(p[i] < p[k])ret += (bit[0].query(p[k])-bit[0].query(p[i]))*bit[1].query(p[i]); } } return ret; } int main(){ scanf("%d",&n); for(int i = 1;i <= 4;++i){ scanf("%d",&A[i]),x = x*10+A[i]; } for(int i = 1;i <= n;++i)scanf("%d",&p[i]); if(n<4){puts("0");return 0;} if(x == 4321)x = 1234,reverse(p); if(x == 4231)x = 1324,reverse(p); if(x == 4312)x = 2134,reverse(p); if(x == 4132)x = 2314,reverse(p); if(x == 4123)x = 3214,reverse(p); if(x == 4213)x = 3124,reverse(p); if(x == 3142)x = 2413,reverse(p); if(x == 3412)x = 2143,reverse(p); if(x == 3241)x = 1423,reverse(p); if(x == 3421)x = 1243,reverse(p); if(x == 2431)x = 1342,reverse(p); if(x == 2341)x = 1432,reverse(p); if(x == 1234)printf("%lld\n",S1234()); if(x == 1324)printf("%lld\n",S1324()); if(x == 1243)printf("%lld\n",S1243()); if(x == 1423)printf("%lld\n",S1423()); if(x == 1432)printf("%lld\n",S1432()); if(x == 1342)printf("%lld\n",S1342()); if(x == 2143)printf("%lld\n",S2143()); if(x == 2413)printf("%lld\n",S2413()); if(x == 2314)printf("%lld\n",S2314()); if(x == 2134)printf("%lld\n",S2134()); if(x == 3124)printf("%lld\n",S3124()); if(x == 3214)printf("%lld\n",S3214()); return 0; }
Q - 昊昊爱运动 II
题意:
给出一个长度为n(<=100000)的数组,有两个操作,1.把[l,r]这个区间里面的值都改为x, 2.[l,r]这个区间里面有多少个不同的数? 数的范围是100
题解:
0.这个就是线段树啊!而且每个数最多只有100!那么线段树每个区间都维护不同的个数啊!状压啊!感觉状压数就大了啊!bitset啊!
代码:
#include <cstdio> #include <cstring> #include <bitset> #include <iostream> #include <algorithm> using namespace std; const int N = 100010; char c[2]; int x,n,m,q,s,t; struct node{ int lz; bitset<110>state; }st[N<<2]; #define lson k<<1,l,mid #define rson k<<1|1,mid+1,r #define mid (l+r>>1) void maintain(int k){st[k].state = st[k<<1].state|st[k<<1|1].state;} void pushdown(int k){ int &lz = st[k].lz; st[k<<1].lz = st[k<<1|1].lz = lz; st[k<<1].state.reset(),st[k<<1|1].state.reset(); st[k<<1].state[lz] = 1,st[k<<1|1].state[lz] = 1; lz = 0; } void build(int k,int l,int r){ if(l == r){ scanf("%d",&x); st[k].state.reset(); st[k].state[x] = 1; return; } build(lson),build(rson); maintain(k); } void update(int k,int l,int r,int ql,int qr,int x){ if(l == ql && qr == r){ st[k].lz = x; st[k].state.reset(),st[k].state[x] = 1; return; } if(st[k].lz)pushdown(k); if(qr <= mid)update(lson,ql,qr,x); else if(ql > mid)update(rson,ql,qr,x); else update(lson,ql,mid,x),update(rson,mid+1,qr,x); maintain(k); } bitset<110> query(int k,int l,int r,int ql,int qr){ if(l == ql && r == qr)return st[k].state; if(st[k].lz)pushdown(k); if(qr <= mid)return query(lson,ql,qr); else if(ql > mid)return query(rson,ql,qr); else return query(lson,ql,mid)|query(rson,mid+1,qr); maintain(k); } int main(){ scanf("%d%d",&n,&m); build(1,1,n); scanf("%d",&q); while(q--){ scanf("%s",c); scanf("%d%d",&s,&t); if(c[0] == ‘Q‘)printf("%d\n",query(1,1,n,s,t).count()); if(c[0] == ‘M‘)scanf("%d",&x),update(1,1,n,s,t,x); } return 0; }
R - Japan
题意:
西边有n个城市,东边有m个城市,都是竖直排列,其中有一些城市是有路的,求路之间的交点有多少个?
题解:
0.西边高的城市和东边矮的城市的路 和 西边矮的城市和东边高的城市的路之间才有交点。
1.只需要离散化过后按照西边城市的由低到高进行排序,就是求一个逆序对而已,如果一个城市连了多个城市,那么就要一起处理不能挨着处理。
代码:
#include <cstdio> #include <cstring> #include <iostream> #include <vector> #include <algorithm> using namespace std; #define ll long long const int N = 100010; int T,n,m,k,a,b,kase; ll c[N]; vector <int> x[N]; #define lowbit(i) i&-i #define clr(a,b) memset(a,b,sizeof a) ll query(int x){ ll ret = 0; for(int i = x;i >= 1;i -= lowbit(i))ret += c[i]; return ret; } void update(int x){ for(int i = x;i <= N;i += lowbit(i))c[i] += 1; } int main(){ scanf("%d",&T); while(T--){ clr(c,0); for(int i = 0;i < N;++i)x[i].clear(); scanf("%d%d%d",&n,&m,&k); for(int i = 1;i <= k;++i){ scanf("%d%d",&a,&b),x[b].push_back(a); } ll ret = 0; for(int i = 1;i <= m;++i){ int size = x[i].size(); for(int k = 0;k < size;++k)ret += query(n)-query(x[i][k]); for(int k = 0;k < size;++k)update(x[i][k]); } printf("Test case %d: ",++kase); cout<<ret<<endl; } return 0; }
以上是关于2016 UESTC Training for Data Structures的主要内容,如果未能解决你的问题,请参考以下文章
UESTC 2016 Summer Training #1 J - Objects Panel (A) 按条件遍历树
2017 UESTC Training for Search Algorithm & String
2017 UESTC Training for Data Structures