2022 CCPC 桂林

B

题面看着很吓人,但只要读完就发现很好理解,并且根据题意暴力状压DP即可。
原本忘记可以调顺序,发现后纠结了一下,注意到重复选必然更劣故不用管,所以状压转移的时候,直接枚举选哪个就可以了,经过预处理后效率为O(n23m+nm)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=405,M=20;
void Max(int& x,int y){
	if(y>x) x=y;
}
void Min(int& x,int y){
	if(y<x) x=y;
}
int n,m,pw[M];
struct node{
	int op,tim,mem;
}p[M],tmp[N][M];
void init1(){
	int idx[N];
	idx['O']=0; idx['W']=1; idx['T']=2; idx['M']=3; idx['R']=4;
	for(int i=0;i<M;i++) pw[i]=(1<<i);
	cin>>n>>m;
	for(int i=1;i<=n;i++){
		for(int j=1;j<=m;j++){
			char c1,c2;
			scanf(" %c%c,%d/%d",&c1,&c2,&tmp[i][j].tim,&tmp[i][j].mem);
			tmp[i][j].op=idx[(int)c1];
		}
	}
	
	for(int j=1;j<=m;j++){
		for(int i=1;i<=n;i++){
			node u=tmp[i][j];
			Max(p[j].tim,u.tim);
			Max(p[j].mem,u.mem);
			if(!p[j].op && u.op){
				p[j].op=u.op;
				break;
			}
		}
	}
	//for(int j=1;j<=m;j++) cout<<j<<" "<<p[j].op<<" "<<p[j].tim<<" "<<p[j].mem<<endl;
}

int a[N],b[N],lim[1<<18];
void init2(){
	for(int i=1;i<=n;i++){
		for(int j=0;j<m;j++){
			node u=tmp[i][j+1],v=p[j+1];
			
			if(u.op && u.op==v.op) a[i]+=pw[j*3];
			if(u.tim==v.tim) a[i]+=pw[j*3+1];
			if(u.mem==v.mem) a[i]+=pw[j*3+2];
			
			if(u.op && u.op!=v.op) b[i]+=pw[j*3];
			if(u.tim>v.tim) b[i]+=pw[j*3+1];
			if(u.mem>v.mem) b[i]+=pw[j*3+2];
		}
		//cout<<i<<" "<<a[i]<<" "<<b[i]<<endl;
	}
	
	for(int i=0;i<pw[m*3];i++){
		int u=0;
		for(int j=0;j<m;j++){
			if( !(i&pw[j*3]) ) u+=pw[j*3];
		}
		lim[i]=u*7;
	}
}

int f[1<<18],frt[1<<18],pos[1<<18];
void dp(){
	memset(f,0x3f,sizeof(f));
	f[0]=0;
	for(int j=0;j<pw[m*3];j++){
		for(int i=1;i<=n;i++){
			if(b[i]&lim[j]) continue;
			int u=(j|(a[i]&lim[j]));
			if(f[j]+1<f[u]){
				f[u]=f[j]+1;
				frt[u]=j;
				pos[u]=i;
			}
		}
	}
	int s=pw[m*3]-1;
	for(int j=0;j<m;j++) if(!p[j+1].op) s-=pw[j*3];
	//cout<<"s="<<s<<endl;
	cout<<f[s]<<endl;
	stack<int>q;
	while(s){
		q.push(pos[s]);
		s=frt[s];
	}
	while(!q.empty()) printf("%d ",q.top()),q.pop();
}
int main()
{
	//freopen("1.in","r",stdin);
	init1();
	init2();
	dp();
	return 0;
}

D

列出式子后直接往普通幂转下降幂的方向想(因为变成下降幂之后就可以无穷级数求和再求导了),然后搞了半天发现不可做(中间推错了几次,一度以为可做),因为最后变成f(k)=i=1k{k,i}g(i),用各种第二类斯特林数的公式都难以高效计算出所有的f(k)

