The 2020 ICPC Asia Shanghai Regional Contest

Preface

开启狂暴(存疑)加训模式,国内近些年来的比赛基本都打过了,翻了半天翻出来一场 20 年的上海

这场总体打的还算挺顺,虽然中期唐了 E 题推了个错的式子扔给徐神优化成功卡了徐神半小时,但好在后面写了个暴力发现式子推错马上改出来了

后期两个题没有证明全是猜测,大力乱冲结果都过了,这就是 guess 之力啊


B. Mine Sweeper II

队友开场秒的,我题目都没看

#include<bits/stdc++.h>
using namespace std;

const int N = 1005;
int n, m;
string A[N], B[N];

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    for (int i=0; i<n; ++i) cin >> A[i];
    for (int i=0; i<n; ++i) cin >> B[i];
    int cnt = 0;
    for (int i=0; i<n; ++i) for (int j=0; j<m; ++j) if (A[i][j]!=B[i][j]) ++cnt;
    if (cnt > n*m/2) for (int i=0; i<n; ++i) for (int j=0; j<m; ++j) A[i][j] = (A[i][j]=='.' ? 'X' : '.');
    for (int i=0; i<n; ++i) cout << A[i] << '\n'; 
    return 0;
}

C. Sum of Log

很典的数位 DP,要计算贡献只要记录 highbit 即可,单组数据复杂度 \(O(\log X\log Y)\)

但这题在 QOJ 上很卡常,最后换了读优才过

#include <bits/stdc++.h>

using llsi = long long signed int;

llsi dp[30][30][2][2];
llsi X, Y;

#define Tp template <typename T>
class FileInputOutput
{
    private:
        static const int S=1<<21;
        #define tc() (A==B&&(B=(A=Fin)+fread(Fin,1,S,stdin),A==B)?EOF:*A++)
        char Fin[S],*A,*B;
    public:
        Tp inline void read(T& x)
        {
            x=0; char ch; while (!isdigit(ch=tc()));
            while (x=(x<<3)+(x<<1)+(ch&15),isdigit(ch=tc()));
        }
        #undef tc
}F;


llsi dfs(int cur, int high, int x_limit, int y_limit) {
    if(cur < 0) {
        // if(debug) std::cerr << "debug " << high << char(10);
        return high + 1;
    }
    if(high >= 0 && dp[cur][high][x_limit][y_limit] >= 0) {
        // if(debug) std::cerr << "use cache dp["<< cur << "][" << high << "]\n";
        return dp[cur][high][x_limit][y_limit];
    }
    llsi ans = 0;
    for(int x = 0; x <= (x_limit ? (X >> cur & 1) : 1); ++x) {
        for(int y = 0; y <= (y_limit ? (Y >> cur & 1) : 1); ++y) {
            if(x && y) continue;
            int nxl = x_limit && (x == (X >> cur & 1));
            int nyl = y_limit && (y == (Y >> cur & 1));
            int nh = (high >= 0 ? high : (x || y) ? cur : -1);

            ans += dfs(cur - 1, nh, nxl, nyl);
        }
    }

    ans %= 1'000'000'007;
    if(high >= 0) dp[cur][high][x_limit][y_limit] = ans;
    return ans;
}

void work() {
    F.read(X); F.read(Y);
    memset(dp, -1, sizeof dp);
    std::cout << dfs(30, -1, 1, 1) << char(10);
}

int main() {
//     freopen("4.in", "r", stdin);
//     freopen("4.out", "w", stdout);
    int T; F.read(T); while(T--) work();
    return 0;
}

D. Walker

小清新分讨题,想清楚其实就三种情况:

  • 其中一个人全走了;
  • 两个人面对面走到端点处停止;
  • 存在一个分界点,使得两个人各种走分界点两侧的部分;

前两种情况都很 trivial,第三种情况需要考虑每个人先往哪个方向更优

一个容易的实现是先二分一个答案,确定了这个值后就很容易列式子求出每个人往哪个方向更优

#include <bits/stdc++.h>

int _;

double solve() {
    double n, p1, v1, p2, v2;
    _ = scanf("%lf%lf%lf%lf%lf", &n, &p1, &v1, &p2, &v2);

    if(p1 > p2) std::swap(p1, p2), std::swap(v1, v2);
    p2 = n - p2;
    
    double ans = std::min({
        (n + p1) / v1,
        (n + n - p1) / v1,
        (n + p2) / v2,
        (n + n - p2) / v2,
        std::max((n - p2) / v2, (n - p1) / v1),
    });

    auto check = [&](double T) {
        if(T * v1 < p1 || T * v2 < p2) return false;
        double l1 = std::max(p1 + (T - p1 / v1) * (0.5 * v1), p1 + (T - 2 * p1 / v1) * v1);
        double l2 = std::max(p2 + (T - p2 / v2) * (0.5 * v2), p2 + (T - 2 * p2 / v2) * v2);

        return l1 + l2 >= n;
    };

    double l = 0, r = 1.0e8;
    for(_ = 0; _ < 200; ++_) {
        double mid = (l + r) / 2.0;
        if(check(mid)) r = mid;
        else l = mid;
    }

    return std::min(ans, l);
}

