TJOI2018(8.14~8.15模拟赛)

一:P4588 [TJOI2018]数学计算

题意略。

做法:

分块。分成 \(sqrt(m)\) 个块,算出每个块的乘积,操作一直接乘,操作二找出其对应块,将其数值改为一在做一遍乘积,时间复杂度根号 \(m\)

code:

#include<bits/stdc++.h>
#define ll long long
using namespace std;
const int N=1e5+8;
int q,mod;
int bl[N];
ll kq[1007];
int a[N];
int main()
{
	int T;
	cin>>T;
	while(T--)
	{
		cin>>q>>mod;
		int siz=sqrt(q);
		int nq=ceil((double)q/siz);
		for(int i=1;i<=nq;i++)
		for(int j=(i-1)*siz+1;j<=min(q,i*siz);j++)
		bl[j]=i;
		int op,x;
		ll ans=1;
		for(int i=1;i<=q;i++)
		{
			scanf("%d%d",&op,&x);
			if((bl[i]-1)*siz+1==i)
			kq[bl[i]]=1;
			if(op==1)
			{
				ans=ans*x%mod;
				kq[bl[i]]=kq[bl[i]]*x%mod;
				a[i]=x;
			}
			else{
				a[i]=1;
				ans=1;
				for(int j=1;j<=bl[i];j++)
				if(bl[x]!=j) ans=ans*kq[j]%mod;
				a[x]=1;kq[bl[x]]=1;
				for(int j=(bl[x]-1)*siz+1;j<=min(i,bl[x]*siz);j++)
				ans=ans*a[j]%mod,kq[bl[x]]=kq[bl[x]]*a[j]%mod;
			}
			printf("%lld\n",ans);
		}
	}
}

二:P4589 [TJOI2018]智力竞赛

题意:

给定一张有向无环图,求用 \(n+1\) 条可重合链覆盖,使没覆盖到的点中权值最小的点权值最大,输出最大值。

做法:

求有向无环图的最小链覆盖数:

先用 \(Floyed\) 求出传递闭包,再将一个点拆成两个进行二分图匹配,\(x\) 可达 \(y\) 则将 \(x\) 连向\(y\),刚开始看做每个点用一条链,匹配一次即少一条链,最后点数减去匹配数即为答案。

对于本题,二分答案,将大于答案的点删去,求剩下的二分图最小覆盖链数,看是否小于 \(n-1\)

具体求剩下二分图的最小链覆盖,匹配时跳过大于答案的点不用匹配,深搜时不能匹配大于答案的点,最后用小于答案的点数减去匹配数即可。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=505;
int n,m;
int a[N][N],b[N][N];
int v[N];
bool vis[N];
int bl[N];
int K;
bool dfs(int x)
{
	if(vis[x]) return 0;
	vis[x]=1;
	for(int i=1;i<=m;i++)
	{
		if(v[i]>=K) continue;
		if(a[i][x]&&(bl[i]==0||dfs(bl[i]))){
			bl[i]=x;
			return 1;
		}
	}
	return 0;
}
bool check(int k)
{
	K=k;
	memset(bl,0,sizeof(bl));
	int cnt=m,num=0;
	for(int i=1;i<=m;i++) 
	{
		if(v[i]>=k){
		 cnt--;continue;
		}
		else{
			memset(vis,0,sizeof(vis));
			num+=dfs(i);
		}
	}
	return cnt-num<=n;
}
int main()
{
	cin>>n>>m;
	n++;
	int mx=0;
	for(int i=1;i<=m;i++) {
		scanf("%d",&v[i]);mx=max(mx,v[i]);
		int k,x;
		scanf("%d",&k);
		for(int j=1;j<=k;j++) scanf("%d",&x),a[i][x]=1;
	}
	for(int i=1;i<=m;i++)
	for(int j=1;j<=m;j++)
	for(int p=1;p<=m;p++)
	if(a[i][p]&&a[p][j]) a[i][j]=1;
	if(check(mx+1)) {
		puts("AK");
		return 0;
	}
	int l=1,r=mx;
	int ans=0;
	while(l<=r)
	{
		int mid=l+r>>1;
		if(check(mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	cout<<ans;
}

三:P4591 [TJOI2018]碱基序列

题意:

给定母串 \(s\)\(n\) 个人,每个人有若干个(小于等于十)小子串,从 \(1\)\(n\) 每个人拿出一个串拼成一个长串,做出这个大子串在母串中出现次数的贡献,求每种方案(每个人拿不同小子串的所有情况)的贡献和。

做法:

先在母串上建立后缀自动机,再在自动机上 \(DP\),先枚举每个人,找到自动机上所有有权值的位置作为起始位置,向下找这个人的每一个子串,如果能找到,则将找到的位置加上起始位置的 \(dp\) 值,找完每一个子串后再将起始位置数值清零。一个点对应多个串也没关系,初始时将一的 \(dp\) 值设成一。

最后答案为所有有权值的位置乘上其出现次数的和。

code:

#include<bits/stdc++.h>
using namespace std;
#define ll long long
const int N=4e4+8;
int n;
char s[N];
int tot=1,last=1;
struct sam{
	int len,fa,ch[26];
} a[N];
ll cnt[N];
int fr[N],nxt[N],to[N],too=0;
int nn;
char c[11][N];
ll f[2][N];
int now=1;
int mod=1e9+7;
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void ad(int x)
{
	int p=last,k=last=++tot;cnt[k]=1;
	a[k].len=a[p].len+1;
	for(;p&&!a[p].ch[x];p=a[p].fa) a[p].ch[x]=k;
	if(!p) 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[k].fa=a[t1].fa=t2;
			for(;p&&a[p].ch[x]==t1;p=a[p].fa) a[p].ch[x]=t2;
		}
	}
}
void dfs1(int x)
{
	for(int i=fr[x];i;i=nxt[i])
	{
		dfs1(to[i]);
		cnt[x]+=cnt[to[i]];
	}
}
void ch(int k,int p,int l,int z)
{
	if(l==strlen(c[p]+1)){
		f[now][k]=(f[now][k]+f[now^1][z])%mod;
		return;
	}
	if(a[k].ch[c[p][l+1]-'A']){
		ch(a[k].ch[c[p][l+1]-'A'],p,l+1,z);
	}
}
void dfs(int x)
{
	if(x==0) return;
	if(f[now^1][x])
	{
		for(int i=1;i<=nn;i++)
		ch(x,i,0,x);
		f[now^1][x]=0;
	}
	for(int i=0;i<26;i++) dfs(a[x].ch[i]);
}
int main()
{
	cin>>n;
	scanf("%s",s+1);
	int len=strlen(s+1);
	for(int i=1;i<=len;i++)
	ad(s[i]-'A');
	for(int i=2;i<=tot;i++) add(a[i].fa,i);
	dfs1(1);
	f[0][1]=1;
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&nn);
		for(int j=1;j<=nn;j++) scanf("%s",c[j]+1);
		dfs(1);
		now^=1;
	}
	ll ans=0;
	for(int i=2;i<=tot;i++) ans=(ans+f[now^1][i]*cnt[i]%mod)%mod;
	cout<<ans;
}

