北航ICPC集训队第一次春训(2019.4.27)

 拖了这么久更新有些尴尬。第一次春训是个人场的数据结构主题。总的来说我数据结构掌握得还不是很扎实,很多人轻松打掉的E,我愣愣地看了半天。以后要加强训练。
 另外还是那句老话,做题要专注,思路要无比清晰!(不只是做题,做其它事情也是一样的)

在这里插入图片描述
 水题,简单来说就是栈的应用,一个栈来记录括号序列以及已经求出来的value,碰到一个左括号就放进去,碰到一个右括号就不断弹栈到左括号,把对应左括号中间的value求和的两倍再放进去,如果无value就把1放进去。

#include<cstdio>
#define mo 12345678910
using namespace std;
using LL=long long;

int top,n;
LL val[200005],ans;
char S[200005];

int main()
{
	scanf("%d",&n);
	for(int i=1,d;i<=n;i++)
	{
		scanf("%d",&d);
		if(d==0)
			S[top++]='(';
		else
		{
			LL res=0;
			if(S[top-1]=='(')
			{
				S[--top]=0;
				val[top++]=1;
			}
			else
			{
				while(S[top-1]!='(')
				{
					res=(res+val[top-1])%mo;
					S[--top]=0;
				}
				S[--top]=0;
				val[top++]=res*2%mo;
			}
		}
	}
	for(int i=0;i<top;i++)
		ans=(ans+val[i])%mo;
	printf("%lld",ans);
	return 0;
}

在这里插入图片描述
 这一题中,一个要点就是要处理相同的数字。另外注意它是一行一列中相同的数字依旧相同,并不是指所有矩阵中(一开始没注意到这一点)。我们先看如果数字都不重复的话可以怎么做。我们用数组mx和my记录每行每列出现的最大的新标号,从小到大枚举数字,对应行和列的mx和my的最大值加1就是新的标号。现在有相同数字的时候,同一个数字会涉及多个行和列,比较棘手。这里我们用并查集,把互相涉及(新标号必须相同)的同一个数字放进同一个集合。然后我们统一对一个集合的行和列取一个最值加一,再把这个值统一赋给集合中每个元素。
 我将所有数字进行两次排序,第一次是双关键字,以行优先,第二次则是以列优先。这样同行同列可以相邻,方便我们合并并查集。接着用并查集中每个元素更新整个集合的标号极值即可。
 子渊学长提了另一种做法,似乎更优雅一些。首先也是处理出并查集——把每行每列分别排序,相同的加入一个并查集。同时对每个数,从这个数向比同行同列最小的它大的那两个数连边(当然,边是属于并查集的),然后对这些集合开始搞拓扑排序。(精彩的图论思路!),这里感觉代码挺简单就不实现了。

#include<cstdio>
#include<algorithm>
using namespace std;

struct item
{
	int x,px,py;
	inline bool operator < (const item &t) const
	{
		return x<t.x||x==t.x&&px<t.px; 
	}
}a[1000005];

struct item2
{
	int x,px,py,pos;
	inline bool operator < (const item2 &t) const
	{
		return x<t.x||x==t.x&&py<t.py; 
	}
}b[1000005];

int n,m,an,bn,x,mx[1000005],my[1000005];
int ans[1000005],fa[1000005];

inline int fis(int x)
{
	return x==fa[x]?x:fa[x]=fis(fa[x]);
}

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			scanf("%d",&x);
			a[++an]=(item){x,i,j};
			fa[an]=an;
		}
	sort(a+1,a+an+1);
	for(int i=1;i<=an;i++)
		b[++bn]=(item2){a[i].x,a[i].px,a[i].py,i};
	sort(b+1,b+bn+1);
	for(int i=1,j;i<=an;i=j)
	{
		j=i;
		while(j<=an&&a[j].x==a[i].x)
			j++;
		for(int k=i+1;k<j;k++)
			if(a[k].px==a[k-1].px)
				fa[fis(k)]=fa[fis(k-1)];
		for(int k=i+1;k<j;k++)
			if(b[k].py==b[k-1].py)
				fa[fis(b[k].pos)]=fa[fis(b[k-1].pos)];
		for(int k=i;k<j;k++)
		{
			ans[fis(k)]=max(ans[fis(k)],mx[a[k].px]+1);
			ans[fis(k)]=max(ans[fis(k)],my[a[k].py]+1);
		}
		for(int k=i;k<j;k++)
			mx[a[k].px]=my[a[k].py]=ans[fis(k)];
	}
	printf("%d",*max_element(mx+1,mx+n+1));
	return 0;
}

