牛客2020跨年场全部题解

题目链接

牛牛想起飞

n<=1e5,y<=100,可以用dp,定义
d p [ i ] [ j ] = { 1 , 变换后前n项和可以为j 0 , else dp[i][j]= \begin{cases} 1, &\text{变换后前n项和可以为j} \\0, & \text{else} \end{cases} dp[i][j]={1,0,变换后前n项和可以为jelse
就可以得到状态转移方程
d p [ i ] [ j ] = d p [ i − 1 ] [ ( j + a [ i − 1 ] ) % y ] ∣ ∣ d p [ i − 1 ] [ ( ( j − a [ i − 1 ] ) % y + y ) % y ] ∣ ∣ d p [ i − 1 ] [ a [ i − 1 ] % y ] dp[i][j]=dp[i-1][(j+a[i-1])\%y]||dp[i-1][((j-a[i-1])\%y+y)\%y]||dp[i-1][a[i-1]\%y] dp[i][j]=dp[i1][(j+a[i1])%y]dp[i1][((ja[i1])%y+y)%y]dp[i1][a[i1]%y]

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
const int N=1e5+5;
int dp[N][105],a[N],b[N],n,y;
int f(int x){
	while(x<0) x+=y;
	return x;
}
int main(){
	Rii(n,y);
    rep(i,1,n) Ri(a[i]);
    rep(i,1,n) Ri(b[i]);
    dp[0][0]=1;
    rep(i,1,n)
        rep(j,0,y-1)
            if(dp[i-1][j]){
                dp[i][f(j+a[i])%y]=1;
                dp[i][f(j+a[i]-b[i])%y]=1;
                dp[i][f(j+a[i]+b[i])%y]=1;
            }
    per(i,y-1,0)
        if(dp[n][i]){
        	printf("%d",i);
        	return 0;
        }
} 

最小互质数

首先1如果没出现过,答案必然是1
如果1出现过,考虑出现过的数x,若(x,y)=1,则x所有的素因子都不是y的因子,即只要x出现过,x的素因子的倍数都不满足条件
再考虑满足条件的数y,y的素因子一定不是出现过的任何一个数的素因子,那么最小的y必然是一个素数,又考虑到1e5+3是素数,那么结果肯定不会超过1e5+3,于是对[1,1e5+3]先筛一次素数,再对出现的数质因数分解即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
const int N=1e5+5;
int cnt=0;
bool r[N],u[N];
ll ans=1;
bool s(int x){
	int k=sqrt(x)+1;
	rep(i,2,k)
		if(!(x%i)) return 0;
	return 1;
}
int main(){
	int n,k=sqrt(N)+1;
	Ri(n);
	rep(i,2,k)//筛素数 
		if(!u[i])
			for(int j=i*i;j<N;j+=i)
				u[j]=1;
	rep(i,1,n){
		Ri(k);
		if(k==1) r[1]=1;
		int t=2;
		if(!u[k]){//剪枝 素数不进行质因数分解 
			r[k]=1;
			continue; 
		}
		while(k>1){//质因数分解 
			if(!(k%t)){
				r[t]=1;
				while(!(k%t)) k/=t;
			} 
			t++;
		}
	}
	rep(i,1,N)//枚举 
		if(!r[i]&&!u[i]){
			printf("%d",i);
			return 0;
		}
}

衔尾蛇

三种颜色出现的次数都枚举过去,枚举时用dfs求出所有排列之后,用最小表示法得出该排列的最小表示(取字符串第1~i位放到字符串末尾,字典序最小时即为最小表示),一个环的最小表示必然是相同的,再用set去重即可

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
set<string>g;
void gm(string s){
	int i=0,j=1,k=0,l=s.size();
	while(i<l&&j<l&&k<l){
		int t=s[(i+k)%l]-s[(j+k)%l];
		if(!t) k++;
		else{
			if(t>0) j+=k+1;
			else i+=k+1;
			if(i==j) j++;
			k=0;
		}
	}
	string tmp=s.substr(min(i,j),l-min(i,j))+s.substr(0,min(i,j));
	g.insert(tmp);
}
void dfs(int a,int b,int c,string t){
	if(!a&&!b&&!c){
		if(t!="") gm(t);
		return;
	}
	if(a) dfs(a-1,b,c,t+"a");
	if(b) dfs(a,b-1,c,t+"b");
	if(c) dfs(a,b,c-1,t+"c");
}
int main(){
	int a,b,c;
	Rii(a,b);Ri(c);
	rep(i,0,a)
		rep(j,0,b)
			rep(k,0,c)
				dfs(i,j,k,""); 
	printf("%d",g.size());
	return 0;
}

牛牛的反函数

首先观察递推式,对输入的y,可以理解成进行递推的次数,而要求输出的x在[1,1e18]之内,则隐含的条件即为要找到最小的x满足F(x)=y
即在给定的递推次数下,找到最小的x
很显然进行x+1的操作会比x/2的操作更能得到最小的x,则递推式一定是先+1再/2一直往复操作
我们考虑一个数列 a 1 = x , a n = ( a n − 1 + 1 ) / 2 a_1=x,a_n=(a_{n-1}+1)/2 a1=x,an=(an1+1)/2,终止时 a n = 2 a_n=2 an=2(因为最后两次操作是+1再/2,不可能得到1),可以得到 2 ( a n − 1 ) = a n − 1 − 1 2(a_n-1)=a_{n-1}-1 2(an1)=an11,整理得 a 1 − 1 = 2 n − 1 ( a n − 1 ) = 2 n − 1 a_1-1=2^{n-1}(a_n-1)=2^{n-1} a11=2n1(an1)=2n1,那么y为偶数时就可以得到 x = 2 y / 2 − 1 + 1 x=2^{y/2-1}+1 x=2y/21+1
再考虑y为奇数时,就在开始再加上一次/2操作,补上开始的/2操作,得到 x = ( 2 ( y − 1 ) / 2 − 1 + 1 ) ∗ 2 = 2 ( y − 1 ) / 2 + 2 x=(2^{(y-1)/2-1}+1)*2=2^{(y-1)/2}+2 x=(2(y1)/21+1)2=2(y1)/2+2
然后很容易得到(系统自带的计算器算一下)x<=120

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
int main(){
    int t,y;
    ll a[130]={0,1};
    rep(i,2,121)
        if(i&1) a[i]=(1ll<<(i/2))+2;
        else a[i]=(1ll<<((i-2)/2))+1;
    Ri(t);
    while(t--){
        Ri(y);
        if(y>=121) puts("-1");
        else printf("%lld\n",a[y]);
    }
    return 0;
}

开心消消乐

先考虑能消除的连续区间,一定是形如111000的形式,那么令dp[i].l记录前k个不删除时消除后的长度,dp[i].q记录前k个不删除时消除后末尾连续1的个数.令pd[i].l记录后k个不删除时消除后的长度,dp[i].q记录后k个不删除时消除后前端连续0的个数.
利用dp[i].p来记录前k个不删除时消除之后字符串最后一个字符在原字符串中的位置,那么就可以得到状态转移方程
d p [ i ] . l = { d p [ i − 1 ] . l − 1 , dp[i-1].p与i可消除 d p [ i − 1 ] . l + 1 , 不可消除 dp[i].l= \begin{cases} dp[i-1].l-1,&\text{dp[i-1].p与i可消除} \\dp[i-1].l+1, &\text{不可消除} \end{cases} dp[i].l={dp[i1].l1,dp[i1].l+1,dp[i-1].pi可消除不可消除
d p [ i ] . p = { d p [ d p [ i − 1 ] . p − 1 ] . p − 1 , 可消除 i , 不可消除 dp[i].p= \begin{cases} dp[dp[i-1].p-1].p-1,&\text{可消除} \\i, &\text{不可消除} \end{cases} dp[i].p={dp[dp[i1].p1].p1,i,可消除不可消除
这里可消除的情况的式子,是因为dp[i].p也被消除了,所以dp[i].p应该是再往前的未被消除的字符的位置
d p [ i ] . q = { d p [ i − 1 ] . q − 1 , 可消除 d p [ i − 1 ] . q + 1 − s [ i ] + ′ 0 ′ , 不可消除 dp[i].q= \begin{cases} dp[i-1].q-1,&\text{可消除} \\dp[i-1].q+1-s[i]+'0', &\text{不可消除} \end{cases} dp[i].q={dp[i1].q1,dp[i1].q+1s[i]+0,可消除不可消除
这里不可消除的情况的式子,是s[i]='1’时会加上,否则不加
以及消除后所有0都在最前端,所以一旦开始计数,消除后的字符串后面就只有1,故不用考虑会有1110111这样的情况需要重新计数
pd数组同理也可以得到类似的状态转移方程
那么就可以枚举区间起点终点,若[i+1,j-1]区间满足区间删除的条件时,很容易得到
a n s = m i n ( d p [ i ] . l + p d [ j ] . l − 2 ∗ m i n ( d p [ i ] . q , p d [ j ] . q ) , a n s ) ans=min(dp[i].l+pd[j].l-2*min(dp[i].q,pd[j].q),ans) ans=min(dp[i].l+pd[j].l2min(dp[i].q,pd[j].q),ans)
以及记得ans初始化为没有区间删除操作的情况,即dp[n].l

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
const int N=1e4+5;
int n,a[2][N];
struct node{
	int p,q,l;
}dp[N],pd[N];
char s[N];
int main(){
	Ri(n);
	scanf("%s",s+1);
	s[0]=s[n+1]='@';
	rep(i,1,n){
		if(s[i]=='1'&&s[dp[i-1].p]=='0') dp[i].l=dp[i-1].l-1,dp[i].p=dp[dp[i-1].p-1].p,dp[i].q=dp[i-1].q-1;
		else dp[i].l=dp[i-1].l+1,dp[i].p=i,dp[i].q=dp[i-1].q+1-s[i]+'0';
		a[s[i]-'0'][i]=a[s[i]-'0'][i-1]+1;
		a[1-s[i]+'0'][i]=a[1-s[i]+'0'][i-1];
	}
	per(i,n,1)
		if(s[i]=='0'&&s[pd[i+1].p]=='1') pd[i].l=pd[i+1].l-1,pd[i].p=pd[pd[i+1].p+1].p,pd[i].q=pd[i+1].q-1;
		else pd[i].l=pd[i+1].l+1,pd[i].p=i,pd[i].q=pd[i+1].q+s[i]-'0';
	int ans=dp[n].l;	
	rep(i,0,n)
		rep(j,i+1,n+1)
			if((a[0][j-1]-a[0][i])&&(a[1][j-1]-a[1][i])==(a[0][j-1]-a[0][i])<<1){
				ans=min(ans,dp[i].l+pd[j].l-2*min(dp[i].q,pd[j].q));
			}
	printf("%d",ans);
	return 0;
}

CoolCool的序列

很容易想到实际花费只与每个数需要移动的距离有关
考虑三个相同的数按位置从小到大为 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3
那么翻转后的位置从小到大为 n − x 3 + 1 , n − x 2 + 1 , n − x 1 + 1 n-x_3+1,n-x_2+1,n-x_1+1 nx3+1,nx2+1,nx1+1
翻转后如何交换到 x 1 , x 2 , x 3 x_1,x_2,x_3 x1,x2,x3的花费最小呢?很显然就是第k小的配对第k小的
设期望移动到的位置为 x 1 , x 2 , . . . , x n x_1,x_2,...,x_n x1,x2,...,xn,那么使交换的总距离尽可能短,就每次都在原序列中找满足 x i ≥ j & x j ≤ i x_i≥j\&x_j≤i xij&xji的两个数交换,那么花费j-i,就可以使总共需要交换的距离减少2(j-i),因此只需要 ( Σ x n ) / 2 (Σx_n)/2 (Σxn)/2

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
const int N=1e5+5;
int a[N],n;
queue<int>b[N];
int main(){
	Ri(n);
	rep(i,1,n){
		Ri(a[i]);
		b[a[i]].push(i);
	}
	ll ans=0;
	per(i,n,1){
		ans+=abs(b[a[i]].front()-n+i-1);
		b[a[i]].pop();
	}
	printf("%lld",ans>>1);
	return 0; 
}

牛清楚的裙子!!!

概率期望题,每种裙子出现次数必然是相同的,推出出现次数的期望后即可得到答案

#include<bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef double db;
#define rep(i,s,t) for(int i=s;i<=t;i++)
#define per(i,s,t) for(int i=s;i>=t;i--)
#define ms0(ar) memset(ar,0,sizeof ar)
#define ms1(ar) memset(ar,-1,sizeof ar)
#define Ri(x) scanf("%d",&x)
#define Rii(x,y) scanf("%d%d",&x,&y)
const int N=1e7+5;
double f[N];
int main()
{
    rep(i,1,N-5) f[i]=f[i-1]+1.0/i;
    int t;
    Ri(t);
    while(t--){
        int n; 
		scanf("%d",&n);
        double ans=(n+9999)*f[n];
        printf("%.7f\n",ans);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值