2020 年百度之星·程序设计大赛 - 初赛三 题解

T1

i i i 种套餐在第一次吃饭时省了 ( 1 − c i ) × a (1-c_i)\times a (1ci)×a 元,本来应该付的钱就是 b i × a + ( 1 − c ) × a b_i\times a +(1-c)\times a bi×a+(1c)×a,除一下得到优惠比例 1 − c i b i + ( 1 − c i ) \dfrac {1-c_i} {b_i+(1-c_i)} bi+(1ci)1ci

话说这题我居然看了三分钟才看懂题意啊……(最后还是靠一手样例才get到它的意思qwq)

代码如下:

#include 

int T,n;

int main()
{
    scanf("%d",&T);while(T--)
    {
        scanf("%d",&n);
        int x;double y,ans=0;
        for(int i=1;i<=n;i++){
            scanf("%d %lf",&x,&y);y=1.0-y;
            if(y/(1.0*x+y)>ans)ans=y/(1.0*x+y);
        }
        printf("%.5lf\n",ans);
    }
}

T2

假如 p > 1.0 p>1.0 p>1.0,那么肯定拿到了多的那一堆,肯定不交换。

假如 p ≤ 1.0 p\leq 1.0 p1.0,简单计算一下,可以知道交换后得到的金币数量期望为 1 2 × p 2 + 1 2 × 2 p = 5 4 p > p \dfrac 1 2\times \dfrac p 2+\dfrac 1 2\times 2p=\dfrac 5 4p>p 21×2p+21×2p=45p>p,所以要换。

代码如下:

#include 

int T;double n;

int main()
{
    scanf("%d",&T);while(T--)
    {
        scanf("%lf",&n);
        if(n<=1.0)printf("Yes\n");
        else printf("No\n");
    }
}

T3

容易发现,每次肯定交换头尾的两个数字,先换 1 , n 1,n 1,n,再换 2 , n − 1 2,n-1 2,n1,以此类推,这样的逆序对数一定是最大的,然后推一推每次交换的贡献即可。

代码如下:

#include 

int T;
long long n,m;

int main()
{
    scanf("%d",&T);while(T--)
    {
        scanf("%lld %lld",&n,&m);
        if(m>=n/2)printf("%lld\n",n*(n-1)/2ll);
        else printf("%lld\n",m+2ll*n*m-2*m*(m+1));
    }
}

T4

考虑拐角处的四个位置,设左上角坐标为 ( 1 , 1 ) (1,1) (1,1)

每个时刻,在 ( 1 , 2 ) (1,2) (1,2) ( 2 , 2 ) (2,2) (2,2) 的车肯定右移一步直接到达线 y y y 东边, ( 1 , 1 ) (1,1) (1,1) 位置上的车也肯定右移一步因为别无选择,需要考虑的是 ( 2 , 1 ) (2,1) (2,1) 上的车。

假如在拐角的下面还有车,那么让 ( 2 , 1 ) (2,1) (2,1) 上的车挪到 ( 1 , 1 ) (1,1) (1,1) 位置肯定不亏,因为如果挪到 ( 2 , 2 ) (2,2) (2,2),有可能挡住下面的车。

然后再判断一下有没有车能填到 ( 1 , 2 ) (1,2) (1,2) ( 2 , 2 ) (2,2) (2,2) 位置即可,代码如下:

#include 
#include 
#include 
#include 
using namespace std;

int T,n,a[3][3];
vector<int>l,r;
bool check(){
    for(int i=1;i<=2;i++)
    for(int j=1;j<=2;j++)if(a[i][j])return true;
    return l.size()>0||r.size()>0;
}
bool cmp(int x,int y){return x>y;}

int main()
{
    scanf("%d",&T);while(T--)
    {
        l.clear();r.clear();
        scanf("%d",&n);
        for(int i=1,id,x;i<=n;i++){
            scanf("%d %d",&id,&x);
            if(id==1)r.push_back(x);
            else l.push_back(x);
        }
        sort(l.begin(),l.end(),cmp);
        sort(r.begin(),r.end(),cmp);
        int ans=0;while(check()){
            ans++;
            a[1][2]=a[2][2]=0;
            a[1][2]=a[1][1];a[1][1]=0;
            if(l.size()||r.size())a[1][1]=a[2][1];
            else a[2][2]=a[2][1];
            a[2][1]=0;
            if(l.size()&&l.back()-ans<=0)a[2][1]=1,l.pop_back();
            if(r.size()&&r.back()-ans<=0)a[2][2]=1,r.pop_back();
        }
        printf("%d\n",ans);
    }
}