所以考虑其它方向:
首先发现E(nk)中的n拆成两部分后,用二项式展开是EGF的卷积形式,所以求出一张牌的期望F后,Fn即为n张牌的期望。
列出级数的式子后发现是等比*k次等差,考虑经典错位相减,然后发现F(k)可以化成i<k的F(i)乘组合数,用EGF即可求出F。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=(1<<20)+5,P=998244353,G[2]={3,(P+1)/3};
int Mul(int a,int b){
	return 1ll*a*b%P;
}
int Add(int a,int b){
	a+=b;
	if(a>=P) a-=P;
	if(a<0) a+=P;
	return a;
}
inline int fpw(int a,int x){
    int s=1;
    for(;x;x>>=1,a=1ll*a*a%P) if(x&1) s=1ll*s*a%P;
    return s;
}
int rv[N],gp[2][N],iv[N],fc[N],fv[N];
void init(int n){
	fc[0]=1;
    for(int i=1;i<n;i++) iv[i]=fpw(i,P-2),fc[i]=1ll*fc[i-1]*i%P;
    fv[n-1]=fpw(fc[n-1],P-2);
    for(int i=n-2;~i;i--) fv[i]=1ll*fv[i+1]*(i+1)%P;
    for(int p=0;p<2;p++){
        for(int i=1;i<n;i<<=1){
            gp[p][i]=1;
            int t=fpw(G[p],(P-1)/(i<<1));
            for(int j=i+1;j<(i<<1);j++) gp[p][j]=1ll*gp[p][j-1]*t%P;
        }
    }
}
void print(char name[],int n,int* a){
	printf("%s n=%d\n",name,n);
	for(int i=0;i<n;i++) cout<<a[i]<<" "; puts(""); 
}
inline void dft(int* a,int n,int p){
    for(int i=0;i<n;i++) if(i<rv[i]) swap(a[i],a[rv[i]]);
    for(int i=1;i<n;i<<=1){
        for(int j=0;j<n;j+=(i<<1)){
            for(int k=0;k<i;k++){
                int &A=a[i+j+k],&B=a[j+k],t=1ll*gp[p][i+k]*A%P;
                A=B-t; if(A<0) A+=P;
                B=B+t; if(B>=P) B-=P;
            }
        }
    }
    if(p) for(int i=0;i<n;i++) a[i]=1ll*a[i]*iv[n]%P;
}
inline int Rev(int m){
    int p=0,n=1;
    while(n<m) n<<=1,p++;
    for(int i=0;i<n;i++) rv[i]=(rv[i>>1]>>1)|((i&1)<<(p-1));
    return n;
}
inline void poly_mul(int m,int* A,int* B,int* C){
    int n=Rev(m);
    dft(A,n,0); dft(B,n,0);
    for(int i=0;i<n;i++) C[i]=1ll*A[i]*B[i]%P;
    dft(C,n,1);
    fill(C+m,C+n,0);
}
//C=A*B m:需要C的0~(m-1)项,m~(n-1)为空
int C[N],D[N];
inline void poly_inv(int m,int *a, int *b) {
    if(m==1){
        b[0]=fpw(a[0],P-2);
        return;
    }
    poly_inv((m + 1)>>1,a,b);
    int n=Rev(m<<1);
    copy(a,a+m,C);
    fill(C+m,C+n,0);
    dft(b,n,0); dft(C,n,0);
    for(int i=0;i<n;i++) b[i]=1ll*b[i]*(P+2-1ll*b[i]*C[i]%P)%P;
    dft(b,n,1);
    fill(b+m,b+n,0);
}
//a*b=1,给定a的0~(m-1)项,返回b的0~(m-1)项
//需要保证初始b为空!
inline void poly_Ln(int m,int* a,int* b){
	poly_inv(m,a,b);
	for(int i=0;i<m-1;i++) C[i]=1ll*a[i+1]*(i+1)%P; C[m-1]=0;
    int n=Rev(m<<1);
    fill(C+m,C+n,0);
    dft(C,n,0); dft(b,n,0);
    for(int i=0;i<n;i++) b[i]=1ll*C[i]*b[i]%P;
    dft(b,n,1);
	for(int i=m-1;i;i--) b[i]=1ll*b[i-1]*iv[i]%P; b[0]=0;
	fill(b+m,b+n,0);
}
//b=Ln(a),给定a的0~(m-1)项,返回b的0~(m-1)项
//需要保证初始b为空!
inline void poly_Exp(int m,int* a,int* b){
	if(m==1){
		b[0]=1;
		return;
	}
	poly_Exp((m+1)>>1,a,b);
	poly_Ln(m,b,D);
	for(int i=0;i<m;i++) D[i]=(P+a[i]-D[i]+(i==0))%P;
	int n=Rev(m<<1);
    dft(b,n,0); dft(D,n,0);
    for(int i=0;i<n;i++) b[i]=1ll*b[i]*D[i]%P;
    dft(b,n,1);
    fill(b+m,b+n,0);
    fill(D,D+n,0);
}
//b=Exp(a),给定a的0~(m-1)项,返回b的0~(m-1)项
//需要保证初始b为空!
int n,m,a,b,p,q;
int A[N],B[N],E[N],F[N],g[N];
int main(){
	cin>>n>>m>>a>>b;
	init((n+m)<<2);
	p=Mul(a,fpw(b,P-2));
	q=Add(P+1,-p);
	B[0]=1;
	for(int i=0;i<=m;i++) A[i]=fv[i],B[i]=Add(B[i],P-Mul(q,A[i]));
	poly_inv(m+1,B,E);
	poly_mul(m+m+1,A,E,F);
	fill(F+m+1,F+m+m+1,0);
	poly_Ln(m+1,F,g);
	fill(F,F+m+1,0);
	for(int i=0;i<=m;i++) g[i]=Mul(g[i],n);
	poly_Exp(m+1,g,F);
	for(int i=0;i<=m;i++) printf("%d\n",Mul(F[i],fc[i]));
	return 0;
}