在这里插入图片描述
 这是一道模板题,感谢我的笔记本里恰好保存着,然后羞耻地AC了,佩服在现场打出来的同学,同时确实意识到了自己基础还是很成问题的。
 板子打的确实不怎么好看,不过还算清晰,之后要多写写Splay了。

#include<cstdio>
#include<algorithm>
#define maxn 100005
#define xl c[x][0]
#define xr c[x][1]
#define dir(x) (c[fa[x]][1]==x)
using namespace std;
using LL=long long;

struct SplayTree
{
	int root,tot;
	int c[maxn][2],fa[maxn],key[maxn],rev[maxn],s[maxn];
	LL sum[maxn];
	
	inline void push_up(int x)
	{
		s[x]=s[xl]+s[xr]+1;
		sum[x]=sum[xl]+sum[xr]+key[x];
	}
	
	inline void push_down(int x)
	{
		if(rev[x])
		{
			rev[xl]^=1;
			rev[xr]^=1;
			swap(xl,xr);
			rev[x]=0;
		}
	}
	
	inline void maintain(int x)
	{
		while(x!=root)
			push_up(x),x=fa[x];
		push_up(x);
	}
	
	inline void rotate(int x, int f)
	{
		int y=fa[x];
		c[y][f^1]=c[x][f];
		if(c[x][f])
			fa[c[x][f]]=y;
		fa[x]=fa[y];
		if(fa[y])
			c[fa[y]][dir(y)]=x;
		c[x][f]=y;
		if(y)
			fa[y]=x;
		push_up(y);
	}
	
	void splay(int x, int goal_fa)
	{
		int f;
		push_down(x);
		while(fa[x]!=goal_fa)
		{
			int y=fa[x],z=fa[y];
			if(z==goal_fa)
				push_down(z);
			push_down(y),push_down(x);
			if(z==goal_fa)
				rotate(x,dir(x)^1);
			else
			{
				f=dir(y);
				c[y][f^1]==x?rotate(x,f):rotate(y,f^1);
				rotate(x,f^1);		
			}
		}
		push_up(x);
		if(goal_fa==0)
			root=x;
	}
	
	int find_kth(int k)
	{
		int x=root;
		push_down(x);
		while(x&&(s[xl]+1<k||k<=s[xl]))
		{
			if(k<=s[xl])
				x=xl;
			else
				k-=s[xl]+1,x=xr;
			push_down(x);
		}
		return x;
	}
	
	inline void new_node(int &x, int v, int pa)
	{
		x=++tot;
		xl=xr=0;
		fa[x]=pa;
		sum[x]=key[x]=v;
		s[x]=1;
	}
	
	void insert(int v)
	{
		if(root==0)
		{
			root=1;
			key[1]=v;
			tot=s[1]=1;
			c[1][0]=c[1][1]=0;
			return ;
		}
		int x=root;
		while(xr)
			x=xr;
		new_node(xr,v,x);
		maintain(x);
		splay(x,0);
	}
	
	LL reverse(int l, int r)
	{ 
		int x1=find_kth(l-1),x2=find_kth(r+1),x;
		if(x1==0&&x2==0)
		{
			rev[root]^=1;
			return sum[root];
		}
		if(x1)
			splay(x1,0);
		if(x2)
			splay(x2,x1?root:0);
		x=c[x2][0]?c[x2][0]:c[x1][1];
		rev[x]^=1;
		return sum[x];
	}
	
	void print(int x)
	{
		push_down(x); 
		if(x==0)
			return ;
		print(xl);
		printf("%d ",x);
		print(xr);
	}
}S;

int n,l,r,x,m;

