提高组数学专题 1

提高组数学专题 1

T1 [CF1909F1] Small Permutation Problem (Easy Version)

将排列的每项 \(p_i\) 记成 \((i,p_i)\) 的形式,则问题转化为:在一个 \(n\times n\) 的棋盘上放置 \(n\) 个車,使这些車互不攻击,且满足题目中 \(a\) 的限制。

题目中 \(a_i\) 的限制实际上就是限制了左上角的 \(i\times i\) 棋盘中車的数量,考虑与 \(a_{i-1}\) 作差,则进一步转化为包含 \((i,i)\) 的一个 “L 形” 区域的車的数量。结合每行每列都只有一个車,则 \(a_i-a_{i-1}\) 的取值只可能有 \(0,1,2\) 三种情况,否则必无解。

设一个 \(k\) 表示当前空下来的能放車的行列数,对于 \(\mathit{ans}\) 的计算有三种情况:

  • \(a_i-a_{i-1}=0\),说明这一行列空下了,\(k\gets k+1\)
  • \(a_i-a_{i-1}=1\),说明要么在空下的 \(k\) 行或 \(k\) 列放,要么在 \((i,i)\) 放,\(\mathit{ans}\gets\mathit{ans}\times(2k+1)\)\(k\) 不变;
  • \(a_i-a_{i-1}=2\),说明必须在空下的 \(k\) 行和 \(k\) 列各放一个,则 \(\mathit{ans}\gets\mathit{ans}\times k^2\),然后 \(k\gets k-1\)
#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
using namespace std;

char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
template<typename T>
void write(T x,char sf='\n'){
	if(x<0) putchar('-'),x=~x+1;
	int top=0;
	do str[top++]=x%10,x/=10;while(x);
	while(top) putchar(str[--top]+48);
	if(sf^'#') putchar(sf);
}
constexpr int MAXN=2e5+5,MOD=998244353;
int T,n,a[MAXN];
bool fool(){
	for(int i=1;i<=n;++i) if(a[i]>i||a[i]-a[i-1]>2) return 1;
	return 0;
}

int main(){
	T=read();
	while(T--){
		n=read();
		for(int i=1;i<=n;++i) a[i]=read();
		if(a[n]^n||fool()){
			write(0);
			continue;
		}
		long long ans=1,k=0;
		for(int i=1;i<=n;++i)
			switch(a[i]-a[i-1]){
				case 0: ++k; break;
				case 1: ans=ans*(k<<1|1)%MOD; break;
				default:ans=ans*k%MOD*k%MOD;--k; break;
			}
		write(ans);
	}
	return fw,0;
}

T2 [CF1909E] Multiple Lamps

这题的 \(\lfloor n/5\rfloor\) 非常 bug,我们一时想不出对应的维护方法。

但仔细一想就可以发现,这个类似素数筛一样的开灯规则使得如果挨个开灯,最后亮着的一定是完全平方数。而 \(n\) 以内的完全平方数数量在 \(n\ge20\) 时永远是 \(\le\lfloor n/5\rfloor\) 的,所以对于 \(n\ge20\) 直接挨个输出 \(1\sim n\) 即可。同理,显然如果 \(n\le4\) 则无解。

于是此题原本 \(n\le2\times10^5\) 的范围一下子缩小到了 \(5\le n\le19\)

数据范围小了许多,我们可以暴力枚举状态。具体地,预处理 \(n\in[5,19]\) 时所有满足条件的开灯顺序。对于每组 \(m\),检查所有的可能答案中是否有满足 \(m\) 条限制的答案,如果有直接输出。

预处理复杂度是 \(O(n2^nn\log\log n)=O(2^nn^2\log\log n)\),检查复杂度是 \(O(\text{siz}(\mathit{ans}_n)\times m)\) 的。将 \(n=19\) 代入发现不到 \(2\times10^8\),可以通过本题。

#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
using namespace std;

char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
template<typename T>
void write(T x,char sf='\n'){
	if(x<0) putchar('-'),x=~x+1;
	int top=0;
	do str[top++]=x%10,x/=10;while(x);
	while(top) putchar(str[--top]+48);
	if(sf^'#') putchar(sf);
}
constexpr int MAXN=2e5+5;
int T,n,m;
struct{
	int x,y;
}a[MAXN];
vector<int>ans[20];

