2014 UESTC暑前集训数据结构专题解题报告

时间:2023-01-14 08:06:59

A.Islands

这种联通块的问题一看就知道是并查集的思想。

做法:从高水位到低水位依序进行操作,这样每次都有新的块浮出水面,可以在前面的基础上进行合并集合的操作。
给每个位置分配一个数字,方便合并集合。同时将这些数字也排一个序,降低枚举的复杂度。合并集合时向四周查询浮出水面但是没有合并到同一集合的点进行合并。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 1007 struct node
{
int x,y,h;
}p[N*N]; int a[N*N],sh[],cnt[],fa[N*N];
int n,m;
int dx[] = {,,,-};
int dy[] = {,-,,}; int cmp(node ka,node kb)
{
return ka.h < kb.h;
} int findset(int x)
{
if(x != fa[x])
fa[x] = findset(fa[x]);
return fa[x];
} inline int ok(int x,int y)
{
if(x < n && x >= && y < m && y >= )
return ;
return ;
} int main()
{
int t,i,j,k,q,ind,pos;
scanf("%d",&t);
while(t--)
{
memset(cnt,,sizeof(cnt));
scanf("%d%d",&n,&m);
for(i=;i<n;i++)
{
for(j=;j<m;j++)
{
pos = i*m+j;
fa[pos] = pos;
scanf("%d",&a[pos]);
p[pos].x = i,p[pos].y = j,p[pos].h = a[pos];
}
}
scanf("%d",&q);
for(i=;i<q;i++)
scanf("%d",&sh[i]);
sort(p,p+n*m,cmp);
i = n*m-;
for(j=q-;j>=;j--)
{
cnt[j] = cnt[j+];
while(sh[j] < p[i].h) //浮出水面
{
cnt[j]++;
ind = p[i].x*m+p[i].y;
int fx = findset(ind);
for(k=;k<;k++)
{
int tx = p[i].x + dx[k];
int ty = p[i].y + dy[k];
if(!ok(tx,ty))
continue;
int newind = tx*m+ty;
int fy = findset(newind);
if(fx != fy && sh[j] < a[newind]) //浮出水面且没有合并
{
fa[fy] = fx; //不能使fa[fx] = fy.因为本次fx不会再findset.
cnt[j]--;
}
}
i--;
}
}
for(i=;i<q;i++)
printf("%d ",cnt[i]);
printf("\n");
}
return ;
}

B.母仪天下

线段树单点更新,区间查询。模板题,不解释。不会做说明你线段树很水。。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#define Mod 1000000007
#define INF 1000000007
#define INT 2147483647
#define lll __int64
#define ll long long
using namespace std;
#define N 100007 struct node
{
ll sum;
}tree[*N]; void pushup(int rt)
{
tree[rt].sum = tree[*rt].sum + tree[*rt+].sum;
} void build(int l,int r,int rt)
{
if(l == r)
{
scanf("%lld",&tree[rt].sum);
return;
}
int mid = (l+r)/;
build(l,mid,*rt);
build(mid+,r,*rt+);
pushup(rt);
} void update(int l,int r,int pos,ll val,int rt)
{
if(l == r)
{
tree[rt].sum += val;
return;
}
int mid = (l+r)/;
if(pos <= mid)
update(l,mid,pos,val,*rt);
else
update(mid+,r,pos,val,*rt+);
pushup(rt);
} ll query(int l,int r,int aa,int bb,int rt)
{
if(aa <= l && bb >= r)
return tree[rt].sum;
int mid = (l+r)/;
ll res = ;
if(aa <= mid)
res += query(l,mid,aa,bb,*rt);
if(bb > mid)
res += query(mid+,r,aa,bb,*rt+);
return res;
} int main()
{
int m,n,aa,bb;
int i,pos;
ll val;
while(scanf("%d%d",&n,&m)!=EOF)
{
build(,n,);
while(m--)
{
scanf("%d",&i);
if(i == )
{
scanf("%d%d",&aa,&bb);
printf("%lld\n",query(,n,aa,bb,));
}
else
{
scanf("%d%lld",&pos,&val);
update(,n,pos,val,);
}
}
}
return ;
}

C.东风不与周郎便

