[2020年百度之星·程序设计大赛-初赛三]Coda的题解集

这三场比赛复盘感觉题还挺简单的,但是赛场上想不到正确的方法,大概是题做少了。

第一场和第三场都是Rank1100+,第二场也不知道为什么,两题就混进了Rank600+。

复赛准备白给。


Discount

纯模拟,取最大值。

#pragma GCC optimize(2)
#include
using namespace std;
const int maxn=110;
int t,n,a[maxn];
double ans,b[maxn];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>t;
    while(t--)
    {
        ans=0;
        cin>>n;
        for(int i=1;i<=n;++i)
        {
            cin>>a[i]>>b[i];
            b[i]=1-b[i];
            ans=max(ans,b[i]/(a[i]+b[i]));
        }
        cout<<fixed<<setprecision(5)<<ans<<endl;
    }
}

Game

根据题意,如果拿到大于一元,一定是拿到了2x那一堆,直接不换。如果小于等于一元,设拿到的是n元,是x和2x的几率都是一半,期望为0.5x2n+0.5x0.5n=1.25n。因为期望大于n,所以换。

#pragma GCC optimize(2)
#include
using namespace std;
const int maxn=110;
int t;
double num;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>t;
    while(t--)
    {
        cin>>num;
        if(num>1)
            cout<<"No"<<endl;
        else
            cout<<"Yes"<<endl;
    }
}

Permutation

我曾经在极度愤怒的状态下因为忘记把freopen注释掉把自己WA得心态爆炸。
对于1…n的序列,当我们交换1与n后,n作为整个序列中最大的元素,与之后的n-1个元素各组成一个逆序对,而1作为最小的序列,与它之前的所有n-1个元素也各形成一个逆序对,但是请注意n与1和1与n的逆序对重复了,所以最终形成了n-2个逆序对。可以想到,只要n一直在第一位,无论后面数字的顺序怎么改变,形成的逆序对数是一定的,1同理。由此我们得到了(n-1)+(n-2)个逆序对和一条去除了1和n的,长度为n-2的序列2…n-1,再对它做上述一模一样的操作,得到n-3个逆序对,n-4个逆序对等等。最终变成了简单的前缀和问题。

#pragma GCC optimize(2)
#include
#define int long long
using namespace std;
const int maxn=1e6+7;
int t,n,m,sum[maxn];
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    for(int i=1;i<maxn;++i)
        sum[i]=sum[i-1]+i;
    cin>>t;
    while(t--)
    {
        cin>>n>>m;
        if(m>(n/2)) m=n/2;
        cout<<sum[n-1]-sum[((n-2*m-1)>0) ? (n-2*m-1) : 0]<<endl;
    }
}

Intersection

模拟,在最优情况下右车道的车始终沿着右车道行驶最快,而对于左车道的每一辆车,如果正右后方那个位置没车,就可以在刚过线时变道到右车道,少花费1点时间。根据以上分析,不存在停顿等待的情况,此时可以认为只有每个车道的最后一辆车对整体的结束时间有影响,所以只需要算出他们两的时间,取更大的一个即可。注意不要忘记过滤空车道。

#include "bits/stdc++.h"
using namespace std;
const int maxn=1e5+7;
int cas,n,x,y,l[maxn],r[maxn],lastl,lastr;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>cas;
    while(cas--)
    {
        lastl=lastr=0;
        memset(l,0,sizeof(l));
        memset(r,0,sizeof(r));
        cin>>n;
        for(int i=1;i<=n;++i)
        {
            cin>>x>>y;
            if(x==1)
                r[y]=true,lastr=max(lastr,y);
            else
                l[y]=true,lastl=max(lastl,y);
        }
        if(lastr) lastr+=1;
        if(lastl)
            if(!r[lastl+1]) lastl+=2;
            else lastl+=3;
        cout<<max(lastl,lastr)<<endl;
    }
}

Chess

DP,dp[i][j][k]存储到第i格为止,一共放掉j个传送门,到第i格连续传送门的长度。然后推状态转移方程:如果第i格放传送门,一定是由前一个状态的ijk分别+1得来的,即为dp[i][j][k]=dp[i-1][j-1][k-1]。如果第i格不放传送门,则可以是由dp[i-1][j]的任意一个k得到,题面中求的是加和,于是对k从0到10的dp[i-1][j][k]求和得出(到10的原因是筛子最多掷出11,而且传送门只能后退不能前进,且会门套门,直到回退到一个没有传送门的方块作为落脚点为止,前一个落脚点必定在10格以内,否则就会永远无法到达终点)。观察到有多组数据且状态通用,开局跑完maxn然后O(1)查询。