E

回想了一下场上卡的地方,真的蠢哭了。。。大概是直接用坐标表示两个向量,算出叉积,转化成ax+by+c的最小值,麻烦的是这个c(1e18范围),要尽量往0靠,然后就要在特解的基础上找到绝对值最小的解,但分析一下,发现这样的解可能是超过1e18的(1e18*1e9),于是往各种奇怪的方向搞了半天,还是自闭了,最后队友换了个做法才过。

但凡稍微冷静一下,就发现可以令其中一个为原点,就变成一个已知向量和一个未知向量叉积的最小非0解,直接exgcd找任意一组即可!!

复盘了一下当时的心态,就觉得是个签到题,没分析原来做法的不合理性(在写的时候其实有感到一点诡异,这时候就应该回过头想清楚一点),也不想麻烦队友,wa了几遍之后心态很崩,花了很多时间才注意到答案会超;然后也没回头想,就直接瞎调整,写了一堆很复杂的东西还不对(甚至在怀疑自己对exgcd有什么误解)。。。吸取的教训就是要保持冷静和多交流!!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
void exgcd(ll a,ll b,ll& x,ll& y){
	if(!b){
		x=1;
		y=0;
		return;
	}
	exgcd(b,a%b,y,x);
	y-=a/b*x;
}
void work(){
	ll a,b,c,d,x,y;
	cin>>a>>b>>c>>d;
	c=a-c;
	d=b-d;
	if(!c){
		cout<<a+1<<" "<<0<<endl;
		return;
	}
	
	if(!d){
		cout<<0<<" "<<b+1<<endl;
		return;
	}
	exgcd(-d,c,x,y);
	cout<<a+x<<" "<<b+y<<endl;
}
int main()
{
	int T; cin>>T; while(T--) work();
	return 0;
}

G

场上开到这题的时候已经很急了,没多想性质就直接写了个即子树内两条链状态的方法(大概是题解最后提到的很麻烦状压DP),然后一直没过。。。

原本以为分讨很麻烦,但其实不会:先固定一条链,然后考虑另一条链的LCA;如果两条链相交且LCA在固定的链的链所在的子树外,那么会在固定另一条链时记到;否则LCA只会在固定的链上,而对于这种情况,可以通过改为交于一点,使答案更优。于是分别考虑交于一点和两条链不相交的情况,换根DP即可。

