【超好懂的比赛题解】The 2021 CCPC Weihai Onsite

57 篇文章 0 订阅
38 篇文章 0 订阅
本文记录了作者参与2021年CCPC威海现场赛的解题过程,涉及ACM竞赛策略、字符串处理、组合数学应用及树形结构问题。主要题目包括树的二叉树根节点计数、字符串周期计算、糖果分配方案和圆形桌面上的球反弹次数。此外,还讨论了部分题目使用最小割和多项式计算的方法,但未详述。
摘要由CSDN通过智能技术生成

title :The 2021 CCPC Weihai Onsite
date : 2021-12-1
tags : ACM,练习记录
author : Linno


题目链接 :https://codeforces.com/gym/103428

补题进度 :5/13

A. Goodbye, Ziyin!

题意

给一颗树,求能够作为二叉树的根的节点数。

思路

签到题。把每个点度数记录下来,对每个结点的度数,小于等于2则满足;大于3的时候答案为0。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=1e6+7;
const int mod=1e9+7;

vector<int>G[N];
int n,u,v,deg[N],ans=0;

signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n;
	for(int i=1;i<n;i++){
		cin>>u>>v;
		G[u].push_back(v);
		G[v].push_back(u);
		deg[u]++;deg[v]++;
	}
	for(int i=1;i<=n;i++){
		if(deg[i]>3){
			ans=0;
			break;
		}
		if(deg[i]<=2) ans++;
	}
	cout<<ans<<"\n"; 
	return 0;
}

D. Period

题意

给定字符串和q个询问,每次询问把一个位置修改成‘#’,求周期的数量。

思路

签到题,先预处理出原来的周期,然后对每次询问二分出满足条件的周期即可。

代码

#include<bits/stdc++.h>
using namespace std;
const int maxn=1e6+5;

int pi[maxn],v1[maxn],v2[maxn],n,q,u,minv,t1,t2;
string s;

int main()
{
	ios::sync_with_stdio(false);
	cin.tie(0); cout.tie(0);
	cin>>s>>q; n=s.length();
	for(int i=1;i<n;i++)
	{
		int j=pi[i-1];
		while(j&&s[i]!=s[j])j=pi[j-1];
		if(s[i]==s[j])j++;
		pi[i]=j;
	}
	if(pi[n-1])minv=pi[n-1],v1[++t1]=minv,v2[++t2]=minv;
	while(minv&&pi[minv-1])
	{
		minv=pi[minv-1];
		v1[++t1]=minv;
		v2[++t2]=minv;
	}
	for(int i=1;i<=t1;i++)v1[i]=n-v1[i]+1;
	reverse(v2+1,v2+1+t2); 
	for(int i=1;i<=q;i++)
	{
		cin>>u; 
		int p1=upper_bound(v1+1,v1+1+t1,u)-v1;
		int p2=lower_bound(v2+1,v2+1+t2,u)-v2;
		cout<<min(t1-p1+1,p2-1)<<"\n";
	}
	return 0;
}

G.Desserts

题意

有n种糖果,每种ai个,分给k只队伍,要求每个队伍不能重复拿相同的糖果并且要分完。求1到m每个k的方案数。

思路

结论很简单,就是把每只队分给每种糖果的组合数的结果累乘。 a n s k = Π i = 1 n C ( k , a i ) ans_k=\Pi_{i=1}^nC(k,a_i) ansk=Πi=1nC(k,ai)

O ( n ∗ m ) O(n*m) O(nm)是会超时的,要再注意每种糖果总和不超过1e5这个条件,说明不同的ai只有根号种,相同种的糖果不需要重复计算,用快速幂的形势就可以把复杂度降为 O ( m ∑ a l o g n ) O(m\sqrt{\sum a} logn) O(ma logn)

代码

#include<bits/stdc++.h>
#define int long long 
using namespace std;
const int N=1e5+100;
const int mod=998244353;

int a[N],kinds[N],ans,n,m,mx=0;
int fac[N],inv[N],finv[N];
int vt[N],cnt=0;

int fpow(int a,int b){
	int res=1;
	while(b){
		if(b&1)res=res*a%mod;
		a=a*a%mod;
		b>>=1;
	} 
	return res;
}

void init(){
	memset(kinds,0,sizeof(kinds));
	fac[0]=inv[0]=inv[1]=finv[0]=finv[1]=1ll;
	for(int i=1;i<N;i++)fac[i]=fac[i-1]*i%mod;
	for(int i=2;i<N;i++)inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<N;i++)finv[i]=finv[i-1]*inv[i]%mod;
}

inline int C(int a,int b){
	if(b>a)return 0;
	else return fac[a]*finv[a-b]%mod*finv[b]%mod;
}


