NOI 2021 题目选做


轻重边

题目描述

点此看题

解法

可以转化成染色模型,修改就是将路径染上一种新颜色,查询就是问路径上同色相邻点对个数。

直接上树剖即可,时间复杂度 \(O(n\log^2n)\)本题实现的最大难点就是多测

总结

有区间赋值特性的题目中,可以考虑往染色模型上转化。

#include 
#include 
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=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

量子通信

题目描述

点此看题

解法

我更倾向于把本题看成构造,用到的技巧就是:分析特殊数值的含义

本题最重要的数值就是 \(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 
#include 
#include 
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 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 
#include 
#include 
#include 
#include 
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();
	}
}

相关