[做题笔记] 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();
}

一流团子师傅

题目描述

点此看题

解法

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

考虑逐步增加方便利用的已知信息,一个显然的想法是依次插入每个位置,动态维护 \(m\) 个列表,满足其中每个元素都互不相同。设当前插入元素是 \(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]);
}
posted @ 2022-06-22 15:31  C202044zxy  阅读(246)  评论(2编辑  收藏  举报