signed main(){
	ios::sync_with_stdio(0);
	cin.tie(0);cout.tie(0);
	cin>>n>>m;
	init();
	for(int i=1;i<=n;i++){
		cin>>a[i];
		kinds[a[i]]++;
		mx=max(a[i],mx);
	}
	for(int i=1;i<=mx;i++){
		if(kinds[i]) vt[++cnt]=i;
	}
	for(int k=1;k<=m;k++){
		ans=1ll;
		for(int i=1;i<=cnt;i++){
			ans=ans*fpow(C(k,vt[i]),kinds[vt[i]])%mod;
			ans%=mod;
		}
		cout<<ans<<"\n";
	}
	return 0;
}

H. city safety

题意

n个城市,连成一棵树,每个城市的花费为wi。并且有收益表pi。如果修复i城市时,所有已花费城市距离该城市不超过pi,那么可获得pi的收益(取最大的一个)。问最大收益。

思路

看了题解,是用最小割来做,暂时还不会这东西。

代码

还写不出来,待补。

J. Circular Billiard Table

题意

在圆形球桌上给一个入射角,问要反射几次才能回到起点。

思路

签到题。假设球碰撞了n次,转了k圈回到原点。那么 2 n ∗ a b = 360 ∗ k 2n*\frac{a}{b}=360*k 2nba=360k,稍微化简得 n ∗ a = 180 ∗ k ∗ b n*a=180*k*b na=180kb,那么可以构造 k = a , n = 180 ∗ b k=a,n=180*b k=a,n=180b,使得等式必定成立,然后取k和n的gcd即可。

代码

#include<bits/stdc++.h>
#define inf 0x3f3f3f3f
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=1e9+7;

int gcd(int a,int b){return b?gcd(b,a%b):a;}

int t,a,b,n,k,d;

signed main(){
	//ios::sync_with_stdio(0);
	//cin.tie(0);cout.tie(0);
	cin>>t;
	while(t--){
		cin>>a>>b;
		n=180*b; //碰撞次数 
		k=a; //圈数
		d=gcd(n,k);
		while(d!=1){
			n/=d;
			k/=d;
			d=gcd(n,k);
		}
		cout<<n-1<<"\n";
	}
	return 0;
}

M. 810975

题意

给一个n,m,k,构造一个n位的01串,其中有m个1并且最长的连续的1长度为k。

思路

多项式容易写炸,考虑用组合数学的方法。设 f ( n , m , k ) f(n,m,k) f(n,m,k)是长度为n且有m个1的字符串,每段长度不大于k,那么答案转化为 f ( n , m , k ) − f ( n , m , k − 1 ) f(n,m,k)-f(n,m,k-1) f(n,m,k)f(n,m,k1),可以用容斥来做。用隔板法我们可以转化为把m个1放到n-m+1个盒子(0是隔板)里。然后这里由于我经验不足直接递归写炸了,借鉴了一个很妙的公式来进一步容斥。

for(int i=0;i<=p;i++){ //枚举每一段 
    int op=i&1?-1:1;
    res+=op*C(p,i)*C(m-i*(k+1)+p-1,p-1)%mod;
//感觉很妙,p段选i段来装最长的,并且容斥掉了剩下长度大于k的方案 
}

因为还不太理解,可能注释得不好,如果有更详细的理解可以告诉我。

代码

#include<bits/stdc++.h>
#define int long long
using namespace std;
const int N=2e5+7;
const int mod=998244353;

int fact[N],inv[N];

void init(){ //预处理 
	fact[0]=inv[0]=inv[1]=1;
	for(int i=1;i<N;i++) fact[i]=fact[i-1]*i%mod;
	for(int i=2;i<N;i++) inv[i]=(mod-mod/i)*inv[mod%i]%mod;
	for(int i=2;i<N;i++) inv[i]=inv[i]*inv[i-1]%mod;
}

int C(int n,int m){ //O(1)求组合数 
	if(m>n||m<0) return 0;
	return fact[n]*inv[m]%mod*inv[n-m]%mod;
}

inline int f(int n,int m,int k){ 
	if(!k){ 
		if(!m) return 1; //没有1 
		else return 0;
	}
	if(k>m) return 0; //不可能存在k比1总数大的情况 
	int res=0,p=n-m+1; //隔板拆成p段 
	for(int i=0;i<=p;i++){ //枚举每一段 
		int op=i&1?-1:1;
		res+=op*C(p,i)*C(m-i*(k+1)+p-1,p-1)%mod;
		//感觉很妙,p段选i段来装最长的,并且容斥掉了剩下长度大于k的方案 
	}
	return (res+mod)%mod;
}

inline int fc(int n,int m,int k){  //容斥 
	return (f(n,m,k)-f(n,m,k-1)+mod)%mod;
}

signed main(){
	int n,m,k;
	cin>>n>>m>>k;
	init();
	if(!k||k>m) cout<<f(n,m,k)<<"\n";
	else cout<<fc(n,m,k)<<"\n"
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

RWLinno

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值