24.3.24周报(第一类,第二类斯特林数)

星期一:

ABC345的D题dfs暴搜写着太痛苦了,而且目前题解也很少,先搁置一会

牛客寒假营1的E也是暴搜,当时用的三进制枚举,这次改用dfs写

牛客传送门

代码如下:

ll n;
int m;
int a[20],ans;
vector<int>x,y;
void dfs(int i){
	if(i==m){
		int res=1;
		for(int i=2;i<=n;i++) res+=(a[i]>a[1]);
		ans=min(res,ans);
		return ;
	}
	a[x[i]]+=3;
	dfs(i+1);
	a[x[i]]-=3;
	a[y[i]]+=3;
	dfs(i+1);
	a[y[i]]-=3;
	a[x[i]]++,a[y[i]]++;
	dfs(i+1);
	a[x[i]]--,a[y[i]]--;
	return ;
}
void solve(){
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> a[i];
	while(m--){
		int u,v; cin >> u >> v;
		if(u==1 || v==1) a[1]+=3;
		else x.push_back(u),y.push_back(v);
	}
	ans=n,m=x.size();
	dfs(0);
	cout << ans << "\n";
	x.clear(),y.clear();
}

学了下第一类斯特林数,S(n,m)为n个人坐m张圆桌的方案数,圆桌上的坐法为圆排列,方案数等同于(n-1)的全排列即(n-1) !

第一类斯特林数用递推打表计算

来看一下这题:                                  洛谷传送门

思路:高度为n的建筑在中间,左边有a-1个建筑群,右边b-1个,一个建筑群即为其中最高的建筑在外面,其余建筑任意排列,可以想到这排列方式即为第一类斯特林的圆排列,每个建筑群的顺序即为从外到内由低到高,是确定的,遂计算时不需要考虑

答案即为S(n-1,a+b-2)*C(a+b-2,a-1) ,意为从n-1个建筑中选出a+b-2个建筑群,再选a-1个在左,剩下b-1个即在右

代码如下:

const int N=5e4+10,M=210;
const int mod=1e9+7;
ll n;
ll s[N][M],c[M][M];
void init(){
	s[0][0]=1;
	for(int i=1;i<N;i++){
		for(int j=1;j<M;j++)
			s[i][j]=s[i-1][j-1]+(i-1)*s[i-1][j],s[i][j]%=mod;
	}
	for(int i=0;i<M;i++) c[i][0]=1;
	for(int i=1;i<M;i++){
		for(int j=1;j<=i;j++)
			c[i][j]=c[i-1][j-1]+c[i-1][j],c[i-1][j]%=mod;
	}
}
void solve(){
	init();
	int t; cin >> t;
	while(t--){
		int a,b; cin >> n >> a >> b;
		ll ans=s[n-1][a+b-2]*c[a+b-2][a-1]%mod;
		cout << ans << "\n";
	}
}

星期二:

第二类斯特林数

S(n,m)为n个人进m个房间的方案,房间里的人不需要排列组合

这个算法比较复杂,自己写的递归有问题,网上的资料也没看明白,拾了个买鑫鑫的代码,给封装成了函数,暂时能用就行:

ll power(ll x,ll y){
	ll res=1;
	while(y){
		if(y&1) res=res*x%mod;
		x=x*x%mod,y/=2;
	}
	return res;
}
ll inv(ll x){
	return power(x,mod-2);
}
ll asks(int n,int m){
	if(n<m) return 0;
	vector<ll>f(n+1);
	f[0]=1;
	for(int i=1;i<=n;i++) f[i]=f[i-1]*i%mod;
	ll res=0;
	for(int i=0;i<=m;i++){
		ll t=power(i,n)*inv(f[i])%mod*inv(f[m-i])%mod;
		if(!((m-i)&1)) res+=t,res%=mod;
		else res=(res-t+mod)%mod;
	}
	return res;
}

打了个队内天梯赛,紧接着是cf div3 round935,上分

星期三:

下午打了个中国传媒大学校赛同步赛,边上课边打,轻取樊神

题都不错,题意清晰明了,列几道

签到题: 但我想了一会

思路:正反跑两遍区间最大和的线性dp,i的最大和即为dp1[ i ]+dp2[ i ] - a[ i ]

构造题:一道有水平的构造

思路:首先不存在无解的情况,然后我们想什么地方是可以立马确定的,即是n个1的行和列,当我们把那一行一列填满后,要求一个1的行列就满足了,不能再填1,于是可选的行和列变成了n-2个,此时又可以确定n-1的行和列,如此往下,直到填好 n/2(向上取整)个行列,即结束

最后出的一道题:

思路:条件可翻译为 aj % ai ==0 , ak % aj ==0,我写了三重循环,但复杂度为调和级数

贴下代码:

