TJOI2016(8.11~8.12模拟赛)

P4092 [HEOI2016/TJOI2016]树

题意:

有以下两种操作:

标记操作:对某个结点打上标记。(在最开始,只有结点 1 有标记,其他结点均无标记,而且对于某个结点,可以打多次标记。)

询问操作:询问某个结点最近的一个打了标记的祖先。(这个结点本身也算自己的祖先)

做法:

并查集。

先把所有节点标记添上来,接着倒着删除,如果删到 \(0\) 了就将其并查集父亲指向其树上的父亲。

code:

#include<bits/stdc++.h>
using namespace std;
int n,nq;
const int N=1e5+8;
int fr[N],to[N<<1],nxt[N<<1],too=0;
int fa[N],ff[N];
int q[N][3];
int p[N],ans[N],nn;
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void dfs(int x)
{
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==ff[x]) continue;
		ff[y]=x;
		dfs(y);
	}
}
int find(int x)
{
	return fa[x]==x?x:fa[x]=find(fa[x]);
}
int main()
{
	cin>>n>>nq;
	int x,y;
	for(int i=1;i<n;i++)
	{
		scanf("%d%d",&x,&y);
		add(x,y);add(y,x);
	}
	char c[5];
	p[1]=1;
	for(int i=1;i<=nq;i++)
	{
		scanf("%s%d",&c,&x);
		q[i][1]=x;
		if(c[0]=='Q') q[i][0]=0;
		else q[i][0]=1,p[x]++;
	}
	dfs(1);
	for(int i=1;i<=n;i++) 
	if(p[i]) fa[i]=i;
	else fa[i]=ff[i];
	for(int i=nq;i>=1;i--)
	{
		if(q[i][0]==0)
		ans[++nn]=find(q[i][1]);
		else{
			p[q[i][1]]--;
			if(!p[q[i][1]])
			fa[q[i][1]]=ff[q[i][1]];
		}
	}
	for(int i=nn;i>=1;i--)
	printf("%d\n",ans[i]);
}

P4093 [HEOI2016/TJOI2016]序列

题意:

有一个数列,数列中某些项的值可能会变化,但同一个时刻最多只有一个值发生变化。能否选出一个子序列,使得在任意一种变化中,这个子序列都是不降的。请你告诉她这个子序列的最长长度即可。

做法:

跳过普通 DP 介绍。

如果 \(i\) 可由 \(j\) 转移过来,则

  1. \(i<j\)
  2. \(max[i]<=a[j]\)
  3. \(a[i]<=min[j]\)

则转变为三维偏序问题。

采用 \(cdq\) 分治即可。

注意:

\(cdq\) 必须先做左,再由左向右转移,再做右边。做右边之前还要先按编号将其排回来。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+8;
int n,m;
struct node{
	int v,mx,mi,ans,num;
} a[N],b[N];
int ans=0;
int c[N];
void ad(int x,int v)
{
	while(x<=100000)
	{
		if(v==-1) c[x]=0;
		else c[x]=max(c[x],v);
		x+=x&-x;
	}
}
int ask(int x)
{
	int num=0;
	while(x)
	{
		num=max(c[x],num);
		x-=x&-x;
	}
	return num;
}
bool cmp1(node a,node b) {
	return a.mx<b.mx;
}
bool cmp2(node a,node b){
	return a.v<b.v;
}
bool cmp3(node a,node b){
	return a.num<b.num;
}
void cdq(int l,int r)
{
	if(l==r) 	{
		a[l].ans=max(1,a[l].ans);
		return;
	}
	int mid=(l+r)>>1;
	cdq(l,mid);
	sort(a+l,a+mid+1,cmp1);
	sort(a+mid+1,a+r+1,cmp2);
	int p=l;
	for(int i=mid+1;i<=r;i++)
	{
		while(a[p].mx<=a[i].v&&p<=mid)
		{
			ad(a[p].v,a[p].ans);
			p++;
		}
		a[i].ans=max(a[i].ans,ask(a[i].mi)+1);
	}
	for(int i=l;i<p;i++) ad(a[i].v,-1);
//	memset(c,0,sizeof(c));
	sort(a+mid+1,a+r+1,cmp3);
	cdq(mid+1,r); 
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++)
	scanf("%d",&a[i].v),a[i].mi=a[i].mx=a[i].v,a[i].ans=1,a[i].num=i;
	int x,v;
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d",&x,&v);
		a[x].mi=min(a[x].mi,v);
		a[x].mx=max(a[x].mx,v);
	}
	cdq(1,n);
	for(int i=1;i<=n;i++)
	ans=max(ans,a[i].ans);
	cout<<ans;
}

P2824 [HEOI2016/TJOI2016]排序

题意:

