【比赛记录】CF Round 745 div2

A:

求一个排列满足 \(p_i>p_{i+1}\) 的数对大于 \(n\) 的排列数。

显然这玩意对称,直接 \(\frac{(2n)!}{2}\)

#include<bits/stdc++.h>
using namespace std;
const int N=2e5+10;
const int mod=1e9+7;
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
inline int qpow(int x,int y){
	int res=1;
	while(y){
		if(y&1)res=Mul(res,x);
		x=Mul(x,x);y>>=1; 
	}
	return res;
}
int T;
int n,m;
int main(){
	scanf("%d",&T);
	while(T--){
		cin>>n;
		n<<=1;
		int res=1;
		for(int i=1;i<=n;++i)res=Mul(res,i);
		res=Mul(res,qpow(2,mod-2));
		printf("%d\n",res);
	}	
	return 0;
}

B:

构造一张 \(n\)\(m\) 边的图满足最长路径小于 \(k-1\)

显然的梅花图构造,注意一下完全图以及不连通以及重边自环即可。

#include<bits/stdc++.h>
using namespace std;
#define int long long
const int N=2e5+10;
const int mod=998244353;
inline int Add(int x,int y) {
	return (x+y)%mod;
}
inline int Mul(int x,int y) {
	return 1ll*x*y%mod;
}
inline int qpow(int x,int y) {
	int res=1;
	while(y) {
		if(y&1)res=Mul(res,x);
		x=Mul(x,x);
		y>>=1;
	}
	return res;
}
int T;
int n,m,k;
signed main() {
	scanf("%d",&T);
	while(T--) {
		cin>>n>>m>>k;
		if(k<=1){
			puts("NO");
			continue;
		}
		if(m<n-1&&n!=1) {
			puts("NO");
			continue;
		}
		if(m>(n*(n-1)/2)){
			puts("NO");
			continue;
		}
		if(m>=n-1&&k>3) {
			puts("YES");
			continue;
		}
		if(k<=3) {
			if(k==2) {
				if(n==1) {
					puts("YES");
					continue;
				}
				puts("NO");
				continue;
			}
			if(k==3) {
				if(m==(n*(n-1))/2) {
					puts("YES");
					continue;
				} else {
					puts("NO");
					continue;
				}
			}
			if(k<=1) {
				puts("NO");
				continue;
			}
		}
	}
	return 0;
}

C:

给定矩阵求其满足四边及其左上右下角为 \(1\) 其它部分为 \(0\) 的子矩阵的最小修改次数

听说直接 \(O(n^4)\) 并判断答案是否大于 \(16\) 就直接过了……

考虑:先枚举两列再枚举一行。然后去确定剩下的一行。

倒着枚举行,然后做一个前缀最大值,这样贡献就变成用一个前缀来减去后面的最大值来得到最小贡献。

注意计算的时候要带着两个角。还有就是用前缀和来优化这个算贡献的过程,只需要维护行即可。复杂度 \(O(n^3)\)

#include<bits/stdc++.h>
using namespace std;
const int N=500;
int sum[N][N],n,m,T,a[N][N];
char ch[N];
inline int qr(int x,int y,int xx,int yy){
	return sum[xx][yy]-sum[x-1][yy]-sum[xx][y-1]+sum[x-1][y-1];
}
inline int Max(int x,int y){return x<y?y:x;}
inline int Min(int x,int y){return x<y?x:y;}
int f[N];
#define Mem(a) memset(a,0,sizeof a)
int main(){
	freopen("in.txt","r",stdin);
	scanf("%d",&T);
	while(T--){
		Mem(a);Mem(sum);
		scanf("%d%d",&n,&m);
		for(int i=1;i<=n;++i){
			scanf("%s",ch+1);
			for(int j=1;j<=m;++j)a[i][j]=ch[j]-'0';
			for(int j=1;j<=m;++j)sum[i][j]=sum[i][j-1]+a[i][j];
		}
//		for(int i=1;i<=n;++i){
//			for(int j=1;j<=n;++j){
//				sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+a[i][j];
//			}
//		}
		int ans=(1<<30);
		for(int i=1;i<=m-3;++i){
			for(int j=i+3;j<=m;++j){
				int pre=0,mx=-1e9;
				Mem(f);
				for(int k=n;k>=1;--k){
					if(k+4<=n)mx=Max(mx,f[k+4]);
					ans=Min(ans,j-i-1+pre-mx-sum[k][j-1]+sum[k][i]);
					pre=pre+2-a[k][i]-a[k][j]+sum[k][j-1]-sum[k][i];
					f[k]=pre-(j-i-1-sum[k][j-1]+sum[k][i]);
				}
			}
		}
		printf("%d\n",ans);
	}
	return 0;
}