T5

容易发现,不管传送器设置传送到哪里,我们都只关心:是否存在连续 11 11 11 个传送器。

如果存在,那么肯定不能跨过,如果不存在,那么一定可以到达终点,于是dp一下即可。

代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 1010
#define mod 1000000007

int T,n,m;
int f[maxn][maxn];//f[i][j]表示前i位放j个传送器,第i位不放传送器,且不存在11个连续传送器的方案数
void add(int &x,int y){x=(x+y>=mod?x+y-mod:x+y);}
void dp(){
    f[1][0]=1;
    for(int i=2;i<=maxn-10;i++){
        f[i][0]=1;
        for(int j=1;j<=maxn-10;j++){
            if(j>i-2){f[i][j]=-1;continue;}
            //k枚举的是上一个没有传送器的位置,且k+1~i-1全是传送器
            //fac表示k+1~i-1中间的传送器有多少种设置传送地点的方案
            for(int k=i-1,fac=1;i-k-1<11&&i-k-1<=j&&k>=1;fac=1ll*fac*(k-1)%mod,k--)
            if(f[k][j-(i-k-1)]!=-1)add(f[i][j],1ll*f[k][j-(i-k-1)]*fac%mod);
            if(!f[i][j])f[i][j]=-1;
        }
    }
}

int main()
{
    scanf("%d",&T);dp();
    while(T--){
        scanf("%d %d",&n,&m);
        printf("%d\n",f[n][m]);
    }
}

T6

如果要让第二只蚂蚁概率大于等于 1 2 \dfrac 1 2 21,那么在第一只蚂蚁走过的路上, 1 1 1 到石榴的路径上分叉不超过 1 1 1 处,枚举一下分叉点计算一下概率就好了。

代码如下:

#include 
#include 
#include 
using namespace std;
#define maxn 100010
#define mod 1000000007

int T,n,m,du[maxn];
struct edge{int y,next;}e[maxn<<1];
int first[maxn],len;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
int fa[maxn];void dfs(int x){
	for(int i=first[x],y;i;i=e[i].next)
	if((y=e[i].y)!=fa[x])fa[y]=x,dfs(y);
}
int add(int x){return x>=mod?x-mod:x;}

int main()
{
	scanf("%d",&T);while(T--)
	{
		scanf("%d %d",&n,&m);
		memset(first,0,(n+1)<<2);len=0;
		memset(du,0,(n+1)<<2);
		for(int i=1,x,y;i<n;i++)scanf("%d %d",&x,&y),
		buildroad(x,y),buildroad(y,x),du[x]++,du[y]++;
		if(m==1){printf("1\n");continue;}
		dfs(1);
		int dir=1,ans=0;
		for(int i=fa[m];i;i=fa[i])dir=1ll*dir*ksm(du[i],mod-2)%mod;
		ans=dir;
		for(int i=fa[m];i!=1;i=fa[i])
		if(du[i]>2)ans=add(ans+1ll*dir*(du[i]-2)%mod*ksm(du[i]-1,mod-2)%mod);
		if(du[1]>1)ans=add(ans+dir);
		printf("%d\n",ans);
	}
}

T7

设攻击力最大的人为 a a a,次大的为 b b b,另一个是 c c c

考虑贪心,要让次数最少,肯定先让 a a a b b b 打, b b b 死了之后再让 a a a c c c 打。

有一种特殊情况,假如 a a a b , c b,c b,c 的攻击力加起来都大,那么 a a a 是可以一个人k死另外两个人的,此时 a a a 可能会对 b , c b,c b,c 造成过量伤害,这其实是不优的,考虑让 a a a 少和 b , c b,c b,c 打几轮,取而代之的,让 b , c b,c b,c 互博几轮,这可能是更优的,枚举一下 b , c b,c b,c 互博的轮数即可。

举个例子,三个人攻击力分别为 3 , 1 , 1 3,1,1 3,1,1,当 a a a b , c b,c b,c 打剩 1 1 1 滴血时,让他们互博一次,那么他们就都死了,不需要 a a a 分别k死他们两个。

时间复杂度为 O ( t ) O(t) O(t) ~ O ( t n ) O(tn) O(tn),但是我比较懒……互博的过程是可以 O ( 1 ) O(1) O(1) 实现的,我写了个 O ( n ) O(n) O(n) 的,但是还是可以无压力 0 s 0s 0s 跑过。

代码如下:

#include 
#include 
using namespace std;