四:P4592 [TJOI2018]异或

题意:

给定一棵树,
现在有 \(q\) 次操作,操作如下:

\(1\).\(x\) \(z\):查询节点 \(x\) 的子树中的节点权值与 \(z\) 异或结果的最大值。

\(2\). \(x\) \(y\) \(z\):查询节点 \(x\) 到节点 \(y\) 的简单路径上的节点的权值与 \(z\) 异或结果最大值。

做法:

类似于树链剖分,加上可持久化 \(tire\) 树。

将其按照树剖建立序列,对序列进行可持久化(从左向右往加数更新 \(tire\) 树),每个节点记录其左儿子和右儿子个数。

对于询问做类似于树剖的处理,对于每个区间,利用前缀和看这个区间此位值是否有左儿子或右儿子,找到最优答案并更新答案。

细节颇多。

code:

#include<bits/stdc++.h>
using namespace std;
const int N=1e5+8;
int n,m;
int a[N];
int rt[N];
int rev[N],seg[N],df=0,son[N],top[N],siz[N],fath[N];
struct tree{
	int l,r,cnt;
} tr[N<<6];
int fr[N],nxt[N<<1],to[N<<1],too=0;
int dep[N];
void add(int x,int y)
{
	to[++too]=y;
	nxt[too]=fr[x];
	fr[x]=too;
}
void ad(int &k,int kk,int p,int v)
{
	k=++too;
	tr[k]=tr[kk];
	tr[k].cnt++;
	if(p<0) return;
	if((1<<p)&v) ad(tr[k].r,tr[kk].r,p-1,v);
	else ad(tr[k].l,tr[kk].l,p-1,v);
}
void dfs1(int x,int fa)
{
	fath[x]=fa;
	dep[x]=dep[fa]+1;
	siz[x]=1;
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fa) continue;
		dfs1(y,x);
		siz[x]+=siz[y];
		if(siz[y]>siz[son[x]]) son[x]=y;
	}
}

void dfs2(int x,int topf)
{
	seg[x]=++df;
	rev[df]=x;
	top[x]=topf;
	if(son[x]) dfs2(son[x],topf);
	for(int i=fr[x];i;i=nxt[i])
	{
		int y=to[i];
		if(y==fath[x]||y==son[x]) continue;
		dfs2(y,y);
	}
}
int get(int t1,int t2,int v)
{
	int ans=0,p=29;
	int k1=t1,k2=t2;
	while(p>=0){
		if(v&(1<<p)){
			if(tr[tr[k2].l].cnt-tr[tr[k1].l].cnt) k1=tr[k1].l,k2=tr[k2].l,ans+=(1<<p);
			else k1=tr[k1].r,k2=tr[k2].r;
		}
		else{
			if(tr[tr[k2].r].cnt-tr[tr[k1].r].cnt) k1=tr[k1].r,k2=tr[k2].r,ans+=(1<<p);
			else k1=tr[k1].l,k2=tr[k2].l;
		}
		p--;
	}
	return ans;
}
int getl(int x,int y,int z)
{
	int ans=0;
	while(top[x]!=top[y])
	{
		if(dep[top[x]]<dep[top[y]]) swap(x,y);
		ans=max(ans,get(rt[seg[top[x]]-1],rt[seg[x]],z));
		x=fath[top[x]];
	}
	ans=max(ans,get(rt[seg[x]-1],rt[seg[x]],z));
	if(x==y) return ans;
	if(dep[x]<dep[y]) swap(x,y);
	ans=max(ans,get(rt[seg[y]-1],rt[seg[x]],z));
	return ans;
}
int main()
{
	cin>>n>>m;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	int op,x,y,z;
	for(int i=1;i<n;i++)
	scanf("%d%d",&x,&y),add(x,y),add(y,x);
	dfs1(1,0);
    dfs2(1,1);
	for(int i=1;i<=n;i++)
	{
		rt[i]=rt[i-1];
		ad(rt[i],rt[i-1],29,a[rev[i]]);
	}

	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&op,&x,&y);
		if(op==1) printf("%d\n",get(rt[seg[x]-1],rt[seg[x]+siz[x]-1],y));
		else scanf("%d",&z),printf("%d\n",getl(x,y,z));
	}
}
posted @ 2021-08-16 21:03  ☄️ezuyz☄️  阅读(48)  评论(0)    收藏  举报