线段树区间更新,区间查询。也是模板题,比B题较难,但是也是学线段树必须要会的。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#define Mod 1000000007
#define INF 1000000007
#define INT 2147483647
#define lll __int64
#define ll long long
using namespace std;
#define N 100007 struct node
{
ll sum;
ll mark;
}tree[*N]; void pushup(int rt)
{
tree[rt].sum = tree[*rt].sum + tree[*rt+].sum;
} void pushdown(int l,int r,int rt)
{
if(!tree[rt].mark)
return;
int mid = (l+r)/;
tree[*rt].sum += tree[rt].mark*(ll)(mid-l+);
tree[*rt+].sum += tree[rt].mark*(ll)(r-mid);
tree[*rt].mark += tree[rt].mark;
tree[*rt+].mark += tree[rt].mark;
tree[rt].mark = ;
} void build(int l,int r,int rt)
{
if(l == r)
{
scanf("%lld",&tree[rt].sum);
tree[rt].mark = ;
return;
}
int mid = (l+r)/;
build(l,mid,*rt);
build(mid+,r,*rt+);
pushup(rt);
} void update(int l,int r,int aa,int bb,ll val,int rt)
{
if(aa <= l && bb >= r)
{
tree[rt].sum += val*(ll)(r-l+);
tree[rt].mark += val;
return;
}
pushdown(l,r,rt);
int mid = (l+r)/;
if(aa <= mid)
update(l,mid,aa,bb,val,*rt);
if(bb > mid)
update(mid+,r,aa,bb,val,*rt+);
pushup(rt);
} ll query(int l,int r,int aa,int bb,int rt)
{
if(aa <= l && bb >= r)
return tree[rt].sum;
pushdown(l,r,rt);
int mid = (l+r)/;
ll res = ;
if(aa <= mid)
res += query(l,mid,aa,bb,*rt);
if(bb > mid)
res += query(mid+,r,aa,bb,*rt+);
return res;
} int main()
{
int m,n,aa,bb;
int i;
ll val;
while(scanf("%d%d",&n,&m)!=EOF)
{
memset(tree,,sizeof(tree));
build(,n,);
while(m--)
{
scanf("%d",&i);
if(i == )
{
scanf("%d%d",&aa,&bb);
printf("%lld\n",query(,n,aa,bb,));
}
else
{
scanf("%d%d%lld",&aa,&bb,&val);
update(,n,aa,bb,val,);
}
}
}
return ;
}

D.长使英雄泪满襟

这题是原题,参考POJ 2482 Stars In Your Window

E.休生伤杜景死惊开

即用线段树求逆序数。(或树状数组)
求出每一个数字前面比他小的数字个数以及后面比他大的个数,然后相乘求和即为总的以这个数字为中心的锁的个数。
这里用线段树来实现求逆序数,分别从前面和后面进行查找,查找1~a[i]-1的数的个数。以从前往后查为例,因为是即插即查,所以这时区间内数的个数就是在数组前面比他小的数的个数。从后往前查同理。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#define Mod 1000000007
#define INF 1000000007
#define INT 2147483647
#define lll __int64
#define ll long long
using namespace std;
#define N 50007 struct node
{
ll sum;
}tree[*N]; ll a[N],k1[N],k2[N]; void build(int l,int r,int rt)
{
tree[rt].sum = ;
if(l == r)
return;
int mid = (l+r)/;
build(l,mid,*rt);
build(mid+,r,*rt+);
} ll query(int l,int r,int aa,int bb,int rt)
{
if(aa > bb)
return ;
if(aa <= l && bb >= r)
return tree[rt].sum;
int mid = (l+r)/;
ll res = ;
if(aa <= mid)
res += query(l,mid,aa,bb,*rt);
if(bb > mid)
res += query(mid+,r,aa,bb,*rt+);
return res;
} void update(int l,int r,int pos,int rt)
{
tree[rt].sum++;
if(l == r)
return;
int mid = (l+r)/;
if(pos <= mid)
update(l,mid,pos,*rt);
else
update(mid+,r,pos,*rt+);
} int main()
{
int n,i,j;
while(scanf("%d",&n)!=EOF)
{
ll sum = ;
for(i=;i<=n;i++)
scanf("%lld",&a[i]);
build(,n,);
for(i=;i<=n;i++)
{
k1[i] = query(,n,,a[i]-,);
update(,n,a[i],);
}
build(,n,);
for(i=n;i>=;i--)
{
k2[i] = query(,n,,a[i]-,);
update(,n,a[i],);
}
for(i=;i<=n;i++)
sum += k1[i] * k2[i];
printf("%lld\n",sum);
}
return ;
}

F.天下归晋

树状数组可以解决,每条船的等级即统计每条船的左下方的船只数目。

将船只坐标由y从小到大排序,y坐标相同的根据x坐标从小到大排序。总之后面的点要保证在前面的点的右上方,这样插入时后面的船只不会影响到前面船只的等级,即前面船只的等级确定。

求和时sum(x)表示x坐标值小于等于x的船只个数
然后根据排序顺序将船只一个一个插入,求和,求出的和的那个等级的船只数加1,最后输出。