感觉越是在紧张的状态下,就越会对需要观察性质、思考结论这样的思维活动失去自信,从而转向无脑暴力做法,而在大多数情况下只会使情况更复杂(其实不对劲的话也能大概感觉到);在各种情况下想题,都实事求是地考虑题目的方向和性质(而不是急于求成,直接套算法或猜不靠谱的结论),这样的心理和思维素质都是需要锻炼的!

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=2e5+5;
int n,a[N],hd[N],to[N<<1],nx[N<<1],tt;
void add(int u,int v){
	nx[++tt]=hd[u];
	to[hd[u]=tt]=v;
}
struct node{
	int f,f1,d1,f2,g,d;
}p[N];
bool cmp(node u,node v){
	return u.g>v.g;
}
bool cmp2(node u,node v){
	return u.f>v.f;
}
void pre(int u,int fa){
	vector<node>h;
	p[u].d=u;
	for(int e=hd[u];e;e=nx[e]) if(to[e]!=fa){
		int v=to[e];
		pre(v,u);
		h.push_back(p[v]);
		p[u].g=max(p[u].g,p[v].g);
		int x=p[v].f;
		if(x>p[u].f2){
			if(x>p[u].f1) p[u].f2=p[u].f1,p[u].f1=x,p[u].d1=v;
			else p[u].f2=x;
		}
	}
	//cout<<"pre:"<<u<<endl;
	int sz=h.size();
	//if(!sz) return;
	sort(h.begin(),h.end(),cmp);
	p[u].g+=a[u];
	p[u].f=max(p[u].f1,a[u]+(sz?h[0].g:0)+(sz>1?h[1].g:0));
	//cout<<"u="<<u<<" g="<<p[u].g<<" f="<<p[u].f<<endl;
}
int ans;
void dfs(int u,int fa){
	vector<node>h;
	h.push_back(p[fa]);
	for(int e=hd[u];e;e=nx[e]) if(to[e]!=fa){
		int v=to[e];
		h.push_back(p[v]);
	}
	int sz=h.size();
	sort(h.begin(),h.end(),cmp);
	int s=0;
	for(int i=0;i<min(4,sz);i++) s+=h[i].g;
	ans=max(ans,s);
	
	p[u].g=(sz?h[0].g:0)+a[u];
	p[u].f=(sz?h[0].g:0)+(sz>1?h[1].g:0)+a[u];
	
	sort(h.begin(),h.end(),cmp2);
	p[u].f1=(sz?h[0].f:0);
	p[u].d1=(sz?h[0].d:0);
	p[u].f2=(sz>1?h[1].f:0);
	
	for(int e=hd[u];e;e=nx[e]) if(to[e]!=fa){
		int v=to[e];
		node tu=p[u],tv=p[v];
		
		//new g
		if(h[0].d==v){
			if(sz>1) p[u].g=h[1].g+a[u];
			else p[u].g=a[u];
		}
		//new fu
		int mf=p[u].f1;
		if(v==p[u].d1) mf=p[u].f2;
		int mg;
		if(v==h[0].d) mg=(sz>1?h[1].g:0)+(sz>2?h[2].g:0);
		else if(sz>1 && v==h[1].d) mg=h[0].g+(sz>2?h[2].g:0);
		else mg=h[0].g+(sz>1?h[1].g:0);
		p[u].f=max(mf,mg+a[u]);
		
		ans=max(ans,p[u].f+p[v].f);
		dfs(v,u);
		p[u]=tu; p[v]=tv;
	}
}
int main()
{
	cin>>n;
	for(int i=1;i<=n;i++) scanf("%d",&a[i]);
	for(int u,v,i=1;i<n;i++) scanf("%d%d",&u,&v),add(u,v),add(v,u);
	if(n==1){
		puts("0");
		return 0;
	}
	pre(1,0);
	//return 0;
	dfs(1,0);
	cout<<ans<<endl;
	return 0;
}

J