给出一个 1 到 n 的排列,现在对这个排列序列进行 m 次局部排序,排序分为两种:

  1. \(0\) \(l\) \(r\) 表示将区间 \([l,r]\) 的数字升序排序
  2. \(1\) \(l\) \(r\) 表示将区间 \([l,r]\) 的数字降序排序

最后询问第 q 位置上的数字。

做法:

二分答案。

将大于等于答案的数标为 \(1\),小于答案的标为 \(0\),每次排序及查询区间一的个数,再将其复制到区间前或区间后,最后 q 处为一则答案合理。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+8;
int a[N];
int tr[N<<2],tag[N<<2];
int n,m,p;
int q[N][3];
void push(int k,int l,int r)
{
	if(tag[k]==-1) return;
	tag[k<<1]=tag[k<<1|1]=tag[k];
	int mid=l+r>>1;
	tr[k<<1]=(mid-l+1)*tag[k];
	tr[k<<1|1]=(r-mid)*tag[k];
	tag[k]=-1;
}
void gt(int k,int l,int r,int x,int y,int v)
{
	if(x>y) return;
	if(x<=l&&r<=y)
	{
		tag[k]=v;tr[k]=(r-l+1)*v;
		return;
	}
	push(k,l,r);
	int mid=l+r>>1;
	if(x<=mid) gt(k<<1,l,mid,x,y,v);
	if(y>mid) gt(k<<1|1,mid+1,r,x,y,v);
	tr[k]=tr[k<<1]+tr[k<<1|1];
}
int ask(int k,int l,int r,int x,int y)
{
	if(x<=l&&r<=y) return tr[k];
	push(k,l,r);
	int mid=l+r>>1;
	int num=0;
	if(x<=mid) num+=ask(k<<1,l,mid,x,y);
	if(y>mid) num+=ask(k<<1|1,mid+1,r,x,y);
	return num;
}
bool work(int k)
{
	memset(tr,0,sizeof(tr));
	memset(tag,-1,sizeof(tag));
	for(int i=1;i<=n;i++)
	if(a[i]>=k) gt(1,1,n,i,i,1);
	for(int i=1;i<=m;i++)
	{
		int num=ask(1,1,n,q[i][1],q[i][2]);
		if(q[i][0]){
			gt(1,1,n,q[i][1],q[i][1]+num-1,1);
			gt(1,1,n,q[i][1]+num,q[i][2],0);
		}
		else{
			gt(1,1,n,q[i][1],q[i][2]-num,0);
			gt(1,1,n,q[i][2]-num+1,q[i][2],1);
		}
	}
	return ask(1,1,n,p,p)==1;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int i=1;i<=m;i++) scanf("%d%d%d",&q[i][0],&q[i][1],&q[i][2]);
	int l=1,r=n,ans=0;
	cin>>p;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(work(mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	cout<<ans;
}

P2825 [HEOI2016/TJOI2016]游戏

题意:

略。

做法:

二分图最大匹配。

将行与列连边,如果匹配此边则为在此处放了一个炸弹,一行或一列只能匹配一个。

如果有硬石头,则新建节点,可以视之为将一行分为两行,一列分为两列,隔开的这些点可多匹配一个炸弹。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=6025;
char s[65];
int t1,t2;
int fr[N<<1],to[N<<2],nxt[N<<2],too=0;
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
bool vis[N];
int n,m;
int id[N],bl[N];
bool dfs(int x)
{
	if(vis[x]) return 0;
	vis[x]=1;
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(bl[y]==0||dfs(bl[y]))
		{
			bl[y]=x;
			return 1;
		}
	}
	return 0;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=m;i++) id[i]=++t2;
	for(int i=1;i<=n;i++)
	{
		scanf("%s",s+1);
		t1++;
		for(int j=1;j<=m;j++)
		{
			if(s[j]=='*') add(t1,id[j]);
			if(s[j]=='#'){
				t1++;
				id[j]=++t2;
			}
		}
	}
	int ans=0;
	for(int i=1;i<=t1;i++)
	{
		memset(vis,0,sizeof(vis));
		ans+=dfs(i);
	}
	cout<<ans;
}

五: P4094 [HEOI2016/TJOI2016]字符串

题意:

给定字符串 \(s\)
每个问题均有 \(a,b,c,d\) 四个参数,问你子串 \(s[a..b]\) 的所有子串和 \(s[c..d]\) 的最长公共前缀的长度的最大值是多少。

做法:

后缀自动机加线段树合并。

先翻转子串。

每次二分 \(cd\) 串后缀,看其是否在 \(ab\) 中出现过。

后缀自动机的每个节点上建立线段树表示其 \(endpos\) 集。线段树合并求出每个点的线段树。

