CodeCraft-21 and Codeforces Round #711 (Div. 2) A~E 题解

本场链接:CodeCraft-21 and Codeforces Round #711 (Div. 2)

A. GCD Sum

容易想到:出现互相都是偶数的时候,gcd>1。并且步数肯定也就几步,模拟一下即可。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

ll gcd(ll x,ll y)
{
    if(y == 0)  return x;
    return gcd(y,x % y);
}

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        ll n;scanf("%lld",&n);
        while(1)
        {
            int sum = 0;
            ll x = n;
            while(x)
            {
                sum += x % 10;
                x /= 10;
            }
            if(gcd(n,sum) > 1)  break;
            ++n;
        }
        printf("%lld
",n);
    }
    return 0;
}

B. Box Fitting

一个粗暴的想法:贪心的分配每个砖块,只要能塞在一行就塞。进而不难想到应该先从较大的开始放,维护一个堆,每次取最小值看能不能塞,能塞就塞进去,不能塞就另开一行。

正确性:大概要用到(w)(2^x)的性质,我只感性的想到一点,有疑问建议看官方题解。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 1e5+7;
int w[N];

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int n,W;scanf("%d%d",&n,&W);
        forn(i,1,n) scanf("%d",&w[i]);
        sort(w + 1,w + n + 1);reverse(w + 1,w + n + 1);
        priority_queue<int,vector<int>,greater<int>> pq;
        forn(i,1,n)
        {
            if(pq.empty() || pq.top() + w[i] > W)   pq.push(w[i]);
            else
            {
                int f = pq.top();pq.pop();
                pq.push(f + w[i]);
            }
        }
        printf("%d
",(int)pq.size());
    }
    return 0;
}

C. Planar Reflections

在模拟了一些样例之后可以发现,只有(k-1)半衰期的粒子会多出一个直接飞到外面,其他的时候粒子都会处于第一个平面右侧/最后一个平面左侧,也就是形如在内部不断地弹射。接下来可以考虑一个递推做法:维护在”每个平面上“的粒子个数(因为粒子并不真的在平面上而是在侧面),可以发现当前的操作次数的奇偶性决定了所有半衰期相同的粒子飞行的方向,并且固定的方向会导致他们的贡献形如做前缀/后缀和累加。模拟一下比较多次的样例就比较好理解了。

注意对于(k-1)半衰期的粒子会跑出去一个,答案多一个。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 1007,MOD = 1e9+7;
ll a[N],s[N];

int main()
{
    int T;scanf("%d",&T);
    while(T--)
    {
        int n,k,step = 0;scanf("%d%d",&n,&k);
        forn(i,0,n + 7) a[i] = 0;
        forn(i,1,n - 1) a[i] = 1;
        ll res = 1;--k;
        if(k > 0)   --k,res += n;
        while(k > 0)
        {
            if(!step)
            {
                forr(i,1,n - 1) a[i] = (a[i] + a[i + 1]) % MOD;
                forn(i,1,n - 1) res = (res + a[i]) % MOD;
            }
            else
            {
                forn(i,1,n - 1) a[i] = (a[i] + a[i - 1]) % MOD;
                forn(i,1,n - 1) res = (res + a[i]) % MOD;
            }
            step ^= 1;
            --k;
        }
        printf("%lld
",res);
    }
    return 0;
}

D. Bananas in a Microwave

他十分形似多重背包,但是二进制拆分和单调队列优化似乎都不能有效地处理第二种操作。那么:暴力。

枚举当前处理到的询问个数,并且对于每个在之前已经可以恰好凑出来的答案,尝试将之转移到新的答案(同时跳过还没有答案的位置),转移:暴力枚举即可。

为什么这是正确的呢?首先对于操作二,乘数>1,显然可以接受。其次对于操作一:如果每次都加一个最小的数,那么每次都会让答案拓展(y)个,那么拓展的越多枚举跳过的也就越多,这样剪枝之后跑得非常快。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 1e6+7,INF = 1e9;
int ans[N];

int main()
{
    int n,m;scanf("%d%d",&n,&m);
    forn(i,1,N - 1) ans[i] = INF;

    forn(cur,0,n - 1)
    {
        ll t,x,y;scanf("%lld%lld%lld",&t,&x,&y);
        forr(i,0,m)
        {
            if(ans[i] == INF)   continue;
            ll z = i;
            forn(j,0,y - 1)
            {
                if(t == 1)  z = z + (x + 100000 - 1) / 100000;
                else        z = (z * x + 100000 - 1) / 100000;
                if(z > m)   break;
                if(ans[z] < INF)    break;
                ans[z] = cur + 1;
            }
        }
    }

    forn(i,1,m) printf("%d ",ans[i] == INF ? -1 : ans[i]);puts("");
    return 0;
}

E. Two Houses

首先一个比较容易想到的点是:应该构想一个总是询问不是a->b的边,也就是控制它不是Yes结果的策略。但是这还没有什么关键信息。

什么时候会出现两者都可以访问呢?有向图比较容易联想到SCC。如果我们在一个已经执行了SCC缩点的图上做这个问题,加上题目给了入度这一信息可以联想出一个结论:在SCC缩点过后的图上,如果一个连通分量拓扑序在更前面,那么它里面所有的点的入度一定小宇所有拓扑序更大的连通分量的节点。不妨设入度较大的点是(a),另一点是(b)。如果(a->b)的询问是Yes,那么就说明两者必然是互相可达的。当然,如果两者在同一个SCC里就更显然了。

所以只需枚举所有可能的对,看有没有入度偏大且较小者能连向较大者的点对存在。

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define forn(i,x,n) for(int i = x;i <= n;++i)
#define forr(i,x,n) for(int i = n;i >= x;--i)
#define Angel_Dust ios::sync_with_stdio(0);cin.tie(0)

const int N = 505;
struct Node
{
    int v,f,e;
    bool operator<(const Node& r)   const
    {
        return v < r.v;
    }
};
int deg[N];

int main()
{
    Angel_Dust;
    int n;cin >> n;
    forn(i,1,n) cin >> deg[i];

    vector<Node> a;
    forn(i,1,n) forn(j,i + 1,n)
    {
        if(deg[i] - deg[j] > 0) a.push_back({deg[i] - deg[j],i,j});
        else    a.push_back({deg[j] - deg[i],j,i});
    }

    sort(a.begin(),a.end());reverse(a.begin(),a.end());

    for(auto& _ : a)
    {
        int u = _.f,v = _.e;
        cout << "? " << u << " " << v << endl;
        string s;cin >> s;
        if(s[0] == 'Y') return cout << "! " << u << " " << v << endl,0;
    }

    cout << "! 0 0" << endl;
    return 0;
}
原文地址:https://www.cnblogs.com/HotPants/p/14597592.html