int main(){
	for(int i=5,B;i<=19;++i){
		B=1<<i;
		for(int s=1,sta;s<B;++s){
			sta=0;
			for(int j=1;j<=i;++j){
				if(!(s&(1<<(j-1)))) continue;
				for(int k=j;k<=i;k+=j) sta^=1<<(k-1);
			}
			if(__builtin_popcount(sta)*5<=i) ans[i].emplace_back(s);
		}
	}
	T=read();
	while(T--){
		n=read(),m=read();
		for(int i=1;i<=m;++i) a[i]={read()-1,read()-1};
		if(n<5){
			write(-1);
			continue;
		}else if(n>19){
			write(n);
			for(int i=1;i<=n;++i) write(i,' ');
			putchar('\n');
			continue;
		}
		for(auto s:ans[n]){
			for(int i=1;i<=m;++i) if(s&1<<a[i].x&&!(s&1<<a[i].y)) goto sht;
			write(__builtin_popcount(s));
			for(int j=0;j<n;++j) if(s&1<<j) write(j+1,' ');
			putchar('\n');
			goto byby;
			sht:;
		}
		write(-1);
		byby:;
	}
	return fw,0;
}

T3 [HAOI2017] 方案数

能转移当且仅当后者的 \(\tt1\) 的位置完全覆盖前者,且 \(\tt1\) 的数量大于等于前者。下文中以 \(\operatorname{pc}(x)\) 代表 \(x\) 的二进制中 \(\tt1\) 的数量。

对于这种我们不好考虑 “不经过” 的问题,我们常见的 trick 是容斥。于是对于一点 \(P(x,y,z)\),我们设 \(\mathit{dp}_{i,j,k}\) 表示 \(\operatorname{pc}(x)=i,\operatorname{pc}(y)=j,\operatorname{pc}(z)=k\) 的方案数。因为一步只能变一维坐标,于是有转移方程

\[\mathit{dp}_{i,j,k}=\sum_{l=0}^{i-1}\mathit{dp}_{l,j,k}\times\binom il+\sum_{l=0}^{j-1}\mathit{dp}_{i,l,k}\times\binom jl+\sum_{l=0}^{k-1}\mathit{dp}_{i,j,l}\times\binom kl \]

然后考虑障碍点的情况。先将障碍点按字典序排序以保证无后效性。设 \(f_i\) 表示不经过其他障碍到达第 \(i\) 个障碍点的方案数,则有

\[f_i=\mathit{dp}_{\operatorname{pc}(x_i),\operatorname{pc}(y_i),\operatorname{pc}(z_i)}-\sum_{j=1}^{i-1}f_j\times\operatorname{to}(j,i) \]

其中 \(\operatorname{to}(j,i)\) 表示从 \(j\)\(i\) 的方案数。事实上,\(\mathit{dp}_{i,j,k}\) 的变化仅与下标变化有关,也就是从起点到当前点、坐标比起点坐标分别多 \(i,j,k\)\(\tt1\) 的方案数。所以如果能从 \(j\) 点到达 \(i\) 点,则有

\[\operatorname{to}(j,i)=\mathit{dp}_{\operatorname{pc}(x_i)-\operatorname{pc}(x_j),\operatorname{pc}(y_i)-\operatorname{pc}(y_j),\operatorname{pc}(z_i)-\operatorname{pc}(z_j)} \]

至此,问题解决。

#include<bits/stdc++.h>
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define int long long
#define pc __builtin_popcountll
using namespace std;

char buf[1<<20],*p1=buf,*p2=buf;
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
constexpr int MAXN=1e4+5,MOD=998244353;
int n,m,r,o;
struct P{
	int x,y,z;
	int xx,yy,zz;
	bool operator<(const P&b)const{
		if(x^b.x) return x<b.x;
		if(y^b.y) return y<b.y;
		return z<b.z;
	}
}a[MAXN];
int C[65][65];
void init(int n){
	for(int i=0;i<=n;++i){
		C[i][0]=C[i][i]=1;
		for(int j=1;j<i;++j) C[i][j]=(C[i-1][j]+C[i-1][j-1])%MOD;
	}
}
void add(int&x,int y){
	x=x+y>=MOD?x+y-MOD:x+y;
}
int sub(int x,int y){
	return x-y<0?x-y+MOD:x-y;
}
int dp[65][65][65],f[MAXN];
int to(int j,int i){
	if((a[j].x&a[i].x)^a[j].x||(a[j].y&a[i].y)^a[j].y||(a[j].z&a[i].z)^a[j].z) return 0;
	return dp[a[i].xx-a[j].xx][a[i].yy-a[j].yy][a[i].zz-a[j].zz];
}