(注意树状数组c[]数组下标只能从1开始,所以所有坐标在处理时都加1)

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 202010 struct point
{
int x,y;
}a[N]; int n;
int c[N],ans[N]; int cmp(point ka,point kb)
{
if(ka.y == kb.y)
return ka.x < kb.x;
return ka.y < kb.y;
} int lowbit(int x)
{
return x&(-x);
} void modify(int num,int val)
{
while(num<=N)
{
c[num] += val;
num += lowbit(num);
}
} int sum(int x)
{
int res = ;
while(x>)
{
res += c[x];
x -= lowbit(x);
}
return res;
} int main()
{
int i,x,y;
while(scanf("%d",&n)!=EOF)
{
memset(c,,sizeof(c));
memset(ans,,sizeof(ans));
for(i=;i<n;i++)
scanf("%d%d",&a[i].x,&a[i].y);
sort(a,a+n,cmp);
for(i=;i<n;i++)
{
ans[sum(a[i].x+)]++;
modify(a[i].x+,);
}
for(i=;i<n;i++)
{
printf("%d\n",ans[i]);
}
}
return ;
}

G.程序设计竞赛

即线段树的区间最大连续和问题,我有一篇博文讲过了。

每个节点维护4个值:
max:此区间内的最大连续和
sum:该节点以下的节点值得总和
lmax:此区间的从左端开始的最大连续和
rmax:此区间的从右端开始的最大连续和
合并区间时,该区间的最大连续和为:max(左子节点的最大连续和,右子节点的最大连续和,左子节点的最大右连续和+右子节点的最大左连续和)

查询时返回一个整节点。因为每次要查询左子节点和右子节点,并且要比较它们的右连续最大和和左连续最大和,所以需要返回整个节点以便求值。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 200007 struct node
{
int maxi,lmaxi,rmaxi,sum;
}tree[*N]; void pushup(int rt)
{
tree[rt].sum = tree[*rt].sum + tree[*rt+].sum;
tree[rt].maxi = max(tree[*rt].maxi,max(tree[*rt+].maxi,tree[*rt].rmaxi+tree[*rt+].lmaxi));
tree[rt].lmaxi = max(tree[*rt].lmaxi,tree[*rt].sum + tree[*rt+].lmaxi);
tree[rt].rmaxi = max(tree[*rt+].rmaxi,tree[*rt+].sum + tree[*rt].rmaxi);
} void build(int l,int r,int rt)
{
if(l == r)
{
scanf("%d",&tree[rt].sum);
tree[rt].maxi = tree[rt].lmaxi = tree[rt].rmaxi = tree[rt].sum;
return;
}
int mid = (l+r)/;
build(l,mid,*rt);
build(mid+,r,*rt+);
pushup(rt);
} void update(int l,int r,int pos,int val,int rt)
{
if(l == r)
{
tree[rt].maxi = tree[rt].lmaxi = tree[rt].rmaxi = tree[rt].sum = val;
return;
}
int mid = (l+r)/;
if(pos <= mid)
update(l,mid,pos,val,*rt);
else
update(mid+,r,pos,val,*rt+);
pushup(rt);
} node query(int l,int r,int aa,int bb,int rt)
{
if(aa <= l && bb >= r)
return tree[rt];
int mid = (l+r)/;
node ka,kb,res;
int flag1 = ;
int flag2 = ;
if(aa <= mid)
{
ka = query(l,mid,aa,bb,*rt);
flag1 = ;
}
if(bb > mid)
{
kb = query(mid+,r,aa,bb,*rt+);
flag2 = ;
}
if(flag1 && flag2)
{
res.sum = ka.sum + kb.sum;
res.lmaxi = max(ka.lmaxi,ka.sum+kb.lmaxi);
res.rmaxi = max(kb.rmaxi,kb.sum+ka.rmaxi);
res.maxi = max(ka.rmaxi+kb.lmaxi,max(ka.maxi,kb.maxi));
}
else
{
if(flag1) //left
res = ka;
else
res = kb;
}
return res;
} int main()
{
int n,m,op,aa,bb;
scanf("%d%d",&n,&m);
build(,n,);
while(m--)
{
scanf("%d%d%d",&op,&aa,&bb);
if(!op)
{
node res = query(,n,aa,bb,);
printf("%d\n",res.maxi);
}
else
update(,n,aa,bb,);
}
return ;
}

H.Cookies Test

线段树解决。每次将数据插入到合适位置,注意这里的值比较大,所以需要哈希,哈希过后就能开线段树了。

遇到查询时就查询线段树中第中位数个点(值),返回其值,然后将该点置一个标记,表示没有了。同时更新其上的节点的sum值(子树的数(有值的叶子节点)的个数)。