对于一些点确定的拓扑图,直观的想法是跑正反两边拓扑序,对每个点求出一个限制较弱的区间;而对于n个区间填数又是典中典;但问题在于这里求出的区间不是充分的,仍然有拓扑关系的限制存在;但分析一下发现,直接贪心做就是满足拓扑序的!因为对于每条边,入点的区间的左右端点,一定分别在出点区间的左右端点之右,那么入点填的数一定大于出点。
思路还是好想的,但合法性需要一些觉悟,或者就大胆一点直接莽也对。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=5e5+5;
int T,n,m,p[N];
struct edge{
	int *hd,*to,*nx,*du,tt;
	edge(void){
		hd=new int[N];
		to=new int[N];
		nx=new int[N];
		du=new int[N];
	}
	void add(int u,int v){
		nx[++tt]=hd[u];
		to[hd[u]=tt]=v;
		du[v]++;
	}
};
bool is[N];
struct node{
	int d,l,r;
}a[N];
struct cmp1{
	bool operator () (node u,node v){
		return u.l>v.l;
	}
};
struct cmp2{
	bool operator () (node u,node v){
		return u.r>v.r;
	}
};
queue<node>q;
void topo(int op,edge E){
	//return;
	//return;
	for(int i=1;i<=n;i++){
		if(!p[i] && !E.du[i]) q.push(a[i]);
	}
	//return;
	while(!q.empty()){
		node u=q.front();
		q.pop();
		for(int e=E.hd[u.d];e;e=E.nx[e]){
			int v=E.to[e];
			E.du[v]--;
			if(!op) a[v].l=max(u.l+1,a[v].l);
			else a[v].r=min(u.r-1,a[v].r);
			if(!E.du[v]) q.push(a[v]);
			//t++;
			//break;
		}
	}
}
edge E[2];
void work(int id){
	scanf("%d%d",&n,&m);
	E[0].tt=E[1].tt=0;
	for(int i=1;i<=n;i++){
		is[i]=1,a[i]=(node){i,1,n};
		for(int op=0;op<2;op++) E[op].hd[i]=E[op].du[i]=0;
	}
	for(int i=1;i<=n;i++) scanf("%d",&p[i]),is[p[i]]=0;
	while(m--){
		int u,v;
		scanf("%d%d",&u,&v);
		if(p[u] && p[v]){
			if(p[u]>p[v]){
				puts("-1");
				while(m--) scanf("%d%d",&u,&v);
				return;
			}
		}
		else if(p[u]){
			a[v].l=max(a[v].l,p[u]);
		}
		else if(p[v]){
			a[u].r=min(a[u].r,p[v]);	
		}
		else E[0].add(u,v),E[1].add(v,u);
	}
	//if(T==20000) return;
	topo(0,E[0]);
	//if(T==20000) return;
	topo(1,E[1]);
	priority_queue<node,vector<node>,cmp1>q;
	priority_queue<node,vector<node>,cmp2>t;
	for(int i=1;i<=n;i++){
		if(!p[i]) q.push(a[i]);
	}
	for(int i=1;i<=n;i++){
		if(!is[i]) continue;
		while(!q.empty()){
			node u=q.top();
			//cout<<u.l<<" "<<u.r<<" "<<u.d<<endl;
			if(u.l>i){
				break;
			}
			q.pop();
			t.push(u);
		}
		if(t.empty()){
			puts("-1");
			return;
		}
		node u=t.top();
		if(u.r<i){
			puts("-1");
			return;
		}
		p[u.d]=i;
		t.pop();
	}
	for(int i=1;i<=n;i++) if(!p[i]){
		puts("-1");
		return;
	}
	for(int i=1;i<=n;i++) printf("%d ",p[i]);
	puts("");
	/*if(T==20000 && id==10000){
		int a=1/0;
	}*/
}
int main()
{
	//srand(time(0));
	//freopen("1.in","r",stdin);
	//freopen("1.out","w",stdout);
	cin>>T; for(int i=1;i<=T;i++) work(i);
	return 0;
}

K

分讨+构造题,本质和L差不多,但会更复杂一些。
先考虑一般的情况,大致的思路是尽量构造异或和小的解:
发现只有m是偶数时异或和可以为0,否则最小为1,于是按照m的奇偶分讨;
又发现n为偶数的时候可以分成两组数,相同的部分互相抵消,n为奇数时就两组+剩余一个数;
于是按照n,m的奇偶性分四种情况讨论,以两组尽可能互相抵消的方式,在使得异或和为0或1的同时,每个数不小于1或2,然后可以得到有解的条充分件是一个n,m的不等式,并且感性认知到这也是必要的(不满足就构不出来了)

但在构造上面的解的时候,会发现必须n4(一组内的数要多余一个),于是需要特判,n=1n=2是显然的,难点在于n=3(感觉这部分比一般的情况更难)

还是按照m的奇偶性分讨,容易想到2k(+1),a,2k+a(+1)的构造方式,但对于a1的两种情况需要特殊构造(原本以为无解,不知道wa哪了卡了很久,感觉如果在考场上会很搞心态,所以对于这种分讨题,还是要多自己构造小数据和特殊数据)

