The 3rd Universal Cup. Stage 37: Wuhan

Preface

这场打的时候出现了各种突发情况,比如中途和教练在机房讨论了若干问题,徐神 J 题写一半临时有事走了之类的

再加上有人犯病了魔改欧拉回路板子导致 E 题完全对的思路最后没过,直接 9 题变 7 题了

我再也不乱改板子了.jpg


A. Problem Setting

队友开场写的签

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

using pii = pair<int, int>;
#define ft first
#define sd second
const int N = 105;
const int INF = (int)1e9+5;
int n, q, A[N];
pii pi[N];

pii ins(const pii &a, const pii &b) {
    if (a.ft > b.sd || b.ft > a.sd) return pii{-1, -1};
    return pii{max(a.ft, b.ft), min(a.sd, b.sd)};
}

int solve() {
    cin >> n >> q;
    for (int i=1; i<=n; ++i) cin >> A[i], pi[i].ft = 0, pi[i].sd = INF;
    bool flag = true;
    for (int i=1; i<=q; ++i) {
        int p, l, r; cin >> p >> l >> r;
        pi[p] = ins(pi[p], pii{l, r});
        if (-1 == pi[p].ft) flag = false; 
    }
    if (!flag) return -1;
    // for (int i=1; i<=n; ++i) printf("i=%lld (%lld %lld)\n", i, pi[i].ft, pi[i].sd);
    int ans = 0;
    for (int i=1; i<=n; ++i) {
        if (A[i] < pi[i].ft) ans += (pi[i].ft - A[i]);
        else if (A[i] > pi[i].sd) ans += (A[i] - pi[i].sd);
    }
    return ans;
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    int T; cin >> T; while (T--) cout << solve() << '\n';
    return 0;
}

C. One Must Imagine Sisyphus Happy

思路不难但细节比较多,如果实现不好的话可能会写的很繁琐

将一棵草在向右走的时候被拔掉记为 \(R\),向左走的时候被拔掉记为 \(L\),则一共有以下三种情况:

  • \(R,R,R,R,\dots\)
  • \(R,L,L,L,\dots\)
  • \(R,L,R,L,\dots\)

不难发现除了第三种情况,前面两种情况拔草的时间都可以写成某个周期函数的形式

而第三种情况可能会有两个不同的间隔,会出现形如 \(+2,+3,+2,+3,\dots\) 这样的情况

不过我们很容易将这种情况拆成两个周期函数的形式,并且简单分析后会发现这种两个间隔的情形下二者之差为 \(1\),因此并不会使得最后总状态的数量级增加

最后把所有本质相同的状态合并到一起,暴力维护贡献,复杂度是调和级数的

#include<cstdio>
#include<iostream>
#include<vector>
#define RI register int
#define CI const int&
using namespace std;
const int N=1e6+5;
int t,n,m,a[N],ans[N];
vector <pair <int,int>> vec[N];
inline void set(CI s,CI d)
{
    if (s>m) return;
    if (d>=m) return (void)(++ans[s]);
    for (auto& [x,y]:vec[d])
    if (x==s) return (void)(++y);
    vec[d].push_back({s,1});
}
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        for (RI i=1;i<=m;++i) ans[i]=0,vec[i].clear();
        for (RI i=1;i<=n;++i) scanf("%d",&a[i]),++a[i];
        for (RI i=1;i<=n;++i)
        {
            int T=a[i]/(2*n-1),rem=a[i]-T*(2*n-1);
            int L=2*(i-1),R=2*(n-i)-1;
            if (rem==0) { set(1,T); continue; }
            if (rem<=2*(n-i))
            {
                if (rem<=2*(i-1)+1) set(1,T+T+1),set(1+T,T+T+1);
                else ++ans[1],set(1+T,T+1);
            } else set(1,T+1);
        }
        for (RI d=1;d<m;++d)
        {
            for (auto [s,v]:vec[d])
            for (RI i=s;i<=m;i+=d) ans[i]+=v;
        }
        for (RI i=1;i<=m;++i)
        printf("%d%c",ans[i]," \n"[i==m]);
    }
    return 0;
}

E. Colorful Graph

思路全对,过程也全对,结果板子抄不对,也是神人了

由于答案的下界为 \(\sum \lceil\frac{deg_i}{2}\rceil\),因此考虑下列能构造出该下界的方案

每次找出一个环,将环上的边染为一种颜色,直到最后找不出环为止

对于原图中度数为奇数的点,两两之间加上一条虚拟边后,图就变成了欧拉图

直接找欧拉回路即可轻松构造答案

