2021.05.04【NOIP提高B组】模拟 总结

T1

题目大意, \(S_{i,j}=\sum_{k=i}^j a_k\) ,求 \(ans=\min\{ S_{i,j}\mod P|S_{i,j}\mod P\ge K \}\)

其中 \(i\le j\)\(\{ S_{i,j}\mod P|S_{i,j}\mod P\ge K \}\) 非空

\(s_i\) 是取模的前缀和,显然是要满足条件的 \((s_j-s_i)\mod P\) 最小

对于一个 \(s_j\) ,要 \(s_i\) 最大,变成一个查询前驱的问题

但是有限制,考虑分类讨论

  1. \(s_j\ge s_i\) 时原式变为 \(s_j-s_i\) ,要满足要求, \(s_i\) 最大是 \(s_j-K\),直接查询 \(s_j-K\) 的前驱

    原因:\(s_j-(s_j-K)=K\ge K\)

  2. \(s_j<s_i\) 时原式变为 \(s_j-s_i+P\) ,要满足要求, \(s_i\) 最大是 \(s_j-K+P\),查询 \(s_j-K+P\) 的前驱

查询完,将 \(s_i\) 插入

至于查询前驱,和普通的平衡树有一点区别:不严格前驱

#include<bits/stdc++.h>
using namespace std;
const int N=100005,INF=100000000;
int n,K,P,ans=INF,s[N];
int cnt[N],val[N],sz[N],fa[N],ch[N][2],root,tot;
inline int Get(int x) { return ch[fa[x]][1]==x; }
inline void Up(int x) { sz[x]=cnt[x]+sz[ch[x][0]]+sz[ch[x][1]]; }
inline void Rot(int x) {
	register int y=fa[x],z=fa[y],k=Get(x);
	ch[z][Get(y)]=x,fa[x]=z;
	ch[y][k]=ch[x][k^1],fa[ch[x][k^1]]=y;
	ch[x][k^1]=y,fa[y]=x;
	Up(y),Up(x);
}
inline void Splay(int x,int goal=0) {
	for(int y;(y=fa[x])^goal;Rot(x))
	if(fa[y]^goal)Rot(Get(x)^Get(y)?x:y);
	if(!goal)root=x;
}
inline void Find(int x) {
	if(!root)return;
	register int u=root;
	while(ch[u][x>val[u]] && x^val[u])
		u=ch[u][x>val[u]];
	Splay(u);
}
inline void Ins(int x) {
	register int u=root,fu=0;
	while(u && val[u]^x)fu=u,u=ch[u][x>val[u]];
	if(u)cnt[u]++;
	else {
		u=++tot;
		if(fu)ch[fu][x>val[fu]]=u;
		ch[u][0]=ch[u][1]=0;
		fa[u]=fu,val[u]=x;
		cnt[u]=sz[u]=1;
	}
	Splay(u);
}
inline int Pre(int x) {
	register int u=root,mx=-INF;
	while(1) {
		if(!u)return mx;
		if(val[u]>x)u=ch[u][0];
		else mx=max(mx,val[u]),u=ch[u][1];
	}
}
int main() {
	scanf("%d%d%d",&n,&K,&P);
	Ins(0);
	for(int i=1;i<=n;i++)
		scanf("%d",s+i),
		(s[i]+=s[i-1])%=P;
	for(int i=1,xx,yy;i<=n;i++) {
		xx=Pre(s[i]-K);
		yy=Pre(s[i]-K+P);
//		printf("%d %d\n",xx,yy);
		ans=min(ans,min(s[i]-xx,s[i]-yy+P));
		Ins(s[i]);
	}
	printf("%d",ans);
}

T2

\(n\)\(D\) 维的点,两点 \(A(x_1,x_2,\cdots,x_D),B(X_1,X_2,\cdots,X_D)\) 曼哈顿距离是 \(\sum_{i=1}^D |x_i-X_i|\)

问两点间的最大距离

60

由于 \(D=2\) ,将点放入平面直角坐标系,求的是 \(\max\{ |x_i-x_j|+|y_i-y_j|\}\)

取绝对值,又四种情况

\(+x_i-x_j+y_i-y_j\)

\(+x_i-x_j-y_i+y_j\)

\(-x_i+x_j+y_i-y_j\)

\(-x_i+x_j-y_i+y_j\)

再合并一下

\((+x_i+y_i)+(-x_j-y_j)\)

\((+x_i-y_i)+(-x_j+y_j)\)

\((-x_i+y_i)+(+x_j-y_j)\)

\((-x_i-y_i)+(+x_j+y_j)\)

发现答案就在之中,

于是分类讨论两个括号,相加取 \(\max\) 即可

100

将上面算法改一下即可:将分类讨论变成 \(O(2^D)\) 的全排列

排列同一个点的符号,然后记录对于所有点每一种排列的最大、最小值

最后最大-最小取最大即可


还可以更快的优化:

因为 \(x_i-x_j=-x_i-(-x_j)\)

所以没有必要枚举第一个系数

直接定为 1

总复杂度 \(O(nD\times 2^{D-1})\)