代码不太容易懂。我也是头一次写这种嵌套很多层的数组的代码。
代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
#include <cstdlib>
#include <string>
#include <vector>
#include <map>
#define Mod 1000000007
#define INF 1000000007
#define INT 2147483647
#define lll __int64
#define ll long long
using namespace std;
#define N 600007 struct node
{
int sum;
}tree[*N]; struct A
{
int ind,val;
}a[N],b[N]; int hash[N],k[N]; int cmp(A ka,A kb)
{
return ka.val < kb.val;
} void build(int l,int r,int rt)
{
tree[rt].sum = ;
if(l == r)
return;
int mid = (l+r)/;
build(l,mid,*rt);
build(mid+,r,*rt+);
} int query(int l,int r,int pos,int rt)
{
tree[rt].sum--;
if(l == r)
return l;
int mid = (l+r)/;
if(tree[*rt].sum >= pos)
return query(l,mid,pos,*rt);
else
return query(mid+,r,pos-tree[*rt].sum,*rt+);
} void update(int l,int r,int pos,int rt)
{
tree[rt].sum++;
if(l == r)
return;
int mid = (l+r)/;
if(pos <= mid)
update(l,mid,pos,*rt);
else
update(mid+,r,pos,*rt+);
} int main()
{
int n,i,j,m;
char ss[];
i = ;
while(scanf("%s",ss)!=EOF)
{
if(ss[] == '#')
a[i].val = b[i].val = INF,a[i].ind = b[i].ind = i++;
else
a[i].val = b[i].val = atoi(ss),a[i].ind = b[i].ind = i++;
}
m = i-;
sort(b+,b+m+,cmp);
int now = ;
int pre = -;
for(i=;i<=m;i++)
{
if(b[i].val == INF)
continue;
if(b[i].val != pre)
{
pre = b[i].val;
b[i].val = now++;
}
else
b[i].val = now-;
hash[b[i].val] = b[i].ind;
}
for(i=;i<=m;i++)
k[b[i].ind] = b[i].val;
n = ;
build(,n,);
int cnt = ;
for(i=;i<=m;i++)
{
if(k[i] == INF)
{
if(cnt%) //odd
printf("%d\n",a[hash[query(,n,(cnt+)/,)]].val);
else
printf("%d\n",a[hash[query(,n,cnt/+,)]].val);
cnt--;
}
else
{
update(,n,k[i],);
cnt++;
}
}
return ;
}

I.方师傅学数字逻辑

J.方师傅的01串

字典树,结构node维护两个值: count 和 deep ,结果即为节点的count * deep 的最大值。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 50007 struct node
{
int count,deep;
node *next[];
}*root; char ss[N];
int maxi; node *create()
{
node *p;
p = (node *)malloc(sizeof(node));
p->count = ;
p->deep = ;
for(int i=;i<;i++)
p->next[i] = NULL;
return p;
} void release(node *p)
{
for(int i=;i<;i++)
{
if(p->next[i] != NULL)
release(p->next[i]);
}
free(p);
} void insert(char *ss)
{
node *p = root;
int i = ,k;
while(ss[i])
{
k = ss[i++] - '';
if(p->next[k] == NULL)
p->next[k] = create();
p->next[k]->deep = p->deep + ;
p = p->next[k];
p->count++;
maxi = max(maxi,p->count*p->deep);
}
} int main()
{
int t,n,i;
root = create();
scanf("%d",&n);
maxi = -;
for(i=;i<n;i++)
{
scanf("%s",ss);
insert(ss);
}
cout<<maxi<<endl;
release(root);
return ;
}

K.方师傅与栈

栈上的模拟题。
根据终态来定过程。
终态此时需要出现什么值,初始栈中就要一直找到该值,找的过程中将值都压入一个栈中,然后将该值出栈,如此循环往复,总之要依循终态的顺序。
出现以下两种情况则答案是NO:
1.终态的数还没模拟完,原始数组中的数就已经全部压入栈中了。并且栈顶还不等于终态此时需要的数
2.如果最后栈不为空,说明还有没有匹配的,自然不行。

代码:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <cmath>
#include <algorithm>
using namespace std;
#define N 1000007 int a[N],want[N],stk[N]; int main()
{
int n,i,k,top;
scanf("%d",&n);
for(i=;i<=n;i++)
scanf("%d",&a[i]);
for(i=;i<=n;i++)
scanf("%d",&want[i]);
k = ;
top = ;
stk[] = ;
int flag = ;
for(i=;i<=n;i++)
{
while(stk[top] != want[i])
{
if(k <= n)
stk[++top] = a[k++];
else
{
flag = ;
break;
}
}
if(stk[top] == want[i])
top--;
if(!flag)
break;
}
if(!flag || top)
puts("No");
else
puts("Yes");
return ;
}

L.冰雪奇缘

M.方师傅玩炉石

(没做出来的以后更新)