int main()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=n;i++)
		S.insert(i);
	while(m--)
	{
		scanf("%d%d",&l,&r);
		printf("%lld\n",S.reverse(l,r));		
	}
	S.print(S.root);
	return 0;
}

在这里插入图片描述
 模板题,直接照着汝佳的代码抄的,方法就是用堆两两归并,当然合并方式不会只有汝佳这一种,这道题的做法也不会只有一种。给一个唐老师对这类问题的详细讨论 https://blog.csdn.net/skywalkert/article/details/78848170 (最优卡组,这个问题的加强版)

#include<cstdio>
#include<algorithm>
#include<queue>
using namespace std;

struct item
{
	int s,b;
	bool operator < (const item &t) const
	{
		return s>t.s;
	}
};

int A[1005][1005],n;

void merge(int *A, int *B, int *C, int n)
{
	priority_queue<item> Q;
	int b;
	for(int i=0;i<n;i++)
		Q.push((item){A[i]+B[0],0});
	for(int i=0;i<n;i++)
	{
		item t=Q.top();Q.pop();
		C[i]=t.s;
		b=t.b;
		if(b+1<n)
			Q.push((item){t.s-B[b]+B[b+1],b+1});
	}
}

int main()
{
	scanf("%d",&n);
	for(int i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
			scanf("%d",&A[i][j]);
		sort(A[i],A[i]+n);
	}
	for(int i=1;i<n;i++)
		merge(A[0],A[i],A[0],n);
	for(int i=0;i<n;i++)
		printf("%d ",A[0][i]);
	return 0;
}

在这里插入图片描述
 也就是洛谷2146 软件包管理器,标准解法是用树链剖分+线段树维护和+区间set(可恨当时还不会,现在会了真的感觉好简单),网上题解太多了,这里不多叙述。

#include<cstdio>
#include<vector>
#define kl (k<<1)
#define kr (k<<1|1)
#define M (L+R>>1)
#define lin L,M
#define rin M+1,R
using namespace std;
using LL=long long;

const int maxn=1E5+10;

struct node
{
    int val,setv;
}T[1<<18];

int sz[maxn],top[maxn],son[maxn],dep[maxn],fa[maxn],dfn[maxn],rdfn[maxn],dfs_clock;
int n,m,x;
char o[15];
vector<int> E[maxn];

void build_tree(int k, int L, int R)
{
    if(L==R)
    {
        T[k].val=0;
        T[k].setv=-1;
        return ;
    }
    build_tree(kl,lin);
    build_tree(kr,rin);
    T[k].val=0;
    T[k].setv=-1;
}

int query(int k, int L, int R, int l, int r)
{
    if(l<=L&&R<=r)
        return T[k].setv==-1?T[k].val:(T[k].setv?R-L+1:0);
    if(T[k].setv!=-1)
        return T[k].setv==-1?T[k].val:(T[k].setv?min(R,r)-max(L,l)+1:0);
    int res=0;
    if(l<=M)
        res+=query(kl,lin,l,r);
    if(r>M)
        res+=query(kr,rin,l,r);
    return res;
}

void set(int k, int L, int R, int l, int r, int d)
{
    if(l<=L&&R<=r)
    {
        T[k].setv=d;
        return ;
    }
    if(T[k].setv!=-1)
        T[kl].setv=T[kr].setv=T[k].setv,T[k].setv=-1;
    if(l<=M)
        set(kl,lin,l,r,d);
    if(r>M)
        set(kr,rin,l,r,d);
    T[k].val=(T[kl].setv==-1?T[kl].val:(T[kl].setv?M-L+1:0))
        +(T[kr].setv==-1?T[kr].val:(T[kr].setv?R-M:0));
}

void dfs1(int u, int f, int d)
{
    dep[u]=d,fa[u]=f,sz[u]=1;
    for(int &v:E[u])
        if(v!=f)
        {
            dfs1(v,u,d+1);
            sz[u]+=sz[v];
            if(!son[u]||sz[v]>sz[son[u]])
                son[u]=v;
        }
}