signed main(){
	n=read(),m=read(),r=read(),o=read();
	for(int i=1;i<=o;++i){
		a[i]={read(),read(),read(),0,0,0};
		a[i].xx=pc(a[i].x),a[i].yy=pc(a[i].y),a[i].zz=pc(a[i].z);
	}
	a[++o]={n,m,r,pc(n),pc(m),pc(r)};
	init(max({a[o].xx,a[o].yy,a[o].zz}));
	dp[0][0][0]=1;
	for(int i=0;i<=a[o].xx;++i)
		for(int j=0;j<=a[o].yy;++j)
			for(int k=0;k<=a[o].zz;++k){
				for(int l=0;l<i;++l) add(dp[i][j][k],dp[l][j][k]*C[i][l]%MOD);
				for(int l=0;l<j;++l) add(dp[i][j][k],dp[i][l][k]*C[j][l]%MOD);
				for(int l=0;l<k;++l) add(dp[i][j][k],dp[i][j][l]*C[k][l]%MOD);
			}
	sort(a+1,a+o+1);
	for(int i=1,tmp;i<=o;++i){
		tmp=0;
		for(int j=1;j<i;++j) add(tmp,f[j]*to(j,i)%MOD);
		f[i]=sub(dp[a[i].xx][a[i].yy][a[i].zz],tmp);
	}
	printf("%lld\n",f[o]);
	return 0;
}

T4 [CF1917E] Construct Matrix

奇妙的构造题。

首先 \(k\) 为奇数显然无解。

对于 \(k\bmod4=0\) 的情况,只需用 \(2\times2\) 的矩阵一直填即可,因为任何一行一列有两个 \(\tt1\) 都不会影响异或和。

对于 \(6\le k\le n^2-10\)\(k\bmod4=2\) 的情况,构造一个

\[\begin{array}{c|c|c|c} \tt1&\tt1&\tt0&\tt0\\\hline\tt1&\tt0&\tt1&\tt0\\\hline\tt0&\tt1&\tt1&\tt0\\\hline\tt0&\tt0&\tt0&\tt0 \end{array} \]

的矩形,然后 \(k\bmod4=0\),回到第一种情况。

对于 \(k=n^2-6\) 的情况,只需把上文矩阵改为

\[\begin{array}{c|c|c|c} \tt1&\tt1&\tt0&\tt0\\\hline\tt1&\tt0&\tt1&\tt0\\\hline\tt1&\tt1&\tt1&\tt1\\\hline\tt1&\tt0&\tt0&\tt1 \end{array} \]

之后回到第一种情况。

对于 \(k=2\)\(k=n^2-2\),这两种情况本质等价。我们只考虑 \(k=2\) 的情况,不难发现,此时只有 \(n=2\) 时有解,否则无解。

#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
using namespace std;

char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf;
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
int T,n,k,a[1005][1005];
void print(){
	for(int i=1;i<=n;++i,putchar('\n'))
		for(int j=1;j<=n;++j)
			putchar(a[i][j]?'1':'0'),putchar(' ');
}
#define Yes() putchar('Y'),putchar('e'),putchar('s'),putchar('\n')
#define No() putchar('N'),putchar('o'),putchar('\n')

int main(){
	T=read();
	while(T--){
		n=read(),k=read();
		if(k&1){
			No();
			continue;
		}
		for(int i=1;i<=n;++i) memset(a[i],0,sizeof(int)*(n+5));
		if(!k){
			Yes();
			print();
			continue;
		}else if(k==n*n){
			Yes();
			for(int i=1;i<=n;++i,putchar('\n'))
				for(int j=1;j<=n;++j)
					putchar('1'),putchar(' ');
			continue;
		}else if(k==2||k==n*n-2){
			if(n==2){
				a[1][1]=a[2][2]=1;
				Yes();
				print();
			}else No();
			continue;
		}else if(k%4==0){
			for(int i=1;i<=n;i+=2)
				for(int j=1;j<=n;j+=2){
					a[i][j]=a[i][j+1]=a[i+1][j]=a[i+1][j+1]=1;
					k-=4;
					if(!k) goto fu;
				}
			fu:;
			Yes();
			print();
			continue;
		}else if(6<=k&&k<=n*n-10&&k%4==2){
			a[1][1]=a[1][2]=a[2][1]=a[2][3]=a[3][2]=a[3][3]=1;
			k-=6;
			for(int i=1;i<=n;i+=2)
				for(int j=1;j<=n;j+=2){
					if(i<=4&&j<=4) continue;
					if(!k) goto fy;
					a[i][j]=a[i][j+1]=a[i+1][j]=a[i+1][j+1]=1;
					k-=4;
				}
			fy:;
			Yes();
			print();
			continue;
		}else if(k==n*n-6){
			for(int i=1;i<=n;++i)
				for(int j=1;j<=n;++j)
					a[i][j]=1;
			a[1][3]=a[1][4]=a[2][2]=a[2][4]=a[4][2]=a[4][3]=0;
			Yes();
			print();
			continue;
		}
	}
	return fw,0;
}