#include<bits/stdc++.h>
using namespace std;
const int N=1000005,INF=100000000;
int n,D,up,tot,x[6],mx[40],mn[40],ans;
void dfs(int st,int v) {
	if(st>D) {
		tot++;
		mx[tot]=max(mx[tot],v);
		mn[tot]=min(mn[tot],v);
		return;
	}
	dfs(st+1,v+x[st]);
	dfs(st+1,v-x[st]);
}
int main() {
	scanf("%d%d",&n,&D);
	up=1<<D;
	for(int i=1;i<=up;i++)
		mx[i]=-INF,mn[i]=+INF;
	n=0;
	while(scanf("%d",&x[1])!=EOF) {
		for(int j=2;j<=D;j++)
			scanf("%d",&x[j]);
		tot=0,dfs(2,x[1]);
	}
	for(int i=1;i<=up;i++)
		ans=max(ans,mx[i]-mn[i]);
	printf("%d",ans);
}

T3

\(\dfrac{1}{x}+\dfrac{1}{y}=\dfrac{1}{n}\) 的本质不同的解的个数

\[\begin{aligned} \dfrac{1}{x}+\dfrac{1}{y} &= \dfrac{1}{n} \\ nx+ny &= xy \\ n^2 &= n^2-n(x+y)+xy \\ (x-n)(y-n) &= n^2 \end{aligned} \]

所以,只要 \((x-n)\)\((y-n)\)\(n^2\) 的约数,就是一个解

于是分解质因数求 \(n^2\) 因数个数,注意

  1. 求的是 \(n^2\) 得因数个数,对于 \(n\) 得一个质因数次数要乘二
  2. 如果到最后 \(n>1\) 说明还有一个质因数,答案乘 3
  3. 要求本质不同的解,即 \(\lceil \dfrac{ans}{2} \rceil\)
#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
LL n,ans=1,cnt;
int main() {
	scanf("%lld",&n);
	for(LL i=2;i<=sqrt(n);i++) {
		if(n%i==0) {
			cnt=0;
			while(n%i==0)
				n/=i,++cnt;
			ans*=cnt<<1|1;
		}
	}
	if(n>1)ans*=3;
	printf("%lld",ans+1>>1);
}

T4

题目大意: 对 \(n\times m\) 得地图染色,要求每一个 \(2\times 2\) 得小方阵内颜色不能相同

问方案数模 \(P\)\(n\le 10^{100},m\le 5\)

\(m\le 5\),显然的状压 dp

50

\(f_{i,s}\) 为前 \(i-1\) 行放满,第 \(i\) 行状态为 \(s\) 得方案

\(f_{i,s}=\sum f_{i-1,t}\) ,其中 \(s,t\) 为一组合法状态

复杂度 \(O(n\times 2^m\times 2^m)\) 期望 50

100

然后转移是固定的,设 \(g_{s,t}=1\),其中 \(s,t\) 为一组合法状态

\(f_i\) 变成一个 \(2^m\times2^m\) 的矩阵

可得 \(f_{i}=f{i-1}\times g\)

\(f(n)=f(1)\times g^{n-1}\)

\(f(1)\) 就第一行是 1,其余是 0

所以用矩阵快速幂,最后输出矩阵中所有元素和

#include<bits/stdc++.h>
using namespace std;
typedef long long LL;
int len,tn,m,P,up,p[20],sum,n[200],ck[40][40];
char s[200];
inline bool chk(int S,int T) {
	for(int i=0;i<m-1;i++) {
		if(!(S&p[i]) && !(S&p[i+1]) && !(T&p[i]) && !(T&p[i+1]))return 0;
		if((S&p[i]) && (S&p[i+1]) && (T&p[i]) && (T&p[i+1]))return 0;
	}
	return 1;
}
struct mat {
	int a[40][40];
	mat() { memset(a,0,sizeof(a)); }
	inline mat operator*(mat x) {
		register mat res;
		for(int i=0;i<=up;i++)
			for(int j=0;j<=up;j++)
				for(int k=0;k<=up;k++)
					(res.a[i][j]+=a[i][k]*x.a[k][j])%=P;
		return res;
	}
}ans,tmp;
int main() {
	for(int i=0;i<=10;i++)p[i]=1<<i;
	scanf("%s%d%lld",s+1,&m,&P);
	len=strlen(s+1),up=(1<<m)-1;
	for(int i=1;i<=len;i++)n[i]=s[len-i+1]^48;
	n[1]--;
	for(int i=1;i<=len;i++) {
		if(n[i])break;
		n[i]+=10,--n[i+1]; 
	}
	while(!n[len] && len)--len;
	for(int j=0;j<=up;j++)
		for(int k=0;k<=up;k++)
			tmp.a[j][k]=chk(j,k);
	for(int i=0;i<=up;i++)ans.a[1][i]=1; 
	while(len) {
		if(n[1]&1)ans=ans*tmp;
		tmp=tmp*tmp;
		for(int i=len;i>1;i--)
			n[i-1]+=10*(n[i]&1),n[i]>>=1;
		n[1]>>=1;
		while(!n[len] && len)--len;
	}
	for(int i=0;i<=up;i++)
		for(int j=0;j<=up;j++)
			(sum+=ans.a[i][j])%=P;
	printf("%d",sum);
}

总结

  1. 对于取模的分类讨论
  2. 对于绝对值的分类讨论
  3. 因式分解
  4. 转移固定就用矩阵乘
posted @ 2021-05-04 21:58  小蒟蒻laf  阅读(53)  评论(0编辑  收藏  举报