#include "bits/stdc++.h"
using namespace std;
const int mod=1e9+7;
const int maxn=1001;
int cas,n,m;
long long dp[maxn][maxn][11];
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    n=m=1000;
    dp[1][0][0]=1;
    for(int i=2;i<=n;++i)
        for (int j = 0; j <= min(i - 1, m); ++j)
        {
            long long tmp = 0;
            for (int k = 1; k <= min(j, 10); ++k) {
                dp[i][j][k] = dp[i - 1][j - 1][k - 1] * (i - 1);
                dp[i][j][k]%=mod;
                tmp += dp[i - 1][j][k];
                tmp%=mod;
            }
            dp[i][j][0] = tmp+dp[i-1][j][0];
            dp[i][j][0]%=mod;
        }
    cin>>cas;
    while(cas--)
    {
        cin>>n>>m;
        if(!dp[n][m][0])
            cout<<"-1"<<endl;
        else
            cout<<dp[n][m][0]<<endl;
    }
}

Ant

最开始特判写在了读图前面,导致输入错位MLE了十几发,服了。

根据题意,图为一颗树,且对于任意有边连接的u和v两点,从u到v和从v到u都只能走一遍。如果第一只蚂蚁走到错误的分支上,必然会折返回正确的道路中。第二只蚂蚁但凡走歪就会失败,每个点不失误的概率是1/(该点出边中第一只蚂蚁走过的数目),成功的概率为最短路上每个点的概率之积。可以算出,第一只蚂蚁从最短路上走歪的次数为1时,第二只蚂蚁的成功概率为1/2,所以我们只需要算第一只蚂蚁从最短路上最多只会走歪一次的概率。

DFS记录父节点,再从m走到1,沿路计算概率,需要使用逆元,详见注释。

#include "bits/stdc++.h"
using namespace std;
typedef long long ll;
const int maxn=1e5+7;
const int mod=1e9+7;
int cas,n,m,u,v,fa[maxn];
ll a,b;
vector<int> g[maxn];
ll mod_pow(ll x,ll n)
{
    ll res=1;
    while(n>0)
    {
        if(n&1) res=res*x%mod;
        x=x*x%mod;
        n>>=1;
    }
    return res;
}
ll inv(ll x)
{
    return mod_pow(x,mod-2);
}
void dfs(int x,int father)
{
    fa[x]=father;
    for(auto v:g[x])
        if(v!=father)
            dfs(v,x);
}
signed main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>cas;
    while(cas--)
    {
        cin>>n>>m;
        a=1,b=0;
        for(int i=1;i<=n;++i)
            g[i].clear();
        for(int i=1;i<n;++i)
        {
            cin>>u>>v;
            g[u].push_back(v);
            g[v].push_back(u);
        }
        if(m==1)
        {
            cout<<"1"<<endl;
            continue;
        }
        dfs(1,0);
        for(int i=fa[m];i!=0;i=fa[i])
        {
            int sz=g[i].size();
            //在i点未走歪
            a=a*inv(sz)%mod;//在i之前尚未走歪过
            b=b*inv(sz)%mod;//在i之前已经走歪过一次
            //在i点走歪了
            b=b+a*(sz-1-(i!=1))%mod*inv(sz-1)%mod;//曾经走歪的概率+曾经未走歪的概率*i点走歪的概率=总的走歪概率
        }
        cout<<(a+b)%mod<<endl;
    }
}

Fight

每个case都是O(n^2)暴力枚举轮数,然后判断合法就和已知的ans取min。

#include "bits/stdc++.h"
using namespace std;
int cas,a[4],ans;
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cin>>cas;
    while(cas--)
    {
        ans=3000;
        cin>>a[1]>>a[2]>>a[3];
        sort(a+1,a+3+1);
        for(int i=0;i<=1000;++i)
            for(int j=0;j<=1000;++j)
            {
                int tmp=i+j;
                int hp1=1000-i*a[2]-j*a[3];
                int hp2=1000-i*a[1];
                int hp3=1000-j*a[1];
                if(hp2>0&&hp3>0)
                {
                    //int rnd=min(hp2/a[3]+((hp2%a[3])?1:0),hp3/a[2]+((hp3%a[2])?1:0));
                    //上面是自己写的,下面是别人代码学来的,更方便的除法向上取整。
                    int rnd=min((hp2+a[3]-1)/a[3],(hp3+a[2]-1)/a[2]);
                    hp2-=a[3]*rnd;
                    hp3-=a[2]*rnd;
                    tmp+=rnd;
                }
                if((hp1<1&&hp2<1)||(hp1<1&&hp3<1)||(hp2<1&&hp3<1))
                    ans=min(ans,tmp);
            }
        cout<<ans<<endl;
    }
}

Graph

标答给的启发式合并NTT,没学过暂不补。

你可能感兴趣的:([2020年百度之星·程序设计大赛-初赛三]Coda的题解集)