int T,tot;
struct par{int x,y;}a[10];
bool cmp(par x,par y){return x.y>y.y;}
void fight(int x,int y){a[x].x-=a[y].y;a[y].x-=a[x].y;tot++;}

int main()
{
    scanf("%d",&T);while(T--)
    {
        scanf("%d %d %d",&a[1].y,&a[2].y,&a[3].y);
        a[1].x=a[2].x=a[3].x=1000;tot=0;int ans=999999999;
        sort(a+1,a+4,cmp);
        if(a[1].y>=a[2].y+a[3].y){
            for(int i=0;i<=1000;i++){
                a[1].x=a[2].x=a[3].x=1000;tot=i;
                a[2].x-=a[3].y*i;a[3].x-=a[2].y*i;
                if(a[i].x+a[3].y<=0||a[3].x+a[2].y<=0)break;
                while(a[1].x>0&&a[2].x>0)fight(1,2);
                while(a[1].x>0&&a[3].x>0)fight(1,3);
                ans=min(ans,tot);
            }
        }else{
            while(a[1].x>0&&a[2].x>0)fight(1,2);
            while(a[1].x>0&&a[3].x>0)fight(1,3);
            ans=tot;
        }
        printf("%d\n",ans);
    }
}

T8

赛时并没有做出来,因为太菜了。

限制相当于,每个环内都有奇数个奇数。观察可以发现,图一定是仙人掌。

证明用反证法,考虑两个环的公共部分,设它包含 a a a 个奇数,两个环的不公共部分分别含有 b , c b,c b,c 个奇数,那么有 a + b ≡ 1 ( m o d 2 ) , a + c ≡ 1 ( m o d 2 ) a+b\equiv 1\pmod 2,a+c\equiv 1\pmod 2 a+b1(mod2),a+c1(mod2),则有 2 a + b + c ≡ 0 ( m o d 2 ) 2a+b+c\equiv 0\pmod 2 2a+b+c0(mod2),模 2 2 2 2 a 2a 2a 会被模掉,相当于 b + c ≡ 0 ( m o d 2 ) b+c\equiv 0 \pmod 2 b+c0(mod2),即存在一个不满足要求的简单环,所以任意两个环不可能有公共边。

然后可以对每个环构造一个多项式, x i x^i xi 的系数表示放 i i i 个奇数的方案数。设环的大小为 S S S i i i 为奇数时, x i x^i xi 的系数为 C S i C_S^i CSi,表示选出 i i i 个位置放奇数,否则 x i x^i xi 的系数为 0 0 0

对于非环内边则可放奇数可不放奇数,构造的多项式为 1 + x 1+x 1+x,然后将所有多项式乘起来,取 x ⌈ n 2 ⌉ x^{\lceil \frac n 2 \rceil} x2n 的系数就是答案。

最后还要乘以 ⌈ n 2 ⌉ ! × ⌊ n 2 ⌋ ! \lceil \frac n 2 \rceil !\times \lfloor \frac n 2 \rfloor! 2n!×2n!,因为每个奇数和每个偶数都是不同的,上面只考虑了给他们安排位置。

代码如下:

#include 
#include 
#include 
#include 
#include 
using namespace std;
#define maxn 300010
#define mod 998244353
#define bin(x) (1<<(x))

