2020牛客暑期多校训练营(第九场)

2020牛客暑期多校训练营(第九场)(2020.8.8)

刚刚补完上一篇博客,干脆今天不摸了,趁热打铁也写掉。

A、Groundhog and 2-Power Representation

一看是求表达式,再一看还要手搓大数。

“璇大师,上python!”

s = input()
print(eval(s.replace('(', "**(")))

是的,就这么短。我也傻了。

E、Groundhog Chasing Death

发现我还是适合做点数学题。比如这道。

插曲:本来可以半小时内AC,结果哇了。百思不得其解,觉得我的算法必对,然后把机子让给璇大师,去浩大师电脑上写暴力对拍了。

浩大师的笔记本键盘是往右偏的,实在用不惯。加上没有鼠标,写一个最简单的对拍程序居然用了将近一个多小时。之后为了找错误生成数据又搞了快一个小时。

结果发现double除零不报Re!!!!!

找到错误数据之后把机子抢过来,单步跟进去发现一个变量一步过后突然变成了将近-9e10我整个人都傻掉了。

最后半个小时才跟出来错误,半分钟改完交了。AC。心态血崩。

赛后看题解发现和题解思路完全一致。被这题卡了两小时,本来可以至少五题的结果只有四题了。

闲话说到这里,分析一下这题思路。

看到数的幂次求 g c d gcd gcd,容易往基本性质上面想。我们假设 x = p 1 α 1 p 2 α 2 . . . p n α n x=p_1^{\alpha_1}p_2^{\alpha_2}...p_n^{\alpha_n} x=p1α1p2α2...pnαn y = p 1 β 1 p 2 β 2 . . . p n β n y=p_1^{\beta_1}p_2^{\beta_2}...p_n^{\beta_n} y=p1β1p2β2...pnβn。为方便起见,这里相同下标对应的 p i p_i pi都是相同的。

所以 Π i = a b Π j = c d g c d ( x i , y j ) = Π i = a b Π j = c d Π k = 1 n p k m i n ( i α k , j β k ) \Pi_{i=a}^b\Pi_{j=c}^dgcd(x^i, y^j)=\Pi_{i=a}^b\Pi_{j=c}^d\Pi_{k=1}^np_k^{min(i\alpha_k,j\beta_k)} Πi=abΠj=cdgcd(xi,yj)=Πi=abΠj=cdΠk=1npkmin(iαk,jβk)

而当 k k k相同时,我们可以把前面的累乘符号塞到幂次上面,变成 Π k = 1 n p k Σ i = a b Σ j = c d m i n ( i α k , j β k ) \Pi_{k=1}^np_k^{\Sigma_{i=a}^b\Sigma_{j=c}^dmin(i\alpha_k,j\beta_k)} Πk=1npkΣi=abΣj=cdmin(iαk,jβk)

然后我们只要把 Σ i = a b Σ j = c d m i n ( i α k , j β k ) \Sigma_{i=a}^b\Sigma_{j=c}^dmin(i\alpha_k,j\beta_k) Σi=abΣj=cdmin(iαk,jβk)这玩意搞出来就好了。其中 α , β \alpha, \beta α,β可以看成被预处理出来的常数。

之后很容易想到分类讨论。若固定 i i i作为最小值,则 j j j在该范围内满足条件的个数是可以算出来的。因此整个求和式分成三大类情况: i α k < j β k i\alpha_kiαk<jβk i α k > j β k i\alpha_k>j\beta_k iαk>jβk i α k = j β k i\alpha_k=j\beta_k iαk=jβk。第三类其实可以和第一类并到一起。等下我的程序就是这么写的。所以对于第一类情况枚举 i i i确定 j j j的范围,第二类情况枚举 j j j确定 i i i的范围,最后和另一范围取交集就可以了。

不过这里还有一个问题。比如我们固定 i i i,得到 j ∈ [ p , q ] j\in[p, q] j[p,q]。那么这个 i i i对求和式的贡献为 i α k ( q − p + 1 ) i\alpha_k(q-p+1) iαk(qp+1)。我第一遍写代码的时候是枚举出一个答案就 q p o w qpow qpow一次,结果T了。后来仔细一算,如果每次都快速幂复杂度就多了个 l o g log log

这样下去不行的。所以我们在枚举当前素因子的时候新建一个变量 p w pw pw,记录当前素因子对答案的总贡献。那么每个素因子只要 q p o w qpow qpow一次就行了。但是这样累加 p w pw pw会爆ll。发现模数是个质数,利用费马小定理在加 p w pw pw的时候模个 M O D − 1 MOD-1 MOD1就好了。

