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

    量子通信

    题目描述

    点此看题

    解法

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

    本题最重要的数值就是 \(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();
    	}
    }
    
  • 相关阅读:
    131. 分割回文串
    博客开通第二十七天
    博客开通第三十八天
    博客开通第56天
    博客开通第三十三天
    博客开通第61天
    博客开通第62天
    博客开通第二十六天
    博客开通第四十七天
    博客开通第63天
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16218568.html
Copyright © 2020-2023  润新知