D:

定义一个数是好的,当且仅当在这个排列中,所有包含它的子段一共有 \(m\) 个不同的最大值。

现在要求,给定 \(n,m,k,\) 求有多少个长度为 \(n\) 的排列有 \(k\) 个好数。

考虑一个 \(dp,\)\(f[i][j][k]\) 表示 \(n=i,m=j,k=k\) 的答案。

考虑一个从最大值往最小值的填数过程。我们枚举位置,会观察到一些性质:

比如,最大值左右两端的填数互不影响。 因为它们扩展到最大值之后的最大值就一定会是这里填的数,不再改变了。

同时我们还能发现,这也就相当于把两边转化为了一个 \(m\to m-1\) 的子问题

到这里 \(dp\) 的模型就很显然了,但还是要捋清思路:考虑枚举位置之后,我们还需要知道左右两边分别贡献了多少个好数。

那么转移就算 \(O(n^5)\) 了。考虑选择出 \(l-1\) 个数放在左边,然后去 \(dp\) 枚举左边贡献了多少好数。代码里面就是:

int res=0;
	for(int u=1;u<=n;++u){
		int tmp=0;
		for(int v=0;v<=k;++v){
			tmp=Add(tmp,Mul(dfs(u-1,m-1,v),dfs(n-u,m-1,k-v)));
		}
		tmp=Mul(tmp,C(n-1,u-1));
		res=Add(res,tmp);
	}
	f[n][m][k]=res;

剩下的问题就在于初始化了。这也是最难理解的一部分)

对于 \(n=0\) 的情况:此时如果要求我选出 \(k=0\) 个好数,那我是恰好有一种情况:不选的。否则方案不合法。

对于 \(n=1\) 的情况:如果恰好 \(m=k=1\) 那也就有一种情况来选。注意,如果 \(k=0,m\not =1,\) 此时也有一种情况:不选,否则不合法。

这是因为,我们现在只有可能选择出一个数,对于 \(m\not =1\) 的情况我们没有选择,也就恰好对应了要求;如果选择 \(1\) 的话也就不合法了。

对于 \(m=1\) 的情况:如果 \(k=1\) 意味着我可以随意排列,此时一定只有一个数的 \(m=1,\) 也就是最大值。否则方案不合法。

if(n==0){
		if(k==0)return 1;
		return 0;
	}
	if(n==1){
		if(k==1&&m==1)return 1;
		if(!k&&m!=1)return 1;//
		return 0;
	}
	if(m==1){
		if(k==1)return fac[n];
		return 0;
	}

技巧就是用小数据拍来确定边界情况

那么,这样是 \(O(n^5)=O(10^{10})\) 怎么过?

考虑剪枝。王队长给出了一些神奇的剪枝:

  • if(k>n)return 0;

这个就很显然了。

  • if(n-m+mm<k)return 0;

这里 \(mm\) 是初始 \(m\) 的值。看起来好像没啥用的样子)因为 \(m\leq mm?\)

  • if(n-m+n/2<k)return 0;

因为 \(m\leq 2\) 的时候最多能凑出 \(n/2\) 个好数,如果还是不满足直接返回 \(0.\)

综上,带上火车头就可以过掉了。