最大的问题就是我这里为方便把 p i p_i pi写成相等的了,就造成幂次可能会有0。这时再利用除法确定范围会除0。解决方法只要加一个特判就完了。

下次double除法一定要检查除数是不是0…

#include 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MOD = 998244353;
const int MAXN = 1010, INF = 0x3f3f3f3f;
ll qpow(ll a, ll b)
{
    ll res = 1; a %= MOD;
    while (b)
    {
        if (b & 1) res = (res * a) % MOD;
        a = (a * a) % MOD; b >>= 1;
    }
    return res;
}
int main()
{
    ll a, b, c, d, x, y; scanf("%lld%lld%lld%lld%lld%lld", &a, &b, &c, &d, &x, &y);
    vector <ll> p;
    ll alpha[MAXN] = {0}, beta[MAXN] = {0};
    ll tempx = x, tempy = y, cnt = -1;
    for (ll i = 2; i * i <= tempx; ++i)
    {
        if (tempx % i == 0)
        {
            p.push_back(i);
            while (!(tempx % i)) tempx /= i;
        }
    }
    if (tempx != 1) p.push_back(tempx);
    for (ll i = 2; i * i <= tempy; ++i)
    {
        if (tempy % i == 0)
        {
            p.push_back(i);
            while (!(tempy % i)) tempy /= i;
        }
    }
    if (tempy != 1) p.push_back(tempy);
    sort(p.begin(), p.end()); p.erase(unique(p.begin(), p.end()), p.end());
    tempx = x, tempy = y;
    for (auto &i : p)
    {
        ++cnt;
        while (!(tempx % i)) ++alpha[cnt], tempx /= i;
        while (!(tempy % i)) ++beta[cnt], tempy /= i;
    }
    ll ans = 1;
    for (int i = 0; i <= cnt; ++i)
    {
        ll pw = 0, st = MOD - 1;
        for (int j = a; j <= b; ++j)
        {
            ll tar = alpha[i] * j;
            //特判除0
            ll left, rec = (beta[i] != 0? (ll)ceil((double)tar / beta[i]): INF);
            //取交集
            if (rec <= c) left = c;
            else if (rec > d) left = d + 1;
            else left = rec;
            pw = (pw + tar * (d - left + 1)) % st;
        }
        for (int j = c; j <= d; ++j)
        {
            ll tar = beta[i] * j;
            //特判除0
            ll left, rec = (alpha[i] != 0? (ll)ceil((double)(tar + 1) / alpha[i]): INF);
            //取交集
            if (rec <= a) left = a;
            else if (rec > b) left = b + 1;
            else left = rec;
            pw = (pw + tar * (b - left + 1)) % st;
        }
        ans = (ans * qpow(p[i], pw)) % MOD;
    }
    cout << ans << endl;
}

F、The Escape Plan of Groundhog

璇大师很快出了思路AC了。

不过周围几个队这题疯狂T。赛后交流发现是我们运气好,出题人卡的几个常数都没把璇大师卡掉。%%%

第一个点是map要用unordered_map。这玩意是个黑科技,内部用hash实现的。在不需要元素有序的情况下用这个比map效率高,均摊下来插入和查询都是 O ( 1 ) O(1) O(1)的。

第二个点是出题人卡了vector。这题用vector.size()返回元素个数会超时,原因在于调用成员函数时会产生额外耗时。解决方法就是开个cnt变量就完了。或者就干脆不用vector,直接上数组。

第三个点是要搞个快读。

懒得格式化了) 直接膜原汁原味的璇大师代码就完了。

#include 
using namespace std;
unordered_map<int,int>mp;
const int maxn=1e6+5;
namespace IO {
#define gc getchar()
#define pc(x) putchar(x)
    template<typename T>inline void read(T &x) {
        x=0;
        int f=1;
        char ch=gc;
        while(ch>'9'||ch<'0') {
            if(ch=='-')f=-1;
            ch=gc;
        }
        while(ch>='0'&&ch<='9')x=(x<<3)+(x<<1)+ch-'0',ch=gc;
        x*=f;
        return;
    }
    template<typename T>inline void write(T x=0) {
        T wr[51];
        wr[0]=0;
        if(x<0)pc('-'),x=-x;
        if(!x)pc(48);
        while(x)wr[++wr[0]]=x%10,x/=10;
        while(wr[0])pc(48+wr[wr[0]--]);
        return;
    }
}
using IO::read;
using IO::write;
int n,m,k,temp;
vector<pair<int,int>> vc;
int main()
{
    read(n);read(m);
    for(int i=1;i<=n;i++)
    {
     	read(k);
        for(int j=0;j<k;j++)
        {
            read(temp);
            vc.push_back({temp,i});
        }
    }
    sort(vc.begin(),vc.end());
    int i=0,j=0;
    int ans=0x3f3f3f3f,cnt;
    mp[vc[i].second]++;
    cnt=1;
    while(i<=j&&j<vc.size())
    {
        if(cnt<m)
        {
            j++;if(j==vc.size()) break;
            mp[vc[j].second]++;
            if(mp[vc[j].second]==1) cnt++;
        }
        else
        {
            if(cnt==m) ans=min(vc[j].first-vc[i].first,ans);
            i++;mp[vc[i-1].second]--;
            if(mp[vc[i-1].second]==0) cnt--;
        }
    }
    printf("%d\n",ans);
}