#include<cstdio>
#include<iostream>
#include<vector>
#include<map>
#include<stack>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=400005;
int t,n,m,ans[N],used[N],vis[N],idx; vector <pi> v[N]; vector <int> path; 
inline void Euler(CI now)
{
    while (!v[now].empty())
    {
        auto [to,id]=v[now].back();
        v[now].pop_back();
        if (used[id]) continue;
        used[id]=1; Euler(to);
    }
    path.push_back(now);
}
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        map <pi,int> rst;
        for (RI i=1;i<=n;++i) v[i].clear();
        for (RI i=1;i<=m;++i)
        {
            int x,y; scanf("%d%d",&x,&y);
            v[x].push_back({y,i});
            v[y].push_back({x,i});
            rst[{x,y}]=rst[{y,x}]=i;
            used[i]=ans[i]=0;
        }
        vector <int> odd;
        for (RI i=1;i<=n;++i)
        if ((int)v[i].size()%2==1) odd.push_back(i);
        for (RI i=0;i+1<odd.size();i+=2)
        {
            int x=odd[i],y=odd[i+1];
            v[x].push_back({y,m+i/2+1});
            v[y].push_back({x,m+i/2+1});
            used[m+i/2+1]=0;
        }
        idx=0; 
        for (RI i=1;i<=n;++i) vis[i]=0;
        for (RI i=1;i<=n;++i)
        {
            if (v[i].empty()) continue;
            path.clear(); Euler(i);
            // for (auto x:path) printf("%d ",x); putchar('\n');
            stack <int> stk;
            for (auto x:path)
            { 
                if (vis[x])
                {
                    int lst=x; ++idx;
                    while (stk.top()!=x)
                    {
                        // printf("(%d,%d)\n",lst,stk.top());
                        if (rst.count({lst,stk.top()})) ans[rst[{lst,stk.top()}]]=idx;
                        vis[stk.top()]=0; lst=stk.top();
                        stk.pop();
                    }
                    if (rst.count({lst,x})) ans[rst[{lst,x}]]=idx;
                    vis[x]=1;
                } else stk.push(x),vis[x]=1;
            }
            for (auto x:path) vis[x]=0;
        }
        for (RI i=1;i<=m;++i)
        assert(1<=ans[i]&&ans[i]<=m);
        assert(idx<=m);
        for (RI i=1;i<=m;++i)
        printf("%d%c",ans[i]," \n"[i==m]);
    }
    return 0;
}

F. Knapsack

考虑贪心,将物品从大到小排序,我们的策略是每次将物品放到总体积最小的包里

不过由于包和物品的数量很多,实现时通过维护答案以及总缺口量

因为物品的体积粒度越来越小,因此去填坑总是能填上的,故不需要维护缺口的具体形态

由于这题的数量级比较大,实现时可以维护当前缺口需要多少个当前物品才能不足,并可以通过设阈值的方法避免高精度

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr llsi THRESHOLD = 1ll << 50;

constexpr llsi mod = 998244353;

constexpr llsi ksm(llsi a, llsi b) {
    llsi c = 1;
    while(b) {
        if(b & 1) c = c * a % mod;
        a = a * a % mod;
        b >>= 1;
    }
    return c;
}

// void add(std::set<int> &s, int b) {
//     while(s.find(b) != s.end()) s.erase(b++);
//     s.insert(b);
//     return ;
// }

// void add(std::set<int> &s, int a, int b) {
//     while(a) {
//         if(a & 1) add(s, b);
//         b += 1;
//         a >>= 1;
//     }
//     return ;
// }

// llsi to_mod(std::set<int> &s) {
//     llsi res = 0;
//     for(auto c: s) res = (res + ksm(2, c)) % mod;
//     return res;
// }

llsi work() {
    int n, m; std::cin >> n >> m;
    std::vector<std::pair<int, int>> hkr(n);
    for(auto &[b, a]: hkr) std::cin >> a >> b;

    std::sort(hkr.begin(), hkr.end(), std::greater<std::pair<int, int>>());

    llsi res = 0, res_b = -1;
    
    llsi ans = 0;
    
    for(auto [b, a]: hkr) {
        if(res_b > b && res == 0) res_b = b;
        while(res_b > b) {
            res_b--;
            res *= 2;
            if(res >= THRESHOLD) return ans;
        }

        llsi take = std::min(llsi(a), res);
        a -= take;
        res -= take;
        if(a == 0) continue;
        
        // add(ans, a / m, b);
        
        (ans+=1LL*(a/m)%mod*ksm(2,b)%mod)%=mod;
        res_b = b;
        a %= m;
        if(a) {
            // add(ans, b);
            (ans+=ksm(2,b))%=mod;
            res = (m - a);
        } else {
            res = 0;
        }
    }

    return ans;
}