这题虽然思路比较自然,但需要比较细致的分讨和不厌其烦的构造,要场上过还是很有压力的,感觉难度远大于B,甚至略大于D(擅长构造的队友场切了,本来是一大优势,但前面的题击中的盲点过多+一卡题就自闭就浪费了这样的优势)

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=1e7+5;
int T,lg[N],pw[N],ans[N];
void fill(int t,int s,int r){
	for(int i=1;i<t;i++){
		ans[i]=s;
		if(i<=r) ans[i]++;
		ans[i+t-1]=ans[i];
	}
}
void work(int id){
	int n,m,pd=1;
	cin>>n>>m;
	int t=n/2,k=m/2,g=lg[k]+1;
	if(n==1){
		puts("NO");
		return;
	}
	if(n==2){
		if(m&1){
			if(pw[g]-1==k) puts("NO");
			else{
				puts("YES");
				cout<<k<<" "<<k+1<<endl;
			}	
		}
		else{
			puts("YES");
			cout<<k<<" "<<k<<endl;
		}
		return;
	}
	if(n==3){
		int a=pw[g-1],b=k-a;
		if(m<6) puts("NO");
		else if(m&1){
			if(!b){
				if(k<16) puts("NO");
				else{
					puts("YES");
					cout<<a-1<<" "<<a-5<<" "<<7<<endl;
				}
			}
			else if(b==1){
				if(k<17) puts("NO");
				else{
					puts("YES");
					cout<<a-1<<" "<<a-3<<" "<<7<<endl;
				}
			}
			else{
				puts("YES");
				cout<<a+1<<" "<<b<<" "<<a+b<<endl;
			}
		}
		else{
			if(!b && k<8) puts("NO");
			else if(!b){
				puts("YES");
				cout<<a-1<<" "<<a-2<<" "<<3<<endl;
			}
			else{
				puts("YES");
				cout<<a<<" "<<b<<" "<<a+b<<endl;
			}
		}
		return;
	}
	if(m&1){
		if(n&1){
			if(k<t+t+4) pd=0;
			else{
				int s=(k-6)/(t-1),r=(k-6)%(t-1);
				fill(t,s,r);
				ans[n-2]=2; ans[n-1]=4; ans[n]=7;
			}
		}
		else{
			if(k<n) pd=0;
			else{
				int s=(k-2)/(t-1),r=(k-2)%(t-1);
				fill(t,s,r);
				ans[n-1]=2; ans[n]=3;
			}
		}
	}
	else{
		if(n&1){
			if(k<t+2) pd=0;
			else{
				int s=(k-3)/(t-1),r=(k-3)%(t-1);
				fill(t,s,r);
				ans[n-2]=1; ans[n-1]=2; ans[n]=3;
			}
		}
		else{
			for(int i=1;i<=n-2;i++) ans[i]=1;
			ans[n-1]=ans[n]=(m-n+2)/2;
		}
	}
	if(!pd) puts("NO");
	else{
		puts("YES");
		for(int i=1;i<=n;i++) printf("%d ",ans[i]);
		puts("");
	}
}
int main()
{
	for(int i=2;i<N;i++) lg[i]=lg[i>>1]+1;
	pw[0]=1; for(int i=1;i<=30;i++) pw[i]=(pw[i-1]<<1);
	cin>>T; for(int i=1;i<=T;i++) work(i);
	return 0;
}

L

本质上是一个简单构造,但考场上卡其它题导致思路混乱,没冷静想,非常可惜。。。

先考虑如何判断一个答案是否合法:即对于每个人,它选择概率不为0的下标时,一定是其它人任何情况下的最优解之一。那么发现概率只关心是否为0,并且对于每个人,肯定是有一个下标概率为1,其它为0,这样对自己和别人的合法性都是最优的。

在判断一个选择是否合法时,我们关心的是每个数其它人选择的人数。于是把选择每个下标的人数cnt[i]算出来,问题转化成构造一个大小m的cnt数组,使得对于每一个非0元素,将其-1后,选择该下标是最优解之一。

先考虑没有单独的数的情况,即任意cnt[i]2,发现合法,于是n2m就解决了。

在考虑2m>nm的时候,发现1的个数必须至少三个,又发现只要有3个连续的1,剩下的都放在后面一个,前面补0即可,于是m4解决了。

剩下的情况,n=2直接特判;n3时,只剩下m=2m=3,n=4/5,都单独构造即可。

点击查看代码
#include<bits/stdc++.h>
using namespace std;
const int N=15;
int n,m,c[N];
int main()
{
	cin>>n>>m;
	if(n==2){
		while(n--){
			for(int i=1;i<m;i++) cout<<0<<" ";
			cout<<1<<endl;
		}
		return 0;
	}
	if(n>=m+m){
		int t=n/m,r=n%m;
		for(int i=1;i<=m;i++) c[i]=t;
		for(int i=m-r+1;i<=m;i++) c[i]++;
	}
	else if(m==2){
		c[1]=2;
		c[2]=n-2;
	}
	else if(m==3 && (n==4 || n==5)){
		c[2]=c[3]=2;
		c[1]=(n==5);
	}
	else{
		if(n>3){
			c[m-1]=c[m-2]=c[m-3]=1;
			c[m]=n-3;
		}
		else c[m]=c[m-1]=c[m-2]=1;
	}
	int p=1;
	while(n--){
		while(!c[p]) p++;
		c[p]--;
		for(int i=1;i<=m;i++){
			if(i==p) cout<<1<<" ";
			else cout<<0<<" ";
		}
		puts("");
	}
	return 0;
}

posted @   sz[sz]  阅读(57)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 使用C#创建一个MCP客户端
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示