#pragma GCC optimize(3)
#pragma GCC target("avx,sse2,sse3,sse4,mmx")
#pragma GCC optimize("Ofast")
#pragma GCC optimize("inline")
#pragma GCC optimize("-fgcse")
#pragma GCC optimize("-fgcse-lm")
#pragma GCC optimize("-fipa-sra")
#pragma GCC optimize("-ftree-pre")
#pragma GCC optimize("-ftree-vrp")
#pragma GCC optimize("-fpeephole2")
#pragma GCC optimize("-ffast-math")
#pragma GCC optimize("-fsched-spec")
#pragma GCC optimize("unroll-loops")
#pragma GCC optimize("-falign-jumps")
#pragma GCC optimize("-falign-loops")
#pragma GCC optimize("-falign-labels")
#pragma GCC optimize("-fdevirtualize")
#pragma GCC optimize("-fcaller-saves")
#pragma GCC optimize("-fcrossjumping")
#pragma GCC optimize("-fthread-jumps")
#pragma GCC optimize("-funroll-loops")
#pragma GCC optimize("-fwhole-program")
#pragma GCC optimize("-freorder-blocks")
#pragma GCC optimize("-fschedule-insns")
#pragma GCC optimize("inline-functions")
#pragma GCC optimize("-ftree-tail-merge")
#pragma GCC optimize("-fschedule-insns2")
#pragma GCC optimize("-fstrict-aliasing")
#pragma GCC optimize("-fstrict-overflow")
#pragma GCC optimize("-falign-functions")
#pragma GCC optimize("-fcse-skip-blocks")
#pragma GCC optimize("-fcse-follow-jumps")
#pragma GCC optimize("-fsched-interblock")
#pragma GCC optimize("-fpartial-inlining")
#pragma GCC optimize("no-stack-protector")
#pragma GCC optimize("-freorder-functions")
#pragma GCC optimize("-findirect-inlining")
#pragma GCC optimize("-fhoist-adjacent-loads")
#pragma GCC optimize("-frerun-cse-after-loop")
#pragma GCC optimize("inline-small-functions")
#pragma GCC optimize("-finline-small-functions")
#pragma GCC optimize("-ftree-switch-conversion")
#pragma GCC optimize("-foptimize-sibling-calls")
#pragma GCC optimize("-fexpensive-optimizations")
#pragma GCC optimize("-funsafe-loop-optimizations")
#pragma GCC optimize("inline-functions-called-once")
#pragma GCC optimize("-fdelete-null-pointer-checks")
#include<bits/stdc++.h>
using namespace std;
const int N=101;
int f[N][N][N];
int n,k,m,mod;
int fac[N];
inline int Add(int x,int y){return (x+y)%mod;}
inline int Mul(int x,int y){return 1ll*x*y%mod;}
int Cc[101][101];
void pre(){
	fac[0]=1;
	for(int i=1;i<=100;++i)fac[i]=Mul(fac[i-1],i);
	Cc[0][0]=1;
	for(int i=1;i<=n;++i){
		Cc[i][0]=1;
		for(int j=1;j<=i;++j){
			Cc[i][j]=Add(Cc[i-1][j-1],Cc[i-1][j]);
		}
	}
}
inline int C(int x,int y){
	return Cc[x][y];
}
int nn,mm,kk;
int dfs(int n,int m,int k){
	if(n==0){
		if(k==0)return 1;
		return 0;
	}
	if(n==1){
		if(k==1&&m==1)return 1;
		if(!k&&m!=1)return 1;//
		return 0;
	}
	if(m==1){
		if(k==1)return fac[n];
		return 0;
	}
	if(~f[n][m][k])return f[n][m][k];
	if(k>n)return 0;
	if(n-m+mm<k)return 0;
	if(n-k+n/2<k)return 0; 
	int res=0;
	for(int u=1;u<=n;++u){
		int tmp=0;
		for(int v=0;v<=k;++v){
			tmp=Add(tmp,Mul(dfs(u-1,m-1,v),dfs(n-u,m-1,k-v)));
		}
		tmp=Mul(tmp,C(n-1,u-1));
		res=Add(res,tmp);
	}
	f[n][m][k]=res;
	return f[n][m][k];
}
signed main(){
// 	freopen("in.txt","r",stdin);
	scanf("%d%d%d%d",&n,&m,&k,&mod);
	nn=n;mm=m;kk=k;
	memset(f,-1,sizeof f);
	//n个数,优秀值为m,好数是k个 
	pre();
//	cout<<C(10,1)<<endl;
	printf("%d\n",dfs(n,m,k));
	return 0;
}
posted @ 2021-10-01 19:06  Refined_heart  阅读(32)  评论(0编辑  收藏  举报