拖了这么久更新有些尴尬。第一次春训是个人场的数据结构主题。总的来说我数据结构掌握得还不是很扎实,很多人轻松打掉的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',5,6,6',5',1',2,2',3,3',0' 0,1,4,4′,5,6,6′,5′,1′,2,2′,3,3′,0′,如果从某个状态上,我要安装5,然后我找到了5上方最浅的未安装的点(比如是1),那么我就会把 5 ′ 5' 5′之前到 1 1 1所有的位置+1(实际代码中dfs入序列和出序列需要分开维护),而如果要卸载,则把对应子树区间全部清0即可。这样的序列有以下意义
- 如果对应的是入序列的元素,那么代表进入这个点的子树几次,反之,代表出这个点的几次,因此一个点的入与出的差要么是0(没安装)要么是1(安装)
- 一个子树中有多少点被安装,就是这个子树对应区间和
- 一个点的前缀和就是dfs序列中在这个点之前的已安装包的个数(这个性质这里没用到)
现在问题是,安装时,怎么找到那个未安装的最浅的点。这里可以用倍增+线段树单点查询的方法,用 l o g n ∗ l o g n logn*logn logn∗logn的方法找到,后来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;
}