void dfs2(int u, int t)
{
    top[u]=t,dfn[u]=++dfs_clock;
    rdfn[dfs_clock]=u;
    if(!son[u])
        return ;
    dfs2(son[u],t);
    for(int &v:E[u])
        if(v!=son[u]&&v!=fa[u])
            dfs2(v,v);
}

int query_path(int x, int y)
{
    int ans=0,fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]>=dep[fy])
            ans+=query(1,1,n,dfn[fx],dfn[x]),x=fa[fx];
        else
            ans+=query(1,1,n,dfn[fy],dfn[y]),y=fa[fy];
        fx=top[x],fy=top[y];
    }
    if(dfn[x]>dfn[y])
        swap(x,y);
    ans+=query(1,1,n,dfn[x],dfn[y]);
    return ans;
}

void set_path(int x, int y, int z)
{
    int fx=top[x],fy=top[y];
    while(fx!=fy)
    {
        if(dep[fx]>dep[fy])
            set(1,1,n,dfn[fx],dfn[x],z),x=fa[fx];
        else
            set(1,1,n,dfn[fy],dfn[y],z),y=fa[fy];
        fx=top[x],fy=top[y];
    }
    if(dfn[x]>dfn[y])
        swap(x,y);
    set(1,1,n,dfn[x],dfn[y],z);
}

int main()
{
    scanf("%d",&n);
    for(int i=1,x;i<n;i++)
        scanf("%d",&x),++x,E[x].push_back(i+1),E[i+1].push_back(x);
    dfs1(1,0,0);
    dfs2(1,1);
    build_tree(1,1,n);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s",&o);
        if(o[0]=='i')
        {
            scanf("%d",&x);
            ++x;
            printf("%d\n",dep[x]+1-query_path(1,x));
            set_path(1,x,1);
        }
        else
        {
            scanf("%d",&x);
            ++x;
            printf("%d\n",query(1,1,n,dfn[x],dfn[x]+sz[x]-1));
            set(1,1,n,dfn[x],dfn[x]+sz[x]-1,0);
        }
    }
    return 0;
}

 另外非常有必要提一下这一题不用树链剖分的解法,是当时子渊学长提出来的(orz),他提出,对于一棵树的dfs序列(这里的dfs序列既包括入序列,也包括出序列,长度为2n),维护一个出入次数值,例如数据0 0 0 1 1 5,dfs序列为 0 , 1 , 4 , 4 ′ , 5 , 6 , 6 ′ , 5 ′ , 1 ′ , 2 , 2 ′ , 3 , 3 ′ , 0 ′ 0,1,4,4&#x27;,5,6,6&#x27;,5&#x27;,1&#x27;,2,2&#x27;,3,3&#x27;,0&#x27; 0,1,4,4,5,6,6,5,1,2,2,3,3,0,如果从某个状态上,我要安装5,然后我找到了5上方最浅的未安装的点(比如是1),那么我就会把 5 ′ 5&#x27; 5之前到 1 1 1所有的位置+1(实际代码中dfs入序列和出序列需要分开维护),而如果要卸载,则把对应子树区间全部清0即可。这样的序列有以下意义

  • 如果对应的是入序列的元素,那么代表进入这个点的子树几次,反之,代表出这个点的几次,因此一个点的入与出的差要么是0(没安装)要么是1(安装)
  • 一个子树中有多少点被安装,就是这个子树对应区间和
  • 一个点的前缀和就是dfs序列中在这个点之前的已安装包的个数(这个性质这里没用到)
     现在问题是,安装时,怎么找到那个未安装的最浅的点。这里可以用倍增+线段树单点查询的方法,用 l o g n ∗ l o g n logn*logn lognlogn的方法找到,后来pmxm说可以变成一个log,其实也好理解,就是线段树logn定位找到包含安装点的最大的那个区间和为0的那个区间,然后再用logn去找到最大的被包含的祖先对应的区间。不过我想正常人都懒得这么写(而且估计常数大的恶心)。毕竟本身线段树又add又set已经烦死我了。
#include<cstdio>
#include<vector>
#define kl (k<<1)
#define kr (k<<1|1) 
#define M (L+R>>1)
#define lin L,M
#define rin M+1,R
using namespace std;