int T,n,m;
struct edge{int y,next;}e[maxn];
int first[maxn],len=0;
void buildroad(int x,int y){e[++len]=(edge){y,first[x]};first[x]=len;}
int deep[maxn],fa[maxn];
void dfs(int x){
	for(int i=first[x];i;i=e[i].next)
	if(e[i].y!=fa[x])deep[e[i].y]=deep[x]+1,fa[e[i].y]=x,dfs(e[i].y);
}
bool vis[maxn];
int go(int x,int y)
{
	if(deep[x]>deep[y])swap(x,y); int re=1;
	while(deep[y]>deep[x]){
		if(vis[y])return -1;
		vis[y]=true; y=fa[y],re++;
	}
	while(x!=y){
		if(vis[x]||vis[y])return -1;
		vis[x]=vis[y]=true;
		x=fa[x];y=fa[y];re+=2;
	}
	return re;
}
struct BCJ{
	int Fa[maxn];void init(int N){for(int i=1;i<=N;i++)Fa[i]=i;}
	int findfa(int x){return x==Fa[x]?x:Fa[x]=findfa(Fa[x]);}
	bool link(int x,int y){
		x=findfa(x),y=findfa(y);
		if(x==y)return false;
		Fa[y]=x;return true;
	}
}K;
struct par{int x,y;};
vector<par>h;
int add(int x){return x>=mod?x-mod:x;}
int dec(int x){return x<0?x+mod:x;}
int inv[maxn];
int ksm(int x,int y){int re=1;for(;(y&1?re=1ll*re*x%mod:0),y;y>>=1,x=1ll*x*x%mod);return re;}
struct NTT{
	int w[maxn];NTT(){int N=bin(18);
		inv[1]=1;for(int i=2;i<=N;i++)inv[i]=1ll*(mod-mod/i)*inv[mod%i]%mod;
		for(int i=1,wn;i<N;i<<=1){
			w[i]=1;wn=ksm(3,(mod-1)/(i<<1));
			for(int j=1;j<i;j++)w[i+j]=1ll*w[i+j-1]*wn%mod;
		}
	}
	int limit,r[maxn];
	void work(int lg){r[0]=0;for(int i=1;i<bin(lg);i++)r[i]=(r[i>>1]>>1)|((i&1)<<(lg-1));}
	void dft(int *f,int lg,int type=0)
	{
		limit=bin(lg);if(type)reverse(f+1,f+limit);
		for(int i=1;i<limit;i++){
			if(i<r[i])swap(f[i],f[r[i]]);
		}
		for(int mid=1;mid<limit;mid<<=1)for(int j=0;j<limit;j+=(mid<<1))for(int i=0;i<mid;i++)
		{int t=1ll*f[j+i+mid]*w[i+mid]%mod;f[j+i+mid]=dec(f[j+i]-t);f[j+i]=add(f[j+i]+t);}
	}
}ntt;
int A[maxn],B[maxn];
struct POLY{
	vector<int> a;int len;void rs(int N){a.resize(len=N);}POLY(){len=1;a.clear();}
	int &operator [](int x){return a[x];}
	void dft(int *A_,int lg,int ln){for(int i=0;i<bin(lg);i++)A_[i]=(i<min(ln,len)?a[i]:0);ntt.dft(A_,lg);}
	void idft(int *A_,int lg,int ln){ntt.dft(A_,lg,1);rs(ln);for(int i=0;i<ln;i++)a[i]=1ll*A_[i]*inv[bin(lg)]%mod;}
	POLY Mul(POLY b,int ln=0){
		if(!ln)ln=len+b.len-1;int lg=ceil(log2(ln));
		ntt.work(lg);dft(A,lg,ln);b.dft(B,lg,ln);
		for(int i=0;i<bin(lg);i++)A[i]=1ll*A[i]*B[i]%mod;b.idft(A,lg,ln);return b;
	}
}F[maxn];
int fac[maxn],inv_fac[maxn];
void work(){
	fac[0]=inv_fac[0]=1;for(int i=1;i<=maxn-10;i++)fac[i]=1ll*fac[i-1]*i%mod;
	inv_fac[maxn-10]=ksm(fac[maxn-10],mod-2);
	for(int i=maxn-11;i>=1;i--)inv_fac[i]=1ll*inv_fac[i+1]*(i+1)%mod;
}
int C(int x,int y){return 1ll*fac[x]*inv_fac[y]%mod*inv_fac[x-y]%mod;}
void solve(int l,int r){
	if(l==r)return;
	int mid=(l+r)>>1;solve(l,mid);solve(mid+1,r);
	F[l]=F[l].Mul(F[mid+1]);
}

int main()
{
	scanf("%d",&T);work();while(T--)
	{
		scanf("%d %d",&n,&m);
		h.clear();K.init(n);
		memset(vis,false,sizeof(vis));
		memset(first,0,sizeof(first));len=0;
		for(int i=1,x,y;i<=m;i++){
			scanf("%d %d",&x,&y);
			if(!K.link(x,y))h.push_back((par){x,y});
			else buildroad(x,y),buildroad(y,x);
		}
		dfs(1); int t,tot=0;
		for(int i=0;i<h.size();i++){
			t=go(h[i].x,h[i].y);
			if(t==-1)break;
			F[++tot].rs(t+1);
			for(int j=0;j<=t;j++)F[tot][j]=(j&1?C(t,j):0);
		}
		for(int i=2;i<=n;i++)if(!vis[i]){
			F[++tot].rs(2);
			F[tot][0]=F[tot][1]=1;
		}
		if(t==-1){printf("0\n");continue;}
		solve(1,tot);
		printf("%d\n",1ll*F[1][(m+1)/2]*fac[(m+1)/2]%mod*fac[m/2]%mod);
	}
	return 0;
}

你可能感兴趣的:(随笔小结)