T5 [CF1891E] Brukhovich and Exams

首先修改一个数顶多能使答案 \(-2\),这是显然,也是前提。

所以我们思路的第一步肯定是先操作一次能使答案 \(-2\) 的数。不难发现,当

\[\gcd(a_{i-1},a_i)=1\land\gcd(a_i,a_{i+1})=1\land a_{i-1}\ne1\land a_{i+1}\ne1 \]

时,删除 \(a_i\) 就能使答案一次性 \(-2\)

此时还有一种能使答案一次性 \(-2\) 的情况,就是一段极长的 \(1\) 且两边的数都 \(>1\)。于是我们预处理出所有的 \(1\) 的连续段,优先队列处理即可。

剩下的都是删一个少一对的情况,无非就是首尾的连续的 \(1\) 和中间的还有数的位置。能删多少删多少即可。

#include<bits/stdc++.h>
#define fw fwrite(obuf,p3-obuf,1,stdout)
#define getchar() (p1==p2&&(p2=(p1=buf)+fread(buf,1,1<<20,stdin),p1==p2)?EOF:*p1++)
#define putchar(x) (p3-obuf<1<<20?(*p3++=(x)):(fw,p3=obuf,*p3++=(x)))
using namespace std;

char buf[1<<20],obuf[1<<20],*p1=buf,*p2=buf,*p3=obuf,str[20<<2];
int read(){
	int x=0;
	char ch=getchar();
	while(!isdigit(ch))ch=getchar();
	while(isdigit(ch))x=(x<<3)+(x<<1)+(ch^48),ch=getchar();
	return x;
}
template<typename T>
void write(T x,char sf='\n'){
	if(x<0) putchar('-'),x=~x+1;
	int top=0;
	do str[top++]=x%10,x/=10;while(x);
	while(top)putchar(str[--top]+48);
	if(sf^'#')putchar(sf);
}
constexpr int MAXN=1e5+5;
int T,n,k,a[MAXN],gd[MAXN],ans;
int s[MAXN],q[MAXN],top,len;
struct Y{
	int s,c;
	Y(int s,int c):s(s),c(c){}
	bool operator<(const Y&x)const{
		return c<x.c;
	}
};

int main(){
	T=read();
	while(T--){
		n=read(),k=read();
		for(int i=1;i<=n;++i) a[i]=read();
		a[n+1]=0;
		ans=0;
		for(int i=1;i<n;++i){
			gd[i]=__gcd(a[i],a[i+1]);
			if(gd[i]==1) ++ans;
		}
		for(int i=2;i<n;++i){
			if(!k) break;
			if(gd[i-1]==1&&gd[i]==1&&a[i-1]>1&&a[i+1]>1) 
				a[i]=0,ans-=2,gd[i-1]=gd[i]=0,--k;
		}
		if(!k){
			write(ans);
			continue;
		}
		int kl=0,kr=0;
		if(a[1]==1){
			kl=1;
			while(kl<n&&a[kl+1]==1) ++kl;
		}
		if(kl==n){
			write(n-k);
			continue;
		}
		if(a[n]==1){
			kr=1;
			int r=n;
			while(r>1&&a[r-1]==1) --r,++kr;
		}
		top=len=0;
		for(int i=kl+2;i<=n-kr-1;++i){
			if(a[i]==1){
				if(!len)s[++top]=i,q[top]=++len;
				else ++len,++q[top];
			}else len=0;
		}
		priority_queue<Y>qu;
		for(int i=1;i<=top;++i) qu.emplace(s[i],q[i]);
		while(!qu.empty()){
			if(!k) break;
			int x=qu.top().s,len=qu.top().c;
			qu.pop();
			for(int i=x;i<=x+len-1;++i){
				--k,a[i]=0;
				if(!k) break;
			}
		}
		for(int i=kl;i;--i){
			if(!k) break;
			a[i]=0,--k;
		}
		for(int i=n-kr+1;i<=n;++i){
			if(!k) break;
			a[i]=0,--k;
		}
		for(int i=1;i<n;++i){
			if(!k) break;
			if(__gcd(a[i],a[i+1])==1) a[i]=0,--k;
		}
		ans=0;
		for(int i=1;i<n;++i) if(__gcd(a[i],a[i+1])==1) ++ans;
		write(ans);
	}
	return fw,0;
}

注意细节问题!让 CF 评测一次很费时间。能让 @未来姚班zyl 说出细节有亿点多的题目。

posted @ 2024-11-15 22:05  Laoshan_PLUS  阅读(27)  评论(0)    收藏  举报