const int N=2e6+10;
ll n;
int bu[N],ma;
ll ans;
void solve(){
	cin >> n;
	for(int i=1;i<=n;i++){
		int x; cin >> x;
		ma=max(x,ma);
		bu[x]++;
	}
	for(int i=1;i<=ma;i++){
		if(!bu[i]) continue;
		for(int j=i;j<=ma;j+=i){
			if(!bu[j]) continue;
			for(int k=j;k<=ma;k+=j){
				if(!bu[k]) continue;
				ans+=1ll*bu[i]*bu[j]*bu[k];
			}
		}
	}
	cout << ans;
}

最后是从早上看到晚上的round 935 F题             cf传送门

思路:STL,思路很简单,我也不到为啥断断续续做了一天,一个优先队列其实就够了

星期四:

补了几题

位运算如果会爆int的话,记得用1ll << i 或者 1ull << i

贴道牛客寒假集训营1的题              牛客传送门

思路:和背包没有一点关系,对于m二进制上的每一位1,我们可以考虑将其变为0,并使所有地位变为1,这样枚举二进制的每一位,对于每一个变化过的及原来的m,若m | w[ i ] == m即能拿,最后 答案取最大的res

代码如下:

const int N=2e6+10,M=210;
const int mod=1e9+7;
ll n;
ll m;
int v[N],w[N];
ll ans;
ll ask(ll x){
	ll res=0;
	for(int i=1;i<=n;i++)
		if((x|w[i])==x) res+=v[i];
	return res;
}
void solve(){
	cin >> n >> m;
	for(int i=1;i<=n;i++) cin >> v[i] >> w[i];
	ans=ask(m);
	for(int i=31;i>=0;i--){
		if(m&1<<i){
			ll tmp=(m^(1<<i))|((1<<i)-1);     //i位变为0,低位全变为1
			ans=max(ask(tmp),ans);
		}
	}
	cout << ans << "\n";
	ans=0;
}

星期六:

电科校赛初赛,从中午到晚上坐了一天的牢,有道规律题想半天没想出来,我发现光靠脑子想还是不够的,遇事不决就打个表,打表,打表,打表!!!别搁那坐着格物致知

星期天:

补cf round936 C题:             cf传送门

思路:dfs,加个二分答案,不二分光贪心是贪不了的,dfs完后对删边的数量进行判断,特判如果根节点所在连通块节点数量不够,删边数量减一

代码如下:

const int N=2e5+10;
const int mod=1e9+7;
ll n;
ll k,cnt;
vector<int>ve[N];
int dfs(int x,int fa,int ned){
	ll sum=1;
	for(auto i:ve[x]){
		if(i==fa) continue;
		sum+=dfs(i,x,ned);
	}
	if(sum>=ned && fa) sum=0,cnt++;   //删边操作,对父节点的贡献为0
	if(x==1 && sum<ned) cnt--;        //根节点连通块数量不够,得少删一条边
	return sum;
}
bool check(int x){
	cnt=0;
	dfs(1,0,x);
	if(cnt>=k) return 1;
	return 0;
}
void solve(){
	cin >> n >> k;
	for(int i=1;i<n;i++){
		int u,v; cin >> u >> v;
		ve[u].push_back(v);
		ve[v].push_back(u);
	}
	int l=1,r=n;
	ll ans=0;
	while(l<=r){
		int mid=l+r>>1;
		if(check(mid)) ans=mid,l=mid+1;
		else r=mid-1;
	}
	cout << ans << "\n";
	for(int i=1;i<=n;i++) ve[i].clear();
}

半个下午做了道数位dp,调了蛮久:          洛谷传送门

思路:f [ i ][ j ][ k ]表示位数为i,最高位为j,k数码出现次数

        因为要输出10个数码,所以可以跑10次dp给每个数码单独计算

        这里dp( 0, 0 ) 结果也是0,因为如果数大于0,统计0时是统计不到1位最高位为0的,所以干脆把dp( 0, 0 )也设为0,方便计算

代码如下:

ll n;
ll f[20][10][10],a[20];
ll p10[20];
void init(){
	p10[0]=1;
	for(int i=1;i<20;i++) p10[i]=p10[i-1]*10;
	for(int i=0;i<=9;i++) f[1][i][i]=1;
	for(int i=2;i<20;i++){
		for(int j=0;j<=9;j++)
			for(int k=0;k<=9;k++){
				if(j==k) f[i][j][k]=p10[i-1];
				for(int y=0;y<=9;y++)
					f[i][j][k]+=f[i-1][y][k];
			}
	}
}
ll dp(ll x,int num){
	if(!x) return 0;
	int cnt=0;ll res=0,tmp=x;
	while(x) a[++cnt]=x%10,x/=10;
	for(int i=cnt;i;i--){
		int now=a[i];
		for(int j=(i==cnt);j<now;j++)
			res+=f[i][j][num];
		if(now==num) res+=tmp%p10[i-1]+1;     //固定,now==num时才有贡献
	}
	for(int i=1;i<cnt;i++)
		for(int j=1;j<=9;j++)                 //位数低于cnt的数的贡献,最高位从1开始
			res+=f[i][j][num];
	return res;
}
void solve(){
	init();
	ll a,b; cin >> a >> b;
	for(int i=0;i<=9;i++) cout << dp(b,i)-dp(a-1,i) << " ";
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值