int main() {
    int T; _ = scanf("%d", &T); while(T--) printf("%.10lf\n", solve());
    return 0;
}

E. The Journey of Geor Autumn

感觉想到就很简单的 Counting,优化的部分属于是看一眼就会,但有人又整烂荔枝祸害队友了我不说是谁

考虑设 \(f_i\) 表示长度为 \(i\) 的排列的答案,考虑怎么转移

首先要发现 \(1\) 只能放在 \([1,k]\) 的位置中,否则一定无解;我们考虑枚举 \(1\) 的位置 \(j\),显然 \([1,j-1]\) 这些位置可以随便填

考虑后面的部分在重标号后其实就变为了一个规模为 \(i-j\) 的子问题,因此有转移式:

\[f_i=\sum_{j=1}^k C_{i-1}^{j-1}\times(j-1)!\times f_{i-j} \]

把式子稍微变个形,令 \(g_i=\frac{f_i}{i!}\),则:

\[g_i=\frac{1}{i}\times \sum_{j=1}^k g_{i-j} \]

直接前缀和递推即可做到 \(O(n)\)

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e7+5,mod=998244353;
int n,k,g[N],sg[N],fact[N],ifac[N],inv[N];
inline int quick_pow(int x,int p=mod-2,int mul=1)
{
    for (;p;p>>=1,x=1LL*x*x%mod) if (p&1) mul=1LL*mul*x%mod; return mul;
}
inline void init(CI n)
{
    fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
    ifac[n]=quick_pow(fact[n]); for (RI i=n-1;i>=0;--i) ifac[i]=1LL*ifac[i+1]*(i+1)%mod;
    inv[0]=1; for (RI i=1;i<=n;++i) inv[i]=1LL*ifac[i]*fact[i-1]%mod;
}
int main()
{
    scanf("%d%d",&n,&k); init(n);
    for (RI i=1;i<=k;++i) g[i]=1,sg[i]=(sg[i-1]+g[i])%mod;
    for (RI i=k+1;i<=n;++i)
    {
        g[i]=1LL*(sg[i-1]-sg[i-k-1]+mod)*inv[i]%mod;
        sg[i]=(sg[i-1]+g[i])%mod;
    }
    return printf("%d",1LL*g[n]*fact[n]%mod),0;
}

G. Fibonacci

斐波那契的奇偶性有 \(3\) 的周期,因此直接统计下奇数偶数的个数即可

#include<cstdio>
#include<iostream>
#define RI register int
#define CI const int&
using namespace std;
int n;
int main()
{
    scanf("%d",&n); int odd=n-n/3;
    printf("%lld",1LL*n*(n-1)/2LL-1LL*odd*(odd-1)/2LL);
    return 0;
}

H. Rice Arrangement

神秘猜结论,考虑最后一定存在一个最优解,满足将每个人和对应的食物连边后,线段不会交叉

因此直接大力枚举第一个人对应的食物(从顺时针/逆时针两个方向),然后剩下的就往另一个方向一一匹配,很容易直接计算答案

总复杂度 \(O(n^2)\)

#include<cstdio>
#include<iostream>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1005;
int t,n,k,a[N],b[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&k); int ans=2e9;
        for (RI i=1;i<=k;++i) scanf("%d",&a[i]);
        for (RI i=1;i<=k;++i) scanf("%d",&b[i]),b[i]-=n;
        sort(a+1,a+k+1); sort(b+1,b+k+1);
        for (RI i=1;i<=2*k+1;++i)
        {
            int l=0,r=0;
            for (RI j=1;j<=k;++j)
            l=min(l,a[j]-b[j]),r=max(r,a[j]-b[j]);
            ans=min(ans,-l+r+min(-l,r));
            int tmp=b[1]; for (RI j=1;j<k;++j) b[j]=b[j+1]; b[k]=tmp+n;
        }
        printf("%d\n",ans);
    }
    return 0;
}

I. Sky Garden

祁神写的神秘几何,我题目都没看

#include <bits/stdc++.h>
using namespace std;
using LD = long double;
constexpr LD PI = acosl(-1.0L);

constexpr LD eps = 1e-9;
int sgn(LD x) {return fabs(x)<=eps ? 0 : (x>eps ? 1 : -1);}

int main() {
    std::ios::sync_with_stdio(false); cin.tie(0);
    cout << setiosflags(ios::fixed) << setprecision(10);

    int n, m;
    cin >> n >> m;
    LD ans = 0;
    LD th = PI / m;

    if (m>1) {
        for (int r1=1; r1<=n; ++r1) {
            ans += 2*m*r1;
        }
    }
    // printf("ans=%Lf\n", ans);

    for (int r1=1; r1<=n; ++r1) {
        LD res = 0.0L;
        int cnt = 0;
        LD arc = 0.0L;
        for (int i=1; sgn(i*th-2) < 0; ++i) {
            arc += th*i*r1;
            ++cnt;
        } 
        LD tmp = arc*2 + (((m-cnt)*2-1)*r1*2);
        // printf("tmp=%Lf\n", tmp);
        res += tmp / 2;
        for (int r2=r1+1; r2<=n; ++r2) {
            res += (r2-r1)*2*m + tmp;
        }
        res *= 2*m;
        ans += res;
    }

    cout << ans << '\n';
    return 0;
}

