NOI 2021 题目选做
轻重边
题目描述
解法
可以转化成染色模型,修改就是将路径染上一种新颜色,查询就是问路径上同色相邻点对个数。
直接上树剖即可,时间复杂度 \(O(n\log^2n)\),本题实现的最大难点就是多测。
总结
有区间赋值特性的题目中,可以考虑往染色模型上转化。
#include <cstdio>
#include <iostream>
using namespace std;
const int M = 1000005;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int T,n,m,tot,f[M],siz[M],son[M];
int cnt,top[M],num[M],fa[M],dep[M];
struct edge{int v,next;}e[M<<1];
struct node
{
int l,r,c,f;
node(int L=0,int R=0,int C=0,int F=0) :
l(L) , r(R) , c(C) , f(F) {}
node operator + (const node &b) const
{
return node(l,b.r,c+b.c+(r==b.l));
}
}tr[M<<2];
void dfs1(int u,int p)
{
siz[u]=1;fa[u]=p;
dep[u]=dep[p]+1;son[u]=0;
for(int i=f[u];i;i=e[i].next)
{
int v=e[i].v;
if(v==p) continue;
dfs1(v,u);
siz[u]+=siz[v];
if(siz[v]>siz[son[u]]) son[u]=v;
}
}
void dfs2(int u,int tp)
{
top[u]=tp;num[u]=++cnt;
if(son[u]) dfs2(son[u],tp);
for(int i=f[u];i;i=e[i].next)
if(e[i].v^fa[u] && e[i].v^son[u])
dfs2(e[i].v,e[i].v);
}
void color(int i,int c,int len)
{
tr[i].f=tr[i].l=tr[i].r=c;tr[i].c=len;
}
void down(int i,int l,int r)
{
if(!tr[i].f) return ;
int mid=(l+r)>>1;
color(i<<1,tr[i].f,mid-l);
color(i<<1|1,tr[i].f,r-mid-1);
tr[i].f=0;
}
void build(int i,int l,int r)
{
tr[i]=node(0,0,0,0);
if(l==r) {tr[i].l=tr[i].r=l;return ;}
int mid=(l+r)>>1;
build(i<<1,l,mid);
build(i<<1|1,mid+1,r);
tr[i]=tr[i<<1]+tr[i<<1|1];
}
void cover(int i,int l,int r,int L,int R,int c)
{
if(L>r || l>R) return ;
if(L<=l && r<=R) {color(i,c,r-l);return ;}
int mid=(l+r)>>1;down(i,l,r);
cover(i<<1,l,mid,L,R,c);
cover(i<<1|1,mid+1,r,L,R,c);
tr[i]=tr[i<<1]+tr[i<<1|1];
}
node ask(int i,int l,int r,int L,int R)
{
if(L<=l && r<=R) return tr[i];
int mid=(l+r)>>1;down(i,l,r);
if(mid<L) return ask(i<<1|1,mid+1,r,L,R);
if(R<=mid) return ask(i<<1,l,mid,L,R);
return ask(i<<1,l,mid,L,R)
+ask(i<<1|1,mid+1,r,L,R);
}
void add(int u,int v,int c)
{
while(top[u]^top[v])
{
if(dep[top[u]]<=dep[top[v]]) swap(u,v);
cover(1,1,n,num[top[u]],num[u],c);
u=fa[top[u]];
}
if(dep[u]<=dep[v]) swap(u,v);
cover(1,1,n,num[v],num[u],c);
}
int get(int u,int v)
{
node t1,t2;
while(top[u]^top[v])
{
if(dep[top[u]]>=dep[top[v]])
{
node x=ask(1,1,n,num[top[u]],num[u]);
swap(x.l,x.r);t1=t1+x;
u=fa[top[u]];
}
else
{
t2=ask(1,1,n,num[top[v]],num[v])+t2;
v=fa[top[v]];
}
}
if(dep[u]<=dep[v])
return (t1+ask(1,1,n,num[u],num[v])+t2).c;
node x=ask(1,1,n,num[v],num[u]);swap(x.l,x.r);
return (t1+x+t2).c;
}
void work()
{
n=read();m=read();cnt=tot=0;
for(int i=1;i<=n;i++) f[i]=0;
for(int i=1;i<n;i++)
{
int u=read(),v=read();
e[++tot]=edge{v,f[u]},f[u]=tot;
e[++tot]=edge{u,f[v]},f[v]=tot;
}
dfs1(1,0);dfs2(1,1);build(1,1,n);
for(int i=1;i<=m;i++)
{
int op=read(),x=read(),y=read();
if(op==1) add(x,y,++cnt);
else printf("%d\n",get(x,y));
}
}
signed main()
{
T=read();
while(T--) work();
}
庆典
题目描述
解法
首先把一般图化归为树的情况,我们可以先跑强连通缩点,然后会得到一个拓扑图。考虑拓扑排序,对于每个点保留最后一条被拓扑到的入边,这样就可以获得一棵跟原图可达性相同的树。
对于每个询问,我们把 \(s,t\) 已经新加边的端点拿出来建虚树,然后从 \(s\) 对正图 bfs
一遍,从 \(t\) 对反图 bfs
一遍。如果某个点 \(/\) 某条边正反都被访问过,那么可以计入贡献。时间复杂度 \(O(n\log n)\)
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#include <algorithm>
#include <stack>
#include <queue>
using namespace std;
const int M = 300005;
#define pb push_back
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,q,k,Ind,num[M],dfn[M],low[M],in[M],col[M];
int rt,fa[M][20],dep[M],deg[M],sum[M];
int tot,cnt,f1[M],f2[M],out[M];stack<int> st;
vector<int> G1[M],G2[M],G3[M];
struct edge{int v,c,next;}e[M];
void tarjan(int u)
{
st.push(u);in[u]=1;
dfn[u]=low[u]=++Ind;
for(int v:G1[u])
{
if(!dfn[v])
{
tarjan(v);
low[u]=min(low[u],low[v]);
}
else if(in[v])
low[u]=min(low[u],dfn[v]);
}
if(low[u]==dfn[u])
{
int v=0;cnt++;
do
{
v=st.top();st.pop();in[v]=0;
col[v]=cnt;num[cnt]++;
}while(v!=u);
}
}
void dfs(int u,int p)
{
in[u]=++cnt;
dep[u]=dep[fa[u][0]=p]+1;
sum[u]=sum[p]+num[u];
for(int i=1;i<20;i++)
fa[u][i]=fa[fa[u][i-1]][i-1];
for(int v:G3[u]) if(v^p) dfs(v,u);
out[u]=++cnt;
}
void build()
{
for(int i=1;i<=n;i++)
if(!dfn[i]) tarjan(i);
for(int u=1;u<=n;u++)
for(int v:G1[u]) if(col[u]^col[v])
G2[col[u]].pb(col[v]),deg[col[v]]++;
n=cnt;queue<int> q;
for(int i=1;i<=n;i++)
if(!deg[i]) q.push(i),rt=i;
while(!q.empty())
{
int u=q.front();q.pop();
for(int v:G2[u])
{
deg[v]--;
if(!deg[v]) G3[u].pb(v),q.push(v);
}
}
cnt=0;dfs(rt,0);
}
int lca(int u,int v)
{
if(dep[u]<=dep[v]) swap(u,v);
for(int i=19;i>=0;i--)
if(dep[fa[u][i]]>=dep[v])
u=fa[u][i];
if(u==v) return u;
for(int i=19;i>=0;i--)
if(fa[u][i]^fa[v][i])
u=fa[u][i],v=fa[v][i];
return fa[u][0];
}
void add(int *f,int u,int v,int c)
{
e[++tot]=edge{v,c,f[u]},f[u]=tot;
}
int cmp(int x,int y)
{
int u=x>0?in[x]:out[-x];
int v=y>0?in[y]:out[-y];
return u<v;
}
void work()
{
static int a[M]={},vis[M]={},v1[M]={},v2[M]={};
int s=col[read()],t=col[read()],m=0,ans=0;
if(!vis[s]) a[++m]=s,vis[s]=1;
if(!vis[t]) a[++m]=t,vis[t]=1;
for(int i=1;i<=k;i++)
{
int u=col[read()],v=col[read()];
if(!vis[u]) a[++m]=u,vis[u]=1;
if(!vis[v]) a[++m]=v,vis[v]=1;
add(f1,u,v,0);add(f2,v,u,0);
}
sort(a+1,a+1+m,cmp);int m1=m;
for(int i=1;i<m1;i++)
{
int x=lca(a[i],a[i+1]);
if(!vis[x]) vis[x]=1,a[++m]=x;
}
if(!vis[rt]) a[++m]=rt,vis[rt]=0;m1=m;
for(int i=1;i<=m1;i++) a[++m]=-a[i];
sort(a+1,a+1+m,cmp);
while(!st.empty()) st.pop();
for(int i=1;i<=m;i++)
{
if(a[i]>0) st.push(a[i]);
else
{
int u=st.top();st.pop();
if(u==rt) break;
int fa=st.top(),c=sum[u]-sum[fa]-num[u];
add(f1,fa,u,c);add(f2,u,fa,c);
}
}
queue<int> q;q.push(s);v1[s]=1;
while(!q.empty())
{
int u=q.front();q.pop();v1[u]=1;
for(int i=f1[u];i;i=e[i].next)
if(!v1[e[i].v]) q.push(e[i].v);
}
q.push(t);v2[t]=1;
while(!q.empty())
{
int u=q.front();q.pop();v2[u]=1;
for(int i=f2[u];i;i=e[i].next)
if(!v2[e[i].v]) q.push(e[i].v);
}
for(int i=1;i<=m;i++) if(a[i]>0)
{
int u=a[i];
if(v1[u] && v2[u]) ans+=num[u];
for(int j=f1[u];j;j=e[j].next)
if(v1[u] && v2[e[j].v]) ans+=e[j].c;
}
write(ans);puts("");tot=0;
for(int i=1;i<=m;i++) if(a[i]>0)
vis[a[i]]=v1[a[i]]=v2[a[i]]=
f1[a[i]]=f2[a[i]]=0;
}
signed main()
{
n=read();m=read();q=read();k=read();
for(int i=1;i<=m;i++)
{
int u=read(),v=read();
G1[u].pb(v);
}
build();
while(q--) work();
}
量子通信
题目描述
解法
我更倾向于把本题看成构造,用到的技巧就是:分析特殊数值的含义。
本题最重要的数值就是 \(15(0\leq k\leq 15)\) 和 长度 \(256\),考虑到 \(256=16\times 16\),并且 \(16=15+1\),于是性质就呼之欲出了:如果我们把字典里的串分成长度都为 \(16\) 的 \(16\) 块,那么如果答案为 \(1\),询问串和字典串一定存在相等的块。
那么利用存在块相等的性质来枚举,由于数据随机,单次询问期望判断字典串的个数是 \(\frac{n}{2^{16}}\times 16\approx 100\),判断单个字典串只需要做 \(4\) 次(压缩成 \(\tt ull\) 之后利用 __builtin_popcountll
函数),那么时间复杂度 \(O(m\times 400)\)
感觉完全不需要卡常啊,感觉慢的原因应该在 \(\tt vector\)
#include <cstdio>
#include <vector>
#include <iostream>
using namespace std;
const int N = 400005;
typedef unsigned long long ull;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
int n,m,la,a[N][16];vector<int> v[16][70005];
bool s[N][256];ull a1,a2,b[N][4];char t[70];
ull myRand(ull &k1,ull &k2)
{
ull k3=k1,k4=k2;
k1=k4;k3^=(k3<<23);
k2=k3^k4^(k3>>17)^(k4>>26);
return k2+k4;
}
void gen(int n,ull a1,ull a2)
{
for(int i=1;i<=n;i++)
for(int j=0;j<256;j++)
s[i][j]=(myRand(a1,a2)&(1ull<<32))?1:0;
}
int work()
{
scanf("%s",t);ull z[4]={};
int k=read(),o[16]={},f=la?15:0;
for(int i=0;i<64;i++)
{
int c=0;
if('0'<=t[i] && t[i]<='9') c=t[i]-'0';
if('A'<=t[i] && t[i]<='F') c=t[i]-'A'+10;
c^=f;
o[i/4]=(o[i/4]<<4)|c;
z[i/16]=(z[i/16]<<4)|c;
}
for(int i=0;i<16;i++)
{
for(int &x:v[i][o[i]])
{
int cnt=0;
for(int j=0;j<4;j++)
cnt+=__builtin_popcountll(z[j]^b[x][j]);
if(cnt<=k) return 1;
}
}
return 0;
}
signed main()
{
n=read();m=read();cin>>a1>>a2;
gen(n,a1,a2);
for(int i=1;i<=n;i++)
{
for(int j=0;j<256;j++)
{
a[i][j>>4]=(a[i][j>>4]<<1)|s[i][j];
b[i][j>>6]=(b[i][j>>6]<<1)|s[i][j];
}
for(int j=0;j<16;j++)
v[j][a[i][j]].push_back(i);
}
for(int i=1;i<=m;i++)
printf("%d\n",la=work());
}
密码箱
题目描述
解法
可以把每个 \(a_i\) 都看成作用于 \(\frac{x}{y}\) 的函数,那么 \(f_i(\frac{x}{y})=\frac{a_i\cdot x+y}{x}\)
由于这个函数是线性变换可以写成矩阵,注意这里我们是主动使用矩阵,而不是观察出了什么性质,虽然矩阵并没有简便计算但它是一种易于维护的形式。那么初始是向量 \((1\ \ 0)\),我们按照操作序列从右往左地右乘矩阵:\(\begin{bmatrix}a_i & 1\\1 & 0\end{bmatrix}\)
字符 W
就相当于在末尾添加矩阵 \(\begin{bmatrix}1&1\\1&0\end{bmatrix}\)
至于字符 E
,通过计算可以发现只需要把最后一项减 \(1\),然后再添加两个 \(1\),那么在末尾添加矩阵:
剩下的问题就变成维护操作序列了,我们只需要维护这四种乘积:\(A_nA_{n-1}...A_1/A_1...A_{n-1}A_n/B_nB_{n-1}...B_1/B_1...B_{n-1}B_n\),直接 treap
开写,时间复杂度 \(O(n\log n)\)
总结
遇事不决,矩阵乘法!
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <ctime>
using namespace std;
const int M = 200005;
const int MOD = 998244353;
int read()
{
int x=0,f=1;char c;
while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;}
while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();}
return x*f;
}
void write(int x)
{
if(x>=10) write(x/10);
putchar(x%10+'0');
}
int n,m,rt,cnt,hp[M],fl[M],rv[M],val[M],ls[M],rs[M],siz[M];
struct node {int p[2];node(){p[0]=p[1]=0;}};char s[M];
struct mat
{
int a[4];
mat() {a[0]=a[1]=a[2]=a[3]=0;}
mat operator * (const mat &b) const
{
mat r;
//(0,0) = (0,0) * (0,0) + (0,1) * (1,0)
r.a[0]=(1ll*a[0]*b.a[0]+1ll*a[1]*b.a[2])%MOD;
//(0,1) = (0,0) * (0,1) + (0,1) * (1,1)
r.a[1]=(1ll*a[0]*b.a[1]+1ll*a[1]*b.a[3])%MOD;
//(1,0) = (1,0) * (0,0) + (1,1) * (1,0)
r.a[2]=(1ll*a[2]*b.a[0]+1ll*a[3]*b.a[2])%MOD;
//(1,1) = (1,0) * (0,1) + (1,1) * (1,1)
r.a[3]=(1ll*a[2]*b.a[1]+1ll*a[3]*b.a[3])%MOD;
return r;
}
}W,E,a[M],fa[M],b[M],fb[M];
void up(int x)
{
mat u=(val[x]==0)?W:E;
mat v=(val[x]==0)?E:W;
a[x]=a[rs[x]]*u*a[ls[x]];
b[x]=b[ls[x]]*u*b[rs[x]];
fa[x]=fa[rs[x]]*v*fa[ls[x]];
fb[x]=fb[ls[x]]*v*fb[rs[x]];
siz[x]=siz[ls[x]]+siz[rs[x]]+1;
}
void reve(int x)
{
if(!x) return ;rv[x]^=1;
swap(a[x],b[x]);swap(fa[x],fb[x]);
swap(ls[x],rs[x]);
}
void flip(int x)
{
if(!x) return ;fl[x]^=1;val[x]^=1;
swap(a[x],fa[x]);swap(b[x],fb[x]);
}
void down(int x)
{
if(!x) return ;
if(fl[x]) flip(ls[x]),flip(rs[x]),fl[x]=0;
if(rv[x]) reve(ls[x]),reve(rs[x]),rv[x]=0;
}
node split(int x,int s)
{
node y;
if(!x) return y;
down(x);
if(siz[ls[x]]>=s)
{
y=split(ls[x],s);
ls[x]=y.p[1];y.p[1]=x;
up(x);return y;
}
y=split(rs[x],s-siz[ls[x]]-1);
rs[x]=y.p[0];y.p[0]=x;
up(x);return y;
}
int merge(int x,int y)
{
if(!x || !y) return x+y;
if(hp[x]>=hp[y])
{
down(x);rs[x]=merge(rs[x],y);
up(x);return x;
}
down(y);ls[y]=merge(x,ls[y]);
up(y);return y;
}
void Add(int c)
{
int x=++cnt;hp[x]=rand();val[x]=c;
up(x);rt=merge(rt,x);
}
void Flip(int l,int r)
{
node x=split(rt,l-1);
node y=split(x.p[1],r-l+1);
flip(y.p[0]);
rt=merge(x.p[0],merge(y.p[0],y.p[1]));
}
void Reverse(int l,int r)
{
node x=split(rt,l-1);
node y=split(x.p[1],r-l+1);
reve(y.p[0]);
rt=merge(x.p[0],merge(y.p[0],y.p[1]));
}
void zxy()
{
mat z;z.a[0]=z.a[1]=z.a[3]=1;z=a[rt]*z;
write(z.a[0]);putchar(' ');
write(z.a[1]);puts("");
}
signed main()
{
srand(time(0));
n=read();m=read();scanf("%s",s+1);
a[0].a[0]=a[0].a[3]=1;
b[0]=fa[0]=fb[0]=a[0];
W.a[0]=W.a[1]=W.a[3]=1;
E.a[0]=2;E.a[1]=MOD-1;E.a[2]=1;
for(int i=1;i<=n;i++) Add(s[i]=='E');
zxy();char t[5]={};
for(int i=1;i<=m;i++)
{
scanf("%s",s);
if(s[0]=='A')
{
scanf("%s",t);
Add(t[0]=='E');
}
else
{
int l=read(),r=read();
if(s[0]=='F') Flip(l,r);
if(s[0]=='R') Reverse(l,r);
}
zxy();
}
}