牛客周赛 Round 31(A,B,C,D,E,F)

这场简单,赛时莽过了前五个就下班了,结果发现F是个简单隔板法,亏大了。

D是模拟链表,E是变种01背包,F是概率论隔板法,数论的范畴。

比赛链接官方B站视频讲解


A 小红小紫替换

思路:

如果是这个串是kou就改成yukari,没什么好说的。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;

string s;

int main(){
	cin>>s;
	if(s=="kou")cout<<"yukari";
	else cout<<s;
	return 0;
}

B 小红的因子数

思路:

分解质因数,然后统计有多少种质因数。相当板子,考虑开方暴力从小到大枚举所有 ≤ x \le \sqrt{x} x 可能的因子,每次遇到一个就从x中除去,每次遇到一个新的因子就计数值加一,这样跑一边就可以把所有 ≤ x \le \sqrt{x} x 的质因子去掉了,最后再看一下x是不是大于1,是的话说明留下了一个 > x \gt \sqrt{x} >x 的质因子,计数值加一。

为什么不用判断素性:因为合数一定是由质数组成的,这里遇到质数就除掉了,所以自然 看到合数之前它就被破坏掉了。

为什么x大于1说明留下了一个 > x \gt \sqrt{x} >x 的质因子:因为 > x \gt \sqrt{x} >x 的质因子肯定没有两个,否则光它俩乘积就大于x了,所以剩下的肯定就一个。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

ll x;
int cnt;

int main(){
	cin>>x;
	for(ll i=2;i*i<=x;i++){
		if(x%i==0){
			while(x%i==0)x/=i;
			cnt++;
		}
	}
	if(x>1)cnt++;
	cout<<cnt;
	return 0;
}

C 小红的字符串中值

思路:

先从字符串中找到这个字符,然后以这个字符为中心,向两边扩展,得到长度为1,3,5…的串,这些串都是满足条件的,直到一边到达边界。其实就是比较一下字符串内每个字符是否等于给定字符,如果是累计一下答案。

code:

#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
typedef long long ll;

int n;
char t;
string s;
ll ans;

int main(){
	cin>>n>>t>>s;
	for(int i=0;i<n;i++){
		if(t==s[i]){
			ans+=min(i+1,n-i);
		}
	}
	cout<<ans;
	return 0;
}

D 小红数组操作

思路:

把一个数看作一个节点,其实这就是个模拟链表题,不过数的范围很大,而n的范围可以接受,所以可以离散化。也可以使用map。

code:

离散化:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=2e5+5;

int q;
int a[maxn<<1],cnt;//离散化数组 
int nxt[maxn],pre[maxn];

struct operat{
	int op,x,y;
}opt[maxn];

int main(){
	cin>>q;
	for(int i=1;i<=q;i++){
		cin>>opt[i].op;
		if(opt[i].op==1){
			cin>>opt[i].x>>opt[i].y;
			a[++cnt]=opt[i].x;
			a[++cnt]=opt[i].y;
		}
		else {
			cin>>opt[i].x;
			a[++cnt]=opt[i].x;
		}
	}
	sort(a+1,a+cnt+1);
	cnt=unique(a+1,a+cnt+1)-a-1;
	
	int sz=0;
	nxt[1]=pre[1]=1;//0被分配到位置1,0作为头节点不存储信息 
	for(int i=1,x,y;i<=q;i++){
		if(opt[i].op==1){
			x=lower_bound(a+1,a+cnt+1,opt[i].x)-a;
			y=lower_bound(a+1,a+cnt+1,opt[i].y)-a;
			
			pre[x]=y;
			nxt[x]=nxt[y];
			pre[nxt[y]]=x;
			nxt[y]=x;
			sz++;
		}
		else {
			x=lower_bound(a+1,a+cnt+1,opt[i].x)-a;
			
			pre[nxt[x]]=pre[x];
			nxt[pre[x]]=nxt[x];
			sz--;
		}
	}
	
	cout<<sz<<endl;
	for(int i=nxt[1];i!=1;i=nxt[i])
		cout<<a[i]<<" ";
	return 0;
}

map:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <map>
using namespace std;
const int maxn=2e5+5;

int q;
map<int,int> nxt,pre;

int main(){
	cin>>q;
	nxt[0]=pre[0]=0;
	for(int i=1,op,x,y;i<=q;i++){
		cin>>op;
		if(op==1){
			cin>>x>>y;
			
			pre[x]=y;
			nxt[x]=nxt[y];
			pre[nxt[y]]=x;
			nxt[y]=x;
		}
		else {
			cin>>x;
			
			pre[nxt[x]]=pre[x];
			nxt[pre[x]]=nxt[x];
			nxt.erase(x);
			pre.erase(x);
		}
	}
	
	cout<<nxt.size()-1<<endl;
	for(int i=nxt[0];i;i=nxt[i])
		cout<<i<<" ";
	return 0;
}

E 小红的子集取反