int main() {
    std::ios::sync_with_stdio(false);
    int T; std::cin >> T; while(T--) std::cout << work() << char(10);
    return 0;
}

G. Path Summing Problem

很经典的一个题啊,连我这种计数苦手都能看一眼就编出做法

考虑枚举每个位置,钦定只有它是一条路径经过的同类数字的第一个时才计算贡献,这样显然是不重不漏的

现在要对每个位置求出走到它且不经过与它值相同的路径条数,显然有两种做法:

  • 直接 \(O(nm)\) 递推,在之前遇到和它值相同的位置就清空;
  • 大力容斥,设之前有 \(k\) 个值相同的数,复杂度是 \(O(k^2)\) 的;

很容易发现根号分治,对于出现次数大于 \(\sqrt{nm}\) 的数用第一种方法,否则用第二种方法,总复杂度是 \(O(nm\sqrt{nm})\)

#include<cstdio>
#include<iostream>
#include<vector>
#include<cmath>
#define RI register int
#define CI const int&
using namespace std;
typedef pair <int,int> pi;
const int N=100005,mod=998244353;
int t,n,m,fact[N<<1],ifac[N<<1]; vector <int> a[N],g[N],sp[N]; vector <pi> pos[N];
inline void inc(int& x,CI y)
{
    if ((x+=y)>=mod) x-=mod;
}
inline void dec(int& x,CI y)
{
    if ((x-=y)<0) x+=mod;
}
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;
}
inline int C(CI n,CI m)
{
    if (n<0||m<0||n<m) return 0;
    return 1LL*fact[n]*ifac[m]%mod*ifac[n-m]%mod;
}
inline int calc(const pi& A,const pi& B)
{
    int dx=B.first-A.first,dy=B.second-A.second;
    if (dx<0||dy<0) return 0;
    return C(dx+dy,dx);
}
int main()
{
    for (init(200000),scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&m);
        for (RI i=0;i<=n;++i) a[i].resize(m+1),g[i].resize(m+1),sp[i].resize(m+1);
        for (RI i=1;i<=n*m;++i) pos[i].clear();
        for (RI i=1;i<=n;++i)
        for (RI j=1;j<=m;++j)
        {
            scanf("%d",&a[i][j]);
            pos[a[i][j]].push_back({i,j});
        }
        int S=(int)sqrt(n*m),ans=0;
        for (RI k=1;k<=n*m;++k)
        {
            if ((int)pos[k].size()<=S)
            {
                vector <int> f(pos[k].size(),0);
                for (RI i=0;i<(int)pos[k].size();++i)
                {
                    f[i]=calc({1,1},pos[k][i]);
                    for(RI j=0;j<i;++j)
                    dec(f[i],1LL*f[j]*calc(pos[k][j],pos[k][i])%mod);
                    inc(ans,1LL*f[i]*calc(pos[k][i],{n,m})%mod);
                }
            } else
            {
                for (RI i=0;i<=n;++i)
                for (RI j=0;j<=m;++j)
                g[i][j]=sp[i][j]=0;
                for (auto [x,y]:pos[k])
                sp[x][y]=1;
                g[1][1]=1;
                for (RI i=1;i<=n;++i)
                for (RI j=1;j<=m;++j)
                {
                    inc(g[i][j],g[i-1][j]); inc(g[i][j],g[i][j-1]);
                    if (sp[i][j]) inc(ans,1LL*g[i][j]*calc({i,j},{n,m})%mod),g[i][j]=0;
                }
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

I. Bingo 3

签到,但有人开局看错题了我不说是谁

\(k\) 放在 \((1,1)\),然后找 \(n-1\) 个小于 \(k\) 的数放在第一行,再找 \(n-1\) 个大于 \(k\) 的数放在剩下的主对角线位置即可

#include<cstdio>
#include<iostream>
#include<assert.h>
#define RI register int
#define CI const int&
using namespace std;
const int N=55;
int t,n,k,a[N][N],used[N*N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d%d",&n,&k);
        if (k<n||n*n-k<n-1) { puts("No"); continue; }
        puts("Yes"); int idx=k;
        for (RI i=1;i<=n;++i)
        for (RI j=1;j<=n;++j)
        a[i][j]=0;
        for (RI i=1;i<=n*n;++i) used[i]=0;
        for (RI i=1;i<=n;++i) used[idx]=1,a[1][i]=idx--;
        idx=k+1;
        for (RI i=2;i<=n;++i) used[idx]=1,a[i][i]=idx++;
        idx=1;
        for (RI i=1;i<=n;++i)
        for (RI j=1;j<=n;++j)
        {
            if (a[i][j]) continue;
            while (used[idx]) ++idx;
            a[i][j]=idx++;
        }
        // int res=-1;
        // for (RI s=1;s<=n*n;++s)
        // {
        //     bool flag=0;
        //     for (RI i=1;i<=n;++i)
        //     {
        //         int cnt=0;
        //         for (RI j=1;j<=n;++j)
        //         cnt+=a[i][j]<=s;
        //         if (cnt==n) { flag=1; }
        //     }
        //     if (!flag) continue;
        //     flag=0;
        //     for (RI j=1;j<=n;++j)
        //     {
        //         int cnt=0;
        //         for (RI i=1;i<=n;++i)
        //         cnt+=a[i][j]<=s;
        //         if (cnt==n) { flag=1; }
        //     }
        //     if (!flag) continue;
        //     res=s; break;
        // }
        // assert(res==k);
        for (RI i=1;i<=n;++i)
        for (RI j=1;j<=n;++j)
        printf("%d%c",a[i][j]," \n"[j==n]);
    }
    return 0;
}

J. Dictionary

赛时 SAM 板都抄好了还写了一大半了,结果唯一会 string 的徐神一走留下剩下两条字符串苦手,索性直接开摆

徐神宣布对此题负责.jpg


L. Subsequence

不难发现给序列排序不影响答案

枚举最小最大两个数,可以很容易计算出合法的中位数所在的区间

在区间里选一个尽可能靠近中心的位置即可,注意奇偶长度区间的区别

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<map>
#define RI register int
#define CI const int&
using namespace std;
const int N=3005;
int t,n,a[N];
int main()
{
    for (scanf("%d",&t);t;--t)
    {
        scanf("%d",&n);
        for (RI i=1;i<=n;++i) scanf("%d",&a[i]);
        sort(a+1,a+n+1);
        map <int,int> L,R;
        for (RI i=1;i<=n;++i) R[a[i]]=i;
        for (RI i=n;i>=1;--i) L[a[i]]=i;
        int ans=1;
        for (RI i=1;i<=n;++i)
        for (RI j=i+1;j<=n;++j)
        {
            if ((a[i]+a[j])%2==1) continue;
            int M=(a[i]+a[j])/2;
            if (!L.count(M)) continue;
            int l=L[M],r=R[M];
            auto upt=[&](CI p)
            {
                int res=2*min(p-i,j-p)+1;
                if (p-i<j-p) ++res;
                ans=max(ans,res);
                return;
            };
            if ((j-i)%2==0)
            {
                int p=i+j>>1;
                if (l<=p&&p<=r) upt(p);
                upt(l); upt(r);
            } else
            {
                int p=i+j>>1;
                if (l<=p&&p<=r) upt(p);
                if (l<=p+1&&p+1<=r) upt(p+1);
                upt(l); upt(r);
            }
        }
        printf("%d\n",ans);
    }
    return 0;
}

M. Flight Tracker

神秘的立体几何,但无所谓我们队有几何大手子

#include<bits/stdc++.h>
using namespace std;
#define int long long
#define LD long double

const LD PI = acosl(-1.0L);

struct Pt {
    int x, y, z;
    int dot(const Pt &b) const {return x*b.x + y*b.y + z*b.z;}
    Pt crs(const Pt &b) const {return Pt {y*b.z-z*b.y, z*b.x-x*b.z, x*b.y-y*b.x};}
    int len2() const {return x*x+y*y+z*z;}
};

LD cosv(const Pt &a, const Pt &b) {return 1.0L * a.dot(b) / sqrtl(a.len2()*b.len2());}

LD solve() {
    int r; cin >> r;
    Pt p; cin >> p.x >> p.y >> p.z;
    Pt a; cin >> a.x >> a.y >> a.z;
    Pt b; cin >> b.x >> b.y >> b.z;
    Pt c = a.crs(b);
    int s1 = (cosv(c.crs(a), p) >= 0);
    int s2 = (cosv(p, b.crs(c)) >= 0);
    if (s1 && s2) {
        LD thi = acos(cosv(c, p));
        if (thi < PI/2) thi = PI/2 - thi;
        else thi = thi - PI/2;
        // puts("11111");
        return thi*r;
    } else {
        // puts("22222");
        return r*min(acos(cosv(p, a)), acos(cosv(p, b)));
    }
}

signed main() {
    ios::sync_with_stdio(0); cin.tie(0);
    cout << setiosflags(ios::fixed) << setprecision(18);
    int T; cin >> T; while (T--) cout << solve() << '\n';
    return 0;
}

Postscript

fun fact:有人在有中文题面的时候读错题意的次数比英文题面多得多,我不说是谁

posted @ 2025-09-11 17:34  空気力学の詩  阅读(88)  评论(0)    收藏  举报