• [做题笔记] namespace_std 的杂题选讲


    Latin Square

    题目描述

    点此看题

    解法

    考虑所有操作都是整体操作,那么维护整体标记,本题的核心是考察整体操作对单点的影响

    发现 LRUD 这四个操作对于单点都是独立的,所以可以通过维护偏移量的方式做到。但是 IC 这个对于排列取逆的操作似乎不是独立的,单看每个元素的位置变化是混乱的。

    我们想独立化这个操作,考虑取逆操作的另一种描述形式:三维平面上有 \(n^2\) 个坐标 \((i,j,p)\),对行取逆就是交换坐标的第二维和第三维;对列取逆就是交换坐标的第一维和第三维。

    那么我们再维护表示维护交换的整体标记即可,时间复杂度 \(O(n^2+m)\)

    总结

    想要维护整体标记时,首先把整体操作对单点的影响独立开来。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 1005;
    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,o[3],f[3],p[3],a[M][M][3],b[M][M];
    char s[100005];
    void work()
    {
    	n=read();m=read();
    	for(int i=0;i<3;i++) f[o[i]=i]=0;
    	for(int i=0;i<n;i++) for(int j=0;j<n;j++)
    	{
    		a[i][j][2]=read()-1;
    		a[i][j][0]=i;a[i][j][1]=j;
    	}
    	scanf("%s",s+1);
    	for(int i=1;i<=m;i++)
    	{
    		if(s[i]=='L') f[1]--;
    		if(s[i]=='R') f[1]++;
    		if(s[i]=='U') f[0]--;
    		if(s[i]=='D') f[0]++;
    		if(s[i]=='I') swap(o[1],o[2]),swap(f[1],f[2]);
    		if(s[i]=='C') swap(o[0],o[2]),swap(f[0],f[2]);
    	}
    	for(int i=0;i<3;i++) f[i]=(f[i]%n+n)%n;
    	for(int i=0;i<n;i++) for(int j=0;j<n;j++)
    	{
    		for(int k=0;k<3;k++)
    			p[k]=(a[i][j][o[k]]+f[k])%n;
    		b[p[0]][p[1]]=p[2];
    	}
    	for(int i=0;i<n;i++,puts(""))
    		for(int j=0;j<n;j++)
    			printf("%d ",b[i][j]+1);
    }
    signed main()
    {
    	T=read();
    	while(T--) work();
    }
    

    一流团子师傅

    题目描述

    点此看题

    解法

    当时我写的 这篇文章 真是有用啊,本题所有技巧都可以从那题迁移过来。

    考虑逐步增加方便利用的已知信息,一个显然的想法是依次插入每个位置,动态维护 \(n\) 个列表,满足其中每个元素都互不相同。设当前插入元素是 \(x\),那么我们应该把它插入到哪个列表中呢?

    有一个暴力的想法是依次检查每个列表,询问 \(x\) 和列表中元素的并。如果这个并集中元素的最大出现次数 \(\leq 1\),说明 \(x\) 没有在这个列表中出现。询问一个集合元素最大出现次数的方法是,用 \(m\) 减去其补集的询问结果。

    询问次数 \(n\cdot m^2\),有点不能接受。不妨把这个过程套上二分,我们询问 \(x\)\([l,mid]\) 列表中元素的并。如果这个并集中元素的最大出现次数 \(\leq mid-l+1\),存在一个列表使得 \(x\) 没有在其中出现过。那么二分到 \(l=r\) 时,我们直接把 \(x\) 插入到列表 \(l\) 中就是合法的。

    询问次数 \(n\cdot m\log m\),正好对应得上 \(5\cdot 10^4\) 的询问次数。

    不过真的不要再迫害团子啦,我每次看到 JOI 的这种制作团子题目都要 ptsd

    #include "dango3.h"
    #include <bits/stdc++.h>
    using namespace std;
    bool vis[10005];int n,m;
    vector<int> v[30],t;
    int qry()
    {
    	t.clear();
    	for(int i=1;i<=n*m;i++)
    		if(!vis[i]) t.push_back(i);
    	for(int i=1;i<=n*m;i++) vis[i]=0;
    	return Query(t);
    }
    void work(int l,int r,int x)
    {
    	if(l==r)
    	{
    		v[l].push_back(x);
    		return ;
    	}
    	int mid=(l+r)>>1;vis[x]=1;
    	for(int i=l;i<=mid;i++) for(int x:v[i])
    		vis[x]=1;
    	int h=m-qry();
    	if(h<=mid-l+1) work(l,mid,x);
    	else work(mid+1,r,x);
    }
    void Solve(int N,int M)
    {
    	n=N;m=M;
    	for(int i=1;i<=n*m;i++) work(1,m,i);
    	for(int i=1;i<=m;i++) Answer(v[i]);
    }
    

    Tiles for Bathroom

    题目描述

    点此看题

    解法

    考虑求出以 \((i,j)\) 为右下角的,切比雪夫距离第 \(x\) 小的点 \(c_{i,j,x}\)(颜色相同的保留距离最小的那个),这样定义是因为第 \(q+1\) 小的切比雪夫距离减 \(1\) 描述了向右上延伸的最大长度。

    快速求出 \(c_{i,j,x}\) 需要充分利用以前计算过的信息,发现我们可以从 \(c_{i-1,j},c_{i,j-1},c_{i-1,j-1}\) 这三者归并上来(有重复的点没关系,因为相同颜色的点最后只会保留一个)

    按切比雪夫距离归并排序 \(O(n^2q)\),当然暴力排序也是可以通过的。

    #include <cstdio>
    #include <vector>
    #include <iostream>
    #include <algorithm>
    using namespace std;
    const int M = 1505;
    #define pii pair<int,int>
    #define pb push_back
    #define fi first
    #define se second
    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,a[M][M],b[M],v[M*M];
    vector<pii> c[M][M],t;
    void get(int x,int y)
    {
    	t.clear();
    	t.pb({1,read()});
    	for(auto i:c[x-1][y]) t.pb({i.fi+1,i.se});
    	for(auto i:c[x][y-1]) t.pb({i.fi+1,i.se});
    	for(auto i:c[x-1][y-1]) t.pb({i.fi+1,i.se});
    	sort(t.begin(),t.end());
    	for(auto i:t)
    	{
    		if(v[i.se] || c[x][y].size()>m) continue;
    		c[x][y].pb(i);v[i.se]=1;
    	}
    	for(auto i:t) v[i.se]=0;
    	t.clear();
    }
    signed main()
    {
    	n=read();m=read();
    	for(int i=1;i<=n;i++)
    		for(int j=1;j<=n;j++)
    		{
    			get(i,j);
    			int p=min(i,j);
    			if(c[i][j].size()>m)
    				p=min(p,c[i][j][m].fi-1);
    			b[p]++;
    		}
    	for(int i=n;i>=1;i--) b[i]+=b[i+1];
    	for(int i=1;i<=n;i++) printf("%d\n",b[i]);
    }
    

    Resurrection

    题目描述

    点此看题

    解法

    引理:\(G\) 能被生成,当且仅当 \(G\) 中的所有点 \(u\) 的父亲 \(f_p\) 都是 \(T\) 上的祖先,且不存在两条链 \((x,f_x)\)\((y,f_y)\) 相交且不包含。必要性显然,下证充分性。

    设点数 \(<n\)\(G\) 都能被构造出来,我们现在证明任意点数 \(=n\) 的点 \(G\) 都可以被构造。

    如果 \(n=1\),结论显然成立。

    如果 \(n>1\),那么点 \(n\)\(G\) 上至少有一个儿子。我们取 \(n\)\(T\) 中最深的那个儿子 \(x\),那么边 \((x,fa_x)\) 肯定是 \(x\)\(T\) 的子树内的边中,最晚被删除的那一条。根据条件子树内不会有点连接到 \(x\)\(T\) 上的祖先。那么就划分成了两个独立的子问题,\(x\) 的子树和 \(G\) 除去 \(x\) 子树部分 分别合法,所以 \(G\) 也合法。

    可以直接用这个引理计数,设 \(f_{u,i}\) 表示 \(u\) 选择完父亲之后,\(u\) 的儿子可选的 \(u\) 祖先的个数是 \(i\) 的方案数,转移:

    \[f_{u,i}=\prod_{v\in son_u}\sum_{j=0}^{i+1} f_{v,j} \]

    如果 \(u=n\),初始化 \(f_{u,0}=1\);否则初始化 \(f_{u,1}=f_{u,2}...=f_{u,dep-1}=1\);最后的答案是 \(f_{n,0}\)

    时间复杂度 \(O(n^2)\)

    #include <cstdio>
    #include <vector>
    #include <iostream>
    using namespace std;
    const int M = 3005;
    const int MOD = 998244353;
    #define int long long
    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,f[M][M],d[M];vector<int> g[M];
    void dfs(int u,int fa)
    {
    	for(int i=(fa!=0);i<=d[u];i++) f[u][i]=1;
    	for(int v:g[u]) if(v^fa)
    	{
    		d[v]=d[u]+1;
    		dfs(v,u);
    		for(int i=1;i<=d[u]+1;i++)
    			f[v][i]=(f[v][i]+f[v][i-1])%MOD;
    		for(int i=0;i<=d[u];i++)
    			f[u][i]=f[u][i]*f[v][i+1]%MOD;
    	}
    }
    signed main()
    {
    	n=read();
    	for(int i=1;i<n;i++)
    	{
    		int u=read(),v=read();
    		g[u].push_back(v);
    		g[v].push_back(u);
    	}
    	dfs(n,0);
    	printf("%lld\n",f[n][0]);
    }
    

    Defender of Childhood Dreams

    题目描述

    点此看题

    解法

    话说这种连必要性都要归纳的构造题怎么做啊?感觉没有任何切入点啊。

    颜色数的下界是 \(\lceil \log_k n\rceil\),首先证明必要性,假设 \(n\) 个点的图有 \(c\) 染色的方法,我们尝试证明一定有 \(n\leq k^c\),即可得到上述结论,我们对 \(c\) 进行归纳。

    如果 \(c=1\),上述结论显然成立。

    如果 \(c>1\),我们不妨先考虑颜色 \(1\) 的所有边。我们把所有点 \(u\) 按照以 \(u\) 结尾的最长 \(1\) 路径长度来划分等价类,那么一个等价类内部是不能有边的。现在只考虑颜色 \(1\) 带来的限制,对于每个等价类点数不超过 \(k^{c-1}\)

    由于等价类最多有 \(k\) 个,那么可以得到 \(n=\sum|V_i|\leq k\cdot k^{c-1}=k^c\),但是其他颜色在等价类之间的边我们并没有考虑,这些边也有可能带来限制的。那我们考虑放宽限制,因为考虑了这些限制点数只会变少,所以 \(n\leq k^c\) 仍然成立。

    构造方法就蕴含在证明中,我们把所有点均分成 \(k\) 个等价类递归下去,回溯上来时每个等价类都用 \(1\) 边相连。时间复杂度 \(O(n^2)\),这种构造方法能取到下界的原因是一直没有增添其他颜色边的限制。

    #include <cstdio>
    #include <iostream>
    using namespace std;
    const int M = 1005;
    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,k,mx,a[M][M];
    void solve(int l,int r,int c)
    {
    	if(l==r) return ;
    	mx=max(mx,c);
    	int s=(r-l+k)/k;
    	for(int i=l;i<=r;i+=s)
    	{
    		int h=min(i+s-1,r);
    		solve(i,h,c+1);
    		for(int j=l;j<i;j++)
    			for(int k=i;k<=h;k++)
    				a[j][k]=c;
    	}
    }
    signed main()
    {
    	n=read();k=read();
    	solve(1,n,1);
    	printf("%d\n",mx);
    	for(int i=1;i<=n;i++)
    		for(int j=i+1;j<=n;j++)
    			printf("%d ",a[i][j]);
    }
    
  • 相关阅读:
    C. Chessboard( Educational Codeforces Round 41 (Rated for Div. 2))
    B. Lecture Sleep( Educational Codeforces Round 41 (Rated for Div. 2))
    51Nod 1256 乘法逆元(扩展欧几里得)
    C
    B
    9.13 web基础知识
    web基础知识
    9.11 web基础知识
    9.10 web基础知识
    web 基础知识
  • 原文地址:https://www.cnblogs.com/C202044zxy/p/16400820.html
Copyright © 2020-2023  润新知