思路1:

变种01背包。题解的做法更漂亮,不过本质是一样的。先说说我的。

如果计算一下所有元素之和为 t o t tot tot,那么我们的任务其实就是要求选择最少的数,使得它们之和为 t o t / 2 tot/2 tot/2,这样的话,把选中的这些数变成负数就可以使得它们的和为0了,这样,很容易看出如果tot为奇数则肯定无解。而选择一些元素,每个元素最多选一次,元素和小于等于一个值,这就是01背包啊。不过因为有的元素可能是负数,所以是变种01背包。

如果只有正数,那么就是一个很板子的01背包,考虑如何处理负数。为了防止tot本身就是负的,如果tot为负数,tot和所有数就都取负。对数进行降序排序,先处理正数,再处理负数,这样可以防止访问到负的下标。容量记录到它们的绝对值之和,这样可以记录到所有状态。

01背包可以用二维,也可以用一维。一维注意枚举顺序。

code1:

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=205;
const int inf=1e9;

int n,a[maxn],tot;
int dp[maxn*maxn*10];//前i个元素 

int main(){
	cin>>n;
	int maxx=0;//绝对值之和 
	for(int i=1;i<=n;i++){
		cin>>a[i];
		tot+=a[i];
		if(a[i]>=0)maxx+=a[i];
		else maxx-=a[i];
	}
	if(tot<0){
		for(int i=1;i<=n;i++)
			a[i]=-a[i];
		tot=-tot;
	}
	if(tot&1){
		puts("-1");
		return 0;
	}
	
	sort(a+1,a+n+1,greater<int>());
	for(int j=0;j<=maxx;j++)
		dp[j]=inf;
	dp[0]=0;
	
	for(int i=1;i<=n;i++){
		if(a[i]>=0)
			for(int j=maxx;j>=0;j--)
				dp[j]=min(dp[j],(j-a[i]>=0)?dp[j-a[i]]+1:inf);
		else 
			for(int j=0;j<=maxx;j++)
				dp[j]=min(dp[j],(j-a[i]<=maxx)?dp[j-a[i]]+1:inf);
	}
	if(dp[tot>>1]!=inf)printf("%d",dp[tot>>1]);
	else puts("-1");
	
	return 0;
}

参考一下题解的部分处理,既然我们这么怕访问到负的下标,那么我们一开始就就给所有下标加一个偏移量不就行了。

#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
const int maxn=205;
const int inf=1e9;

int n,a[maxn],tot;
int dp[maxn*maxn*10];//前i个元素 
int u=40000;

int main(){
	cin>>n;
	int maxx=u;//绝对值之和 
	for(int i=1;i<=n;i++){
		cin>>a[i];
		tot+=a[i];
		if(a[i]>=0)maxx+=a[i];
		else maxx-=a[i];
	}
	if(tot&1){
		puts("-1");
		return 0;
	}
	
	for(int j=0;j<=maxx;j++)
		dp[j]=inf;
	dp[u]=0;
	
	for(int i=1;i<=n;i++){
		if(a[i]>=0)
			for(int j=maxx;j>=0;j--)
				dp[j]=min(dp[j],(j-a[i]>=0)?dp[j-a[i]]+1:inf);
		else 
			for(int j=0;j<=maxx;j++)
				dp[j]=min(dp[j],(j-a[i]<=maxx)?dp[j-a[i]]+1:inf);
	}
	if(dp[u+tot/2]!=inf)printf("%d",dp[u+tot/2]);
	else puts("-1");
	
	return 0;
}

思路2:

一个元素要么加进来,要么减进来。其实就是要求所有数和起来等于0 废话 ,设 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示前 i i i 个数加减起来等于 j j j 的 减的最少的次数。其实就是要求 d p [ n ] [ 0 ] dp[n][0] dp[n][0],再配合偏移量,其实就是求 d p [ n ] [ u ] dp[n][u] dp[n][u]。这就是题解的做法。

对一个 d p [ i − 1 ] [ j ] dp[i-1][j] dp[i1][j],尝试加入 a i a_i ai,它可以直接加进来,推到 d p [ i ] [ j + a i ] dp[i][j+a_i] dp[i][j+ai],也可以减进来,推到 d p [ i ] [ j − a i ] + 1 dp[i][j-a_i]+1 dp[i][jai]+1,初始 d p [ 0 ] [ u ] = 0 dp[0][u]=0 dp[0][u]=0,最终要求 d p [ n ] [ u ] dp[n][u] dp[n][u]

其实可以边读边跑,可以省下一点空间。不过算了。

code2:

#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
const int maxn=205;
const int inf=1e9;

int n,a[maxn],tot;
int dp[maxn][maxn*maxn*2];//前i个元素 
int u=40000;