L. Traveling in the Grid World

神秘猜结论,如果不能一步走到的话感觉两步显然是最优的

考虑在此基础上怎么算答案,朴素的想法是枚举 \((0,0)\to (p,q)\to(n,m)\),需要满足 \(\gcd(p,q)=\gcd(n-p,m-q)=1\and (p,q)\ne (n-p,m-q)\),但这样复杂度一眼寄

考虑优化,不难发现 \(\frac{p}{q}\) 越接近 \(\frac{n}{m}\) 答案就越小,因此我们枚举 \(p\),然后令 \(q\)\(\frac{p\times m}{n}\) 附近找几个值即可,实测找两个都能过

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cmath>
#include<cassert>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
int t,n,m;
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld",&n,&m);
        int g=__gcd(n,m);
        auto dist=[&](CI x,CI y)
        {
            return sqrt(1LL*x*x+1LL*y*y);
        };
        if (g==1) { printf("%.15lf\n",dist(n,m)); continue; }
        double ans=1e18;
        for (RI p=1;p<=n;++p)
        {
            int q=1LL*m*p/n;
            if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
            {
                //do nothing
            } else ans=min(ans,dist(p,q)+dist(n-p,m-q));
            for (RI j=0;j<3;++j)
            {
                --q; if (q<0) break;
                if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
                {
                    //do nothing
                } else ans=min(ans,dist(p,q)+dist(n-p,m-q));
            }
        }
        for (RI q=1;q<=m;++q)
        {
            int p=1LL*n*q/m;
            if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
            {
                //do nothing
            } else ans=min(ans,dist(p,q)+dist(n-p,m-q));
            for (RI j=0;j<3;++j)
            {
                --p; if (p<0) break;
                if (__gcd(p,q)!=1||__gcd(n-p,m-q)!=1||(p==n-p&&q==m-q))
                {
                    //do nothing
                } else ans=min(ans,dist(p,q)+dist(n-p,m-q));
            }
        }
        printf("%.15lf\n",ans);
    }
    return 0;
}

M. Gitignore

字符串处理+简单 DP

不难发现把文件树建出来后如果一个节点内的所有叶子都要删除,则其贡献为 \(1\);否则为所有儿子的贡献之和

#include<cstdio>
#include<iostream>
#include<string>
#include<map>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n,m,idx,f[N],ext[N]; map <string,int> rst; vector <int> v[N];
inline int ID(const string& s)
{
    if (rst.count(s)) return rst[s];
    return rst[s]=++idx;
}
inline void DFS(CI now=1)
{
    for (auto to:v[now]) DFS(to),ext[now]|=ext[to];
    if (!ext[now]) f[now]=1; else
    {
        f[now]=0;
        for (auto to:v[now]) f[now]+=f[to];
    }
}
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    for (cin>>t;t;--t)
    {
        cin>>n>>m; idx=1;
        for (RI i=1;i<=n;++i)
        {
            string s,tmp; int now=1;
            cin>>s; s+="/";
            for (RI j=0;j<s.size();++j)
            {
                if (s[j]=='/')
                {
                    v[now].push_back(ID(tmp));
                    now=ID(tmp);
                }
                tmp+=s[j];
            }
        }
        for (RI i=1;i<=m;++i)
        {
            string s,tmp; int now=1;
            cin>>s; s+="/";
            for (RI j=0;j<s.size();++j)
            {
                if (s[j]=='/')
                {
                    v[now].push_back(ID(tmp));
                    now=ID(tmp);
                }
                tmp+=s[j];
            }
            ext[now]=1;
        }
        for (RI i=1;i<=idx;++i)
        {
            sort(v[i].begin(),v[i].end());
            v[i].erase(unique(v[i].begin(),v[i].end()),v[i].end());
        }
        // for (RI i=1;i<=idx;++i)
        // {
        //     cout<<"i = "<<i<<": ";
        //     for (auto x:v[i]) cout<<x<<" ";
        //     cout<<'\n';
        // }
        DFS(); int ans=0;
        for (auto x:v[1]) ans+=f[x];
        cout<<ans<<'\n';
        for (RI i=1;i<=idx;++i) v[i].clear(),ext[i]=0; rst.clear();
    }
    return 0;
}

Postscript

感觉现在打比赛就看一手前期啊,前期顺后面就越打越好,前期烂了后面就疯狂红温

posted @ 2024-11-04 22:23  空気力学の詩  阅读(159)  评论(0)    收藏  举报