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));
}
}

浙公网安备 33010602011771号