Codeforces Round 1033 (Div. 2) and CodeNite 2025 vp补题


原题连接

A 模拟

搬砖题

每条边两种情况 要么都相等,要么小的两条相加等于大的

(一开始没有注意到三个矩形的大小关系 卡了很长时间...

B 找规律

  • 在这三个矩形线上的球不会进袋,会一直转

    只有在对角线上的球能进
  • 球之间会发生碰撞

    但是同一轨迹的相撞不变轨迹
    垂直轨迹的相撞极限一换一,也相当于轨迹不变
cpp 复制代码
void solve(){
	int n,s;cin>>n>>s;
	int ans=0;
	forr(i,1,n){
		int dx,dy,x,y;
		cin>>dx>>dy>>x>>y;
		if(x+y==s&&dx*dy<0)ans++;
		else if(x==y&&dx*dy>0)ans++;
	}
	cout<<ans<<endl;
}

C 思维

读假题了 以为不会算根的d(v)值...

  • 先判定合法 树的两种极端结构------
    • 链(除了根,其他d(v)=1, s u m m i n = i + n − 1 sum_{min}=i+n-1 summin=i+n−1)
    • 菊花图(d(v)=min(root,v), s u m m a x = i ( i + 1 ) 2 + i ( n − i ) sum_{max}=\frac{i(i+1)}{2}+i(n-i) summax=2i(i+1)+i(n−i)
  • 之后问题在于怎么分m

一个dalao的做法:分配m

cpp 复制代码
void solve(){
    int n,m;cin>>n>>m;
    vector<int>a(n+1,0);
    if(!(m>=n&&m<=n*(n+1)/2))return cout<<"-1\n",void();
    int id=0,dt=m;
    //分配m
    forr(i,1,n){
        if(dt>n-i+1)dt-=(n-i+1),a[++id]=i;//后面数都能分到i
        else{//不够分 剩了dt个1分给n-dt+1~n   i~n-dt不分
            forr(j,i,n-dt)a[++id]=i-1;//能分到上一个 i-1
            forr(j,n-dt+1,n)a[++id]=i;
            break;
        }
    }
    //a[]数组非递减
    //d(root)=root,最大的是根,没有其他小的数覆盖
    int pre=a[n],nxt=a[n]+1;//nxt是被root覆盖的点
    cout<<a[n]<<endl;
    reforr(i,1,n-1){
        if(a[i]==a[i+1])cout<<pre<<' '<<(pre=nxt++)<<endl;//如果相等 说明有大的数被覆盖了 小的连接大的
        else cout<<pre<<' '<<(pre=a[i])<<endl;//不相等 连续的 没有大的数被覆盖
    }
}

另一个dalao的做法

从菊花图 s u m m a x sum_{max} summax开始向m凑

大的点合并到小的点可以让sum变小

cpp 复制代码
void solve()
{
	int n,m;cin>>n>>m;
	int rt=-1;
	forr(i,1,n){
		int low=i+n-1,up=i*(i+1)/2+(n-i)*i;
		if(low<=m&&m<=up){
			rt=i;
			break;
		}
	}
	if(rt==-1)return cout<<-1<<endl,void();
	int dis=rt*(rt+1)/2+(n-rt)*rt-m;
	vector<int>fa(n+1,rt);//菊花图
	reforr(i,1,n){
		if(dis==0)break;
		int d=min(i,rt);//当前的d(v)
		//d(v)-1是减掉最大的
		if(dis>=d-1){//移到1下面
			dis-=(d-1);
			fa[i]=1;
		}else{//dis<d-1
			int aim=d-dis;
			fa[i]=aim;
			break;
		}
	}
	cout<<rt<<endl;
	forr(i,1,n){
		if(i!=rt)cout<<fa[i]<<' '<<i<<endl;
	}
}

D 组合数学 鸽巢原理

题意:对任意 n ∗ m n*m n∗m,每个数[1,k]的矩阵,能找到 a ∗ b a*b a∗b相同的数组成的子矩阵,找字典序最小的(n,m)

首先让n最小,重复k个数比重复k个数的组合的种数要少

所以先考虑对单列, k + 1 k+1 k+1行会重复一个数,需要a行重复的, n = ( a − 1 ) k + 1 n=(a-1)k+1 n=(a−1)k+1

对每行,设每列有x种组合 m = ( b − 1 ) x + 1 m=(b-1)x+1 m=(b−1)x+1

这x种要满足之前每列有a个重复的, x m a x = k ⋅ C n a x_{max}=k\cdot C_n^a xmax=k⋅Cna,就是n个数中选a个相同,相同的数有k种情况

cpp 复制代码
int qpow(int x,int a){
	int res=1;
	while (a)
	{
		if(a&1)res=res*x%mod;
		x=x*x%mod;
		a>>=1;
	}
	return res%mod;
}
int inv(int x){
	return qpow(x,mod-2)%mod;
}
int C(int n,int a){
	int res=1,tp=1;
	forr(i,1,a){
		tp=tp*i%mod;
	}
	forr(i,n-a+1,n){
		res=res*i%mod;
	}
	return res*inv(tp)%mod;
}
void solve(){
	int a,b,k;cin>>a>>b>>k;
	int n=((a-1)*k+1)%mod;
	int m=(k*C(n,a)%mod*(b-1)%mod+1)%mod;
	cout<<n<<' '<<m<<endl;
}