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\),那么在末尾添加矩阵:

\[\begin{bmatrix}1&1\\1&0\end{bmatrix}\times \begin{bmatrix}1&1\\1&0\end{bmatrix}\times \begin{bmatrix}1&-1\\0&1\end{bmatrix}=\begin{bmatrix}2&-1\\1&0\end{bmatrix} \]

剩下的问题就变成维护操作序列了,我们只需要维护这四种乘积:\(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();
	}
}
posted @ 2022-05-03 15:56  C202044zxy  阅读(292)  评论(0编辑  收藏  举报