int n,m,x,fa[100005],dfn[100005],dfn2[100005],dep[100005],sz[100005],c1,c2,p[100005][17];
char s[15];
vector<int> E[100005];

struct Seg_Tree
{
    int T[1<<18],addv[1<<18];
    bool setv[1<<18];
    
    inline int cal(int k, int L, int R)
    {
        return (setv[k]?0:T[k])+addv[k]*(R-L+1);
    }
    
    inline void maintain(int k, int L, int R)
    {
        T[k]=cal(kl,lin)+cal(kr,rin);
    }
    
    inline void push_down(int k)
    {
        if(setv[k])
        {
            setv[kl]=setv[kr]=setv[k],addv[kl]=addv[kr]=0;
            setv[k]=false;
        }
        if(addv[k])
        {
            addv[kl]+=addv[k],addv[kr]+=addv[k];
            addv[k]=0;
        }
    }
    
    int query(int k, int L, int R, int l, int r, int tmp)
    {
        if(setv[k])
            return (tmp+addv[k])*(min(R,r)-max(l,L)+1);
        if(l<=L&&R<=r)
            return T[k]+(tmp+addv[k])*(R-L+1);
        int res=0;
        if(l<=M)
            res+=query(kl,lin,l,r,tmp+addv[k]);
        if(r>M)
            res+=query(kr,rin,l,r,tmp+addv[k]);
        return res;
    }
    
    void add(int k, int L, int R, int l, int r, int d)
    {
        if(l<=L&&R<=r)
        {
            addv[k]+=d;
            return ;
        }
        push_down(k);
        if(l<=M)
            add(kl,lin,l,r,d);
        if(r>M)
            add(kr,rin,l,r,d);
        maintain(k,L,R);
    }
    
    void set(int k, int L, int R, int l, int r)
    {
        if(l<=L&&R<=r)
        {
            setv[k]=true;
            addv[k]=0;
            return ;
        }
        push_down(k);
        if(l<=M)
            set(kl,lin,l,r);
        if(r>M)
            set(kr,rin,l,r);
        maintain(k,L,R);
    }
}A,B;

void dfs(int x)
{
    sz[x]=1;
    dfn[x]=++c1;
    for(int &j:E[x])
    {
        p[j][0]=x;
        dep[j]=dep[x]+1;
        for(int k=1;1<<k<=dep[j];k++)
            p[j][k]=p[p[j][k-1]][k-1];
        dfs(j),sz[x]+=sz[j];
    }
    dfn2[x]=++c2;
}

int sum(int x, bool single=false)
{
    return A.query(1,1,n,dfn[x],dfn[x]+(single?0:sz[x]-1),0)-B.query(1,1,n,dfn2[x]-(single?0:sz[x]-1),dfn2[x],0);
}

int main()
{
    scanf("%d",&n);
    for(int i=2;i<=n;i++)
    {
        scanf("%d",&fa[i]),fa[i]++;
        E[fa[i]].push_back(i);
    }
    dfs(1);
    scanf("%d",&m);
    while(m--)
    {
        scanf("%s%d",s,&x);
        ++x;
        if(s[0]=='i')
        {
            if(sum(x))
            {
                puts("0");
                continue;
            }
            int pos=0,y=x;
            for(;1<<pos<=dep[x]&&!sum(p[x][pos],true);pos++);
            for(--pos;pos>=0;pos--)
                if(1<<pos<=dep[y]&&!sum(p[y][pos],true))
                    y=p[y][pos];
            printf("%d\n",dep[x]-dep[y]+1);
            A.add(1,1,n,dfn[y],dfn[x]+sz[x]-1,1);
            if(dfn2[x]>dfn2[y]-sz[y]+1) 
                B.add(1,1,n,dfn2[y]-sz[y]+1,dfn2[x]-1,1);
        }
        else
        {
            printf("%d\n",sum(x));
            A.set(1,1,n,dfn[x],dfn[x]+sz[x]-1);
            B.set(1,1,n,dfn2[x]-sz[x]+1,dfn2[x]);
        }
    }
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值