I、The Crime-solving Plan of Groundhog

本质还是个数学题。

考虑这个东西:

a 1 a 2 . . . a n ‾ ∗ b 1 b 2 . . . b m ‾ − a 1 a 2 . . . a n − 1 ‾ ∗ b 1 b 2 . . . b m a n ‾ = ( a 1 a 2 . . . a n − 1 ‾ ∗ 10 + a n ) ∗ b 1 b 2 . . . b m ‾ − a 1 a 2 . . . a n − 1 ‾ ∗ ( b 1 b 2 . . . b m ‾ ∗ 10 + a n ) = a n ( b 1 b 2 . . . b m ‾ − a 1 a 2 . . . a n − 1 ‾ ) \overline {a_1a_2...a_n}*\overline {b_1b_2...b_m}-\overline {a_1a_2...a_{n-1}}*\overline {b_1b_2...b_ma_n}\\= (\overline {a_1a_2...a_{n-1}}*10+a_n)*\overline {b_1b_2...b_m}-\overline {a_1a_2...a_{n-1}}*(\overline {b_1b_2...b_m}*10+a_n)\\=a_n(\overline {b_1b_2...b_m}-\overline {a_1a_2...a_{n-1}}) a1a2...anb1b2...bma1a2...an1b1b2...bman=(a1a2...an110+an)b1b2...bma1a2...an1(b1b2...bm10+an)=an(b1b2...bma1a2...an1)

所以当 b 1 b 2 . . . b m ‾ > a 1 a 2 . . . a n − 1 ‾ \overline {b_1b_2...b_m}>\overline {a_1a_2...a_{n-1}} b1b2...bm>a1a2...an1时,前面的值比后面的大。所以要想值最小,就要把现在要添加的数插在小的数的后面。

因为要整体最小,所以每一位肯定都是递增的。所以这两个数的第一位一定是该数列中除去0以外最小的两个元素。而通过上面的证明会发现之后添加的值一定总会添加在某一个数的后面,因为这玩意会越来越大。所以只要拆一个除0外最小的元素出来,把剩下的排个序搞一个无前导零的最小数就完了。

还有这题要手写int乘大数。之前写过的差不多忘了,赛场上靠着仅存的记忆瞎jb打居然过了。

#include 
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
const int MAXN = 1e6 + 10;
bool cmp(int a, int b)
{
    return a > b;
}
int main()
{
    int t; cin >> t;
    while (t--)
    {
        int n; scanf("%d", &n);
        vector <int> dig;
        int cnt = 0;
        for (int i = 1; i <= n; ++i)
        {
            int x; scanf("%d", &x);
            if (x) dig.push_back(x);
            else cnt++;
        }
        sort(dig.begin(), dig.end(), cmp);
        int temp = dig.back(); dig.pop_back();
        static char str[MAXN]; int p = 0;
        str[p++] = dig.back() + '0'; dig.pop_back();
        while (cnt--) str[p++] = '0';
        for (auto i = dig.rbegin(); i != dig.rend(); ++i) str[p++] = *i + '0';
        static char ans[MAXN]; int q = 0, t = 0;
        for (int i = p - 1; i >= 0 || t > 0; i--)
        {
            if (i >= 0) t += ((str[i] - '0') * temp);
            ans[q++] = t % 10 + '0';
            t /= 10;
        }
        while (ans[q - 1] == '0') q--;
        for (int i = q - 1; i >= 0; i--) printf("%d", ans[i] - '0');
        printf("\n");
    }
    return 0;
}

赛后总结:

1、python很关键。

2、double很关键。

3、常数很关键。

你可能感兴趣的:(2020暑期多校)