找二分的串:先找到此串结尾点最长串在后缀自动机的位置(即添加最后一个字符时所新建的节点),再利用倍增向上跳找到最上边的第一个 \(len\) 大于等于本串长度的节点。

线段树合并:

在一棵树上每个叶子节点只有一个数,将子节点信息并到父节点上。

code:

int mg(int rt1,int rt2,int l,int r)
{
	if(!rt1||!rt2) return rt1+rt2;
	
	int k=++tt;
	if(l==r){
		tr[k].sum=tr[rt1].sum+tr[rt2].sum;
		return k;
	}
	
	int mid=l+r>>1;
	tr[k].l=mg(tr[rt1].l,tr[rt2].l,l,mid);
	tr[k].r=mg(tr[rt1].r,tr[rt2].r,mid+1,r);
	tr[k].sum=tr[rt1].sum+tr[rt2].sum;
	return k;
}

玄学时空复杂度:\(nlogn\)

code:

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+8;
char s[N];
int n,m;
int rt[N];
int tot=1,last=1;
int tt=0;
int f[N][25],pp[N];
struct sam{
	int len,fa,ch[26];
} a[N];
struct tree{
	int l,r,sum;
} tr[N<<6];
int fr[N],to[N],nxt[N],too=0;
int aa,bb,cc,dd;
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void adt(int &k,int l,int r,int x)
{
	if(!k) k=++tt;
	++tr[k].sum;
	if(l==r) return ;
	int mid=l+r>>1;
	if(x<=mid) adt(tr[k].l,l,mid,x);
	else adt(tr[k].r,mid+1,r,x);
}
int ask(int k,int l,int r,int x,int y)
{
	if(!k) return 0; 
	if(y<x) return 0;
	if(x<=l&&r<=y) return tr[k].sum;
	int mid=l+r>>1;
	int num=0;
	if(x<=mid) num+=ask(tr[k].l,l,mid,x,y);
	if(y>mid) num+=ask(tr[k].r,mid+1,r,x,y);
	return num;
}
void ad(int x,int pos)
{
	int p=last,k=last=++tot;pp[pos]=k;
	adt(rt[k],1,n,pos);
	a[k].len=a[p].len+1;
	for(;!a[p].ch[x]&&p;p=a[p].fa) a[p].ch[x]=k;
	if(p==0) a[k].fa=1;
	else{
		int t1=a[p].ch[x];
		if(a[t1].len==a[p].len+1) a[k].fa=t1;
		else{
			int t2=++tot;
			a[t2]=a[t1];
			a[t2].len=a[p].len+1;
			a[t1].fa=a[k].fa=t2;
			for(;a[p].ch[x]==t1&&p;p=a[p].fa)
			a[p].ch[x]=t2;
		}
	}
}
int mg(int rt1,int rt2,int l,int r)
{
	if(!rt1||!rt2) return rt1+rt2;
	
	int k=++tt;
	if(l==r){
		tr[k].sum=tr[rt1].sum+tr[rt2].sum;
		return k;
	}
	
	int mid=l+r>>1;
	tr[k].l=mg(tr[rt1].l,tr[rt2].l,l,mid);
	tr[k].r=mg(tr[rt1].r,tr[rt2].r,mid+1,r);
	tr[k].sum=tr[rt1].sum+tr[rt2].sum;
	return k;
}
void dfs(int x)
{
	f[x][0]=a[x].fa;
	for(int i=1;i<=18;i++)
	f[x][i]=f[f[x][i-1]][i-1];
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		dfs(y);
		rt[x]=mg(rt[x],rt[y],1,n);
	}
}
bool check(int l,int r)
{
	int len=r-l+1;
	int x=pp[r];
	for(int i=20;i>=0;--i)
	{
		if(a[f[x][i]].len>=len)
		x=f[x][i];
	}
    return ask(rt[x],1,n,aa+len-1,bb);
}
int main()
{
	cin>>n>>m;
	scanf("%s",s+1);
	for(int i=1;i<=n/2;i++) 
	swap(s[i],s[n-i+1]);
	for(int i=1;i<=n;i++) ad(s[i]-'a',i);
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	dfs(1);
	while(m--)
	{
        scanf("%d%d%d%d",&aa,&bb,&cc,&dd);
        aa=n-aa+1;bb=n-bb+1;cc=n-cc+1;dd=n-dd+1;
        swap(aa,bb);swap(cc,dd);
        int l=cc,r=dd,ans=0;
        while(l<=r)
        {
            int mid=(l+r)>>1;
            if(check(mid,dd))  ans=dd-mid+1,r=mid-1;
            else  l=mid+1;
        }
        printf("%d\n",ans);
	}
}
posted @ 2021-08-13 20:25  ☄️ezuyz☄️  阅读(34)  评论(0)    收藏  举报