int main(){
	cin>>n;
	int maxx=u;//绝对值之和 
	for(int i=1;i<=n;i++){
		cin>>a[i];
		tot+=a[i];
		if(a[i]>=0)maxx+=a[i];
		else maxx-=a[i];
	}
	if(tot&1){
		puts("-1");
		return 0;
	}
	
	memset(dp,0x3f,sizeof(dp));
	dp[0][u]=0;
	
	for(int i=1;i<=n;i++){
		for(int j=0;j<=maxx;j++){
			if(j+a[i]<=maxx)dp[i][j]=min(dp[i][j],dp[i-1][j+a[i]]);
			if(j-a[i]>=0)dp[i][j]=min(dp[i][j],dp[i-1][j-a[i]]+1);
		}
	}
	if(dp[n][u]!=0x3f3f3f3f)printf("%d",dp[n][u]);
	else puts("-1");
	
	return 0;
}

F 小红的连续段

思路:

开头先放连续的a或b,连续的a,然后再放连续的b,如此反复。问一共 i i i 段连续的方法数是什么。

因为ab是交替的,所以如果一共有 i i i 段,如果 i i i 是奇数,那么奇数段上的有 i + 1 2 \frac {i+1} 2 2i+1 段,偶数段上的有 i − 1 2 \frac {i-1} 2 2i1 段。如果 i i i 是偶数,奇偶数段都是 i 2 \frac {i} 2 2i 段。a放在奇数段b放在偶数段的方法数 与 b放在奇数段a放在偶数段的方法数 之和就是答案。

现在只要研究某个字符放在若干段上的方法数就可以了。这个字符之间是相同的,而这些段之间是不同的(有顺序),每段都不为空。因此这其实就是球相同,盒不同,盒不为空的球盒问题。考虑隔板法。

其实很好理解,假设有n个球,m个盒。如果把球排成一排,n个球会有n-1个间隔,如果在n-1个间隔中插入m-1个隔板,就可以把这一排球分成m个不空的段,按顺序把每段放进盒子里即可。隔板的选择方式有 C n − 1 m − 1 C_{n-1}^{m-1} Cn1m1 种,这就说明n个相同球,m个不同盒,盒不为空的方法数为 C n − 1 m − 1 C_{n-1}^{m-1} Cn1m1

那么上面的就好算了。假设 i i i 是奇数,答案数就是a放在奇数段b放在偶数段的方法数 与 b放在奇数段a放在偶数段的方法数 之和,即: C x − 1 i + 1 2 − 1 ∗ C y − 1 i − 1 2 − 1 + C y − 1 i + 1 2 − 1 ∗ C x − 1 i − 1 2 − 1 C^{\frac {i+1} 2-1}_{x-1}*C^{\frac {i-1} 2-1}_{y-1}+C^{\frac {i+1} 2-1}_{y-1}*C^{\frac {i-1} 2-1}_{x-1} Cx12i+11Cy12i11+Cy12i+11Cx12i11
假设 i i i 是偶数,答案数就是: C x − 1 i 2 − 1 ∗ C y − 1 i 2 − 1 + C y − 1 i 2 − 1 ∗ C x − 1 i 2 − 1 C^{\frac {i} 2-1}_{x-1}*C^{\frac {i} 2-1}_{y-1}+C^{\frac {i} 2-1}_{y-1}*C^{\frac {i} 2-1}_{x-1} Cx12i1Cy12i1+Cy12i1Cx12i1
预处理 C x − 1 i C_{x-1}^{i} Cx1i C y − 1 i C_{y-1}^{i} Cy1i 即可。

code:

#include <iostream>
#include <cstdio>
using namespace std;
typedef long long ll;
const int maxn=1005;
const ll mod=1e9+7;

int x,y;
ll Cx[maxn],Cy[maxn];

ll qpow(ll a,ll b){
	ll base=a,ans=1;
	while(b){
		if(b&1){
			ans*=base;
			ans%=mod;
		}
		base*=base;
		base%=mod;
		b>>=1;
	}
	return ans;
}
ll inv(ll x){return qpow(x,mod-2);}


int main(){
	cin>>x>>y;
	Cx[0]=Cy[0]=1;
	for(int i=1;i<x;i++)
		Cx[i]=Cx[i-1]*(x-i)%mod*inv(i)%mod;
	for(int i=1;i<y;i++)
		Cy[i]=Cy[i-1]*(y-i)%mod*inv(i)%mod;
	
	for(int i=1;i<=x+y;i++){
		ll t=0,m1,m2;
		if(i&1){
			m1=(i+1)>>1;
			m2=(i-1)>>1;
		}
		else m1=m2=i>>1;
//		cout<<m1<<" "<<m2<<endl;
		
		t=((m1>=1 && m1<=x)?Cx[m1-1]:0)*((m2>=1 && m2<=y)?Cy[m2-1]:0)%mod;
		t=(t+((m2>=1 && m2<=x)?Cx[m2-1]:0)*((m1>=1 && m1<=y)?Cy[m1-1]:0))%mod;
		cout<<t<<endl;
	}
	
	return 0;
} 
  • 14
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值