The 2024 CCPC National Invitational Contest (Changchun) , The 17th Jilin Provincial Collegiate Programming Contest

Preface

又是经典省赛找信心,这场虽然中间经典三开三卡,但最后都调出来了 4h 10 题下班

剩下的 A 对着题解推了下式子感觉好麻烦就白兰了,J 更是经典动态维护凸壳写不了一点


A. Eminor Array

神秘 DP 题,比赛的时候推了几个性质感觉没啥思路就白兰了,后面看了下题解就是耐心地不停化式子,鉴定为寄


B. Dfs Order 0.5

小清新 DP + 贪心,由于 DFS 序的性质很容易想到按照子树划分状态

\(f_{x,0/1}\) 表示处理了以 \(x\) 为根的子树,其中偶数/奇数位置上的数和的最大值

对于 \(x\) 的转移考虑其所有子节点 \(y\),如果每个子树大小都是偶数那么其实贡献是固定的,不随顺序变化

否则假设存在 \(k\) 个奇数大小的子树,此时显然所有偶数子树都可以取到 \(\max(f_{y,0},f_{y,1})\)

而奇数子树总是一半取 \(0\),一半取 \(1\),当 \(k\) 为奇数时其中一边会多出一个

这是个很经典的贪心问题,我们先强制所有的都选 \(0\),然后按照 \(f_{y,1}-f_{y,0}\) 的值从大到小排序后选最大的对应个数即可

#include<cstdio>
#include<iostream>
#include<vector>
#include<algorithm>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,a[N],x,y,sz[N],f[N][2]; vector <int> v[N];
inline void DFS(CI now=1,CI fa=0)
{
    f[now][0]=0; f[now][1]=a[now]; sz[now]=1;
    int odd_cnt=0;
    for (auto to:v[now]) if (to!=fa)
    {
        DFS(to,now); sz[now]+=sz[to];
        odd_cnt+=(sz[to]%2==1);
    }
    if (odd_cnt)
    {
        int sum_even=0,sum_odd=0; vector <int> cg;
        for (auto to:v[now]) if (to!=fa)
        if (sz[to]%2==1) sum_odd+=f[to][0],cg.push_back(f[to][1]-f[to][0]);
        else sum_even+=max(f[to][0],f[to][1]);
        sort(cg.begin(),cg.end(),greater <int>());
        int token=odd_cnt/2+odd_cnt%2,dlt=0;
        for (RI i=0;i<token;++i) dlt+=cg[i];
        f[now][0]+=sum_even+sum_odd+dlt;
        token=odd_cnt/2; dlt=0;
        for (RI i=0;i<token;++i) dlt+=cg[i];
        f[now][1]+=sum_even+sum_odd+dlt;
    } else
    {
        for (auto to:v[now]) if (to!=fa)
        f[now][0]+=f[to][1],f[now][1]+=f[to][0];
    }
}
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld",&n);
        for (RI i=1;i<=n;++i) scanf("%lld",&a[i]),v[i].clear();
        for (RI i=1;i<n;++i) scanf("%lld%lld",&x,&y),v[x].push_back(y),v[y].push_back(x);
        DFS(); printf("%lld\n",f[1][0]);
    }
    return 0;
}

C. Fibonacci Sum

徐神一波大力推式子直接秒了此题,ORZ

#include <bits/stdc++.h>

using llsi = long long signed int;
constexpr int $n = 10'000'007;

constexpr llsi mod = 1'000'000'007;

int fac[$n], facinv[$n];

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

void init_fac(int n = 10'000'000) {
    fac[0] = 1;
    for(llsi i = 1; i <= n; ++i) fac[i] = fac[i - 1] * i % mod;
    facinv[n] = ksm(fac[n], mod - 2);
    for(llsi i = n; i >= 0; --i) facinv[i - 1] = facinv[i] * i % mod;
    return ;
}

inline llsi C(int a, int b) {
    return llsi(fac[a]) * facinv[b] % mod * facinv[a - b] % mod;
}

int f[$n], g[$n], h[$n];

void init_f(int n = 10'000'000) {
    f[1] = f[2] = 1;
    for(int i = 3; i <= n; ++i) f[i] = (f[i - 1] + f[i - 2]) % mod;
    g[1] = 1, g[2] = 3; h[0] = 1, h[1] = 1; h[2] = 2;
    for(int i = 3; i <= n; ++i) g[i] = (llsi(g[i - 1]) * 3 + mod - g[i - 2]) % mod;
    for(int i = 3; i <= n; ++i) h[i] = (llsi(h[i - 1]) * 3 + mod - h[i - 2]) % mod;
}

int n;
char s[$n];

int main() {
    init_fac(); init_f();

    scanf("%s", s + 1); n = strlen(s + 1);

    // for(int i = 1; i <= 10; ++i) printf("%d %d %d\n", f[i], g[i], h[i]);

    int b = 0;
    llsi ans = 0;
    for(int i = 1; i <= n; ++i) {
        if(s[i] == '1') {
            ans += (llsi(f[b]) * h[n - i] + llsi(f[b + 1]) * g[n - i]) % mod;
            b += 1;
        } else {
            // Nothing happens
        }
    }

    ans += f[b];
    printf("%lld\n", ans % mod);

    return 0;
}

D. Parallel Lines

神秘题,注意到题目保证有解,且每条直线上至少有 \(2\) 个点,因此最后答案直线的斜率一定可以由一个点对得到

考虑随机搞一个点对,它是答案的概率显然不会低于 \(\frac{1}{k}\),而检验一个答案是否合法可以用扫描线 \(O(n\log n)\) 解决

最后大致复杂度为 \(O(nk\log n)\)

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

struct Pt{
    int x, y;
    Pt operator-(const Pt &b)const{return Pt{x-b.x, y-b.y};}
    int dot(const Pt &b)const{return x*b.x+y*b.y;}
    bool operator<(const Pt &b)const{return x!=b.x ? x<b.x : y<b.y;}
    Pt rot90()const{return Pt{-y, x};}
};

mt19937 rnd(chrono::steady_clock::now().time_since_epoch().count());

const int N = 1e4+5;
int n, k;
Pt pt[N];

int randint(int l, int r) {
    return uniform_int_distribution{l, r}(rnd);
}

pair<int, int> rpair() {
    int l = randint(0, n - 1), r;
    do r = randint(0, n - 1); while(l == r);
    return {l, r};
}

signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> k;
    for (int i=0; i<n; ++i){
        cin >> pt[i].x >> pt[i].y;
    }

    while (1){
        auto [o, x] = rpair();
        Pt ox = pt[x] - pt[o];
        Pt v = ox.rot90();
        map<int, vector<int>> mp;
        for (int i=0; i<n; ++i){
            int res = v.dot(pt[i]-pt[o]);
            mp[res].push_back(i);
            if (mp.size() > k) break;
        }
        bool ok=true;
        if (mp.size()!=k) ok=false;
        if (ok){
            for (auto [_, vec] : mp){
                if (vec.size()<2){ok=false; break;}
            }
        }

        if (ok){
            for (auto [_, vec] : mp){
                cout << vec.size();
                for (int x : vec) cout << ' ' << x+1;
                cout << '\n';
            }
            break;
        }
    }
    return 0;
}

E. Connected Components

首先转化题意,令 \(x_i=a_i-i,y_i=i-b_i\),则两个点之间有边等价于它们间满足二维偏序关系

考虑按照 \(x_i\) 从小到大排序,用增量法一个一个地将点加入

可以用一个单调栈来维护之前每个连通块的 \(y_i\) 最小的元素,对于新加入的点我们钦定栈顶不变然后弹出被合并掉的点即可

实现的时候对于 \(x_i\) 相同的点之间的处理顺序需要注意,复杂度 \(O(n\log n)\)

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

const int N = 1e6+5;
int n, A[N], B[N], X[N], Y[N], id[N];
int stk[N], top=0;
signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i){
        cin >> A[i] >> B[i];
        X[i] = A[i]-i; Y[i] = i-B[i];
        id[i] = i;
    }
    sort(id+1, id+n+1, [&](int a, int b){return X[a]!=X[b] ? X[a]<X[b] : Y[a]<Y[b];});

    int cur=id[1];
    for (int i=2; i<=n; ++i){
        if (Y[id[i]] >= Y[cur]){
            while (top>0 && Y[id[i]] >= Y[stk[top]]) --top;
        }else{
            if (X[cur]!=X[id[i]]) stk[++top] = cur;
            cur = id[i];
        }
    }
    stk[++top] = cur;
    cout << top << '\n';

    return 0;
}

F. Best Player

Light DS 题

首先有一个很直观的 \(O(nm)\) 做法,枚举最后获胜的人 \(p\),然后枚举每场比赛

如果这场比赛包括 \(p\),则让它拿走所有的分即可;否则每场比赛我们把 \(z_i\) 尽量平均分配一定最优

有了这个做法后我们发现可以先把所有的比赛都平均分配,然后枚举到人 \(p\) 的时候直接把与它有关的比赛修改即可,用 multiset 可以轻松维护

总复杂度 \(O(n\log n)\)

#include<cstdio>
#include<iostream>
#include<set>
#include<vector>
#include<algorithm>
#define RI register int
#define CI const int&
using namespace std;
const int N=200005;
int t,n,m,a[N],b[N],x[N],y[N],z[N],tx[N],ty[N],tz[N];
vector <int> vec[N]; multiset <int> s[N];
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    for (cin>>t;t;--t)
    {
        cin>>n>>m;
        for (RI i=1;i<=n;++i) s[i].clear(),vec[i].clear();
        for (RI i=1;i<=m;++i)
        {
            cin>>a[i]>>b[i]>>x[i]>>y[i]>>z[i];
            tx[i]=x[i]; ty[i]=y[i]; tz[i]=z[i];
            vec[a[i]].push_back(i); vec[b[i]].push_back(i);
            if (x[i]<=y[i])
            {
                int dlt=min(y[i]-x[i],z[i]); x[i]+=dlt; z[i]-=dlt;
                if (z[i]>0) x[i]+=z[i]/2+(z[i]%2),y[i]+=z[i]/2;
                s[a[i]].insert(x[i]); s[b[i]].insert(y[i]);
            } else
            {
                int dlt=min(x[i]-y[i],z[i]); y[i]+=dlt; z[i]-=dlt;
                if (z[i]>0) x[i]+=z[i]/2+(z[i]%2),y[i]+=z[i]/2;
                s[a[i]].insert(x[i]); s[b[i]].insert(y[i]);
            }
        }
        multiset <int> rst;
        for (RI i=1;i<=n;++i) rst.insert(*s[i].rbegin());
        vector <int> ans;
        for (RI i=1;i<=n;++i)
        {
            vector <int> tmp;
            for (auto id:vec[i]) tmp.push_back(a[id]),tmp.push_back(b[id]);
            sort(tmp.begin(),tmp.end());
            tmp.erase(unique(tmp.begin(),tmp.end()),tmp.end());
            for (auto p:tmp) rst.erase(rst.find(*s[p].rbegin()));
            for (auto id:vec[i])
            {
                s[a[id]].erase(s[a[id]].find(x[id]));
                s[b[id]].erase(s[b[id]].find(y[id]));
            }
            for (auto id:vec[i])
            {
                if (a[id]==i)
                {
                    int nx=tx[id]+tz[id],ny=ty[id];
                    s[a[id]].insert(nx); s[b[id]].insert(ny);
                } else
                {
                    int nx=tx[id],ny=ty[id]+tz[id];
                    s[a[id]].insert(nx); s[b[id]].insert(ny);
                }
            }
            // printf("i = %d\n",i);
            // for (RI j=1;j<=n;++j) printf("%d ",*s[j].rbegin()); putchar('\n');
            for (auto p:tmp) rst.insert(*s[p].rbegin());
            auto mx=rst.end(); --mx;
            auto smx=mx; --smx;
            if (*mx==*s[i].rbegin()&&*mx!=*smx) ans.push_back(i);
            for (auto p:tmp) rst.erase(rst.find(*s[p].rbegin()));
            for (auto id:vec[i])
            {
                if (a[id]==i)
                {
                    int nx=tx[id]+tz[id],ny=ty[id];
                    s[a[id]].erase(s[a[id]].find(nx));
                    s[b[id]].erase(s[b[id]].find(ny));
                } else
                {
                    int nx=tx[id],ny=ty[id]+tz[id];
                    s[a[id]].erase(s[a[id]].find(nx));
                    s[b[id]].erase(s[b[id]].find(ny));
                }
            }
            for (auto id:vec[i])
            {
                s[a[id]].insert(x[id]);
                s[b[id]].insert(y[id]);
            }
            for (auto p:tmp) rst.insert(*s[p].rbegin());
        }
        printf("%d\n",(int)ans.size());
        for (auto x:ans) printf("%d ",x); putchar('\n');
    }
    return 0;
}

G. Platform Game

签到题,祁神开场写的我题目都没看

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

struct Seg{
    int l, r, y;
    bool operator<(const Seg &b)const{return y>b.y;}
};

void solve(){
    int n; cin >> n;
    vector<Seg> segs(n);
    for (int i=0; i<n; ++i){
        int l, r, y; cin >> l >> r >> y;
        segs[i] = Seg{l, r, y};
    }
    sort(segs.begin(), segs.end());
    int sx, sy; cin >> sx >> sy;
    for (auto [l, r, y] : segs){
        if (y>sy) continue;
        if (sx>l && sx<r){
            sy = y;
            sx = r;
        }
    }
    cout << sx << '\n';
}

signed main(){
    ios::sync_with_stdio(0); cin.tie(0);
    int t; cin >> t; while (t--) solve();
    return 0;
}

H. Games on the Ads 2: Painting

有铸币写拓扑排序写假了爆 TLE 了五发,后面徐神上去给我重新实现了下跑得飞快

首先不难想到根据 \(c_{i,j}\)\(p_i,q_j\) 的关系进行分讨:

  • \(c_{i,j}\ne p_i\and c_{i,j}\ne q_j\),则这种情况一定无解;
  • \(c_{i,j}\ne p_i\and c_{i,j}= q_j\),则说明列 \(j\) 的染色时间一定晚于行 \(i\)
  • \(c_{i,j}= p_i\and c_{i,j}\ne q_j\),则说明行 \(i\) 的染色时间一定晚于列 \(j\)​;
  • \(c_{i,j}= p_i\and c_{i,j}= q_j\),则行 \(i\) 和列 \(j\) 的染色时间不定

由于 \(\{p\},\{q\}\) 都是排列,因此满足第四个条件的点有且仅有 \(n\) 个,我们考虑 \(2^n\) 枚举它们对应的行列的先后信息

这类先后选的问题一眼转为拓扑序计数,即我们用边 \(x\to y\) 表示点 \(x\) 要在 \(y\) 之前选

朴素的拓扑序计数是 NPC 的,但这题的图是个二分竞赛图,手玩下会发现若当前存在 \(k\) 个度数为 \(0\) 的点,则不把这些点取完是不会得到新的度数为 \(0\) 的点的

因此每次找到度数为 \(0\) 的点数量 \(k\),将 \(k!\) 计入贡献,然后删去这些点继续处理即可;注意当出现环时强制贡献为 \(0\)

总复杂度 \(O(2^n\times n^2)\),需要较好的实现

#include<cstdio>
#include<iostream>
#include<vector>
#include<cassert>
#define RI register int
#define CI const int&
using namespace std;
const int N=45,mod=998244353;
int n,m,ans,p[N],q[N],c[N][N],deg[N],tdeg[N],fact[N],x[N],y[N],num[N][N]; vector <int> v[N];
inline void init(CI n)
{
    fact[0]=1; for (RI i=1;i<=n;++i) fact[i]=1LL*fact[i-1]*i%mod;
}
int main()
{
	//freopen("H.in","r",stdin);
    init(40); scanf("%d",&n);
    for (RI i=1;i<=n;++i) scanf("%d",&p[i]);
    for (RI i=1;i<=n;++i) scanf("%d",&q[i]);
    for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j) scanf("%d",&c[i][j]);
    for (RI i=1;i<=n;++i) for (RI j=1;j<=n;++j)
    {
        if (c[i][j]!=p[i]&&c[i][j]!=q[j]) return puts("0"),0;
        if (c[i][j]==p[i]&&c[i][j]==q[j])
		{
			num[i][j]=m; x[m]=i; y[m]=j; ++m;
		} else
        {
            if (c[i][j]==p[i]) v[n+j].push_back(i),++tdeg[i];
            else v[i].push_back(n+j),++tdeg[n+j];
        }
    }
    assert(m==n);
    for(int i = 1; i <= 2 * n; ++i) v[i].reserve(n);
    for (RI mask=0;mask<(1<<m);++mask)
    {
        for (RI i=1;i<=2*n;++i) deg[i]=tdeg[i];
        for (RI i=0;i<m;++i)
        if ((mask>>i)&1) v[x[i]].push_back(n+y[i]),++deg[n+y[i]];
		else v[n+y[i]].push_back(x[i]),++deg[x[i]];
        int ret; // vector <int> pnt;
        /*for (RI i=1;i<=2*n;++i) pnt.push_back(i);
        while (!pnt.empty())
        {
            vector <int> vec,rmv;
            for (auto x:pnt) if (!deg[x]) rmv.push_back(x); else vec.push_back(x);
            if (rmv.empty()) { ret=0; break; } else ret=1LL*ret*fact[(int)(rmv.size())]%mod;
            for (auto x:rmv) for (auto y:v[x]) --deg[y]; pnt=vec;
        }*/
        {
            static int base[2][45];
            auto f1 = base[0], f2 = base[1];
            int count = 0, cc;
            for(RI i = 1; i <= 2 * n; ++i) if(deg[i] == 0) f1[count++] = i;
            ret = fact[count]; cc = count;
            while(count) {
                int ncount = 0;
                for(RI i = 0; i < count; ++i) for(auto v: v[f1[i]])
                    if(--deg[v] == 0) f2[ncount++] = v;
                ret = 1LL * ret * fact[ncount] % mod;
                std::swap(f1, f2); cc += count = ncount;
            }
            if(cc < 2 * n) ret = 0;
        }
        (ans+=ret)%=mod;
        for (RI i=m-1;i>=0;--i)
        if ((mask>>i)&1) v[x[i]].pop_back(); else v[n+y[i]].pop_back();
    }
    return printf("%d",ans),0;
}

I. The Easiest Problem

还真是最简单的题,直接 puts("21") 即可

#include<cstdio>
using namespace std;
int main()
{
    puts("21");
    return 0;
}

J. Lone Trail

沟槽的动态凸包,直接白兰弃疗


K. String Divide II

string master 徐神直接高阶科技 runs 秒了,30s 时限的题跑 300ms 可海星

#include <bits/stdc++.h>

int n, k;
char s[1000006];
namespace has {
    using namespace std;
    const int mod = 998'244'853, b = 131;
    int a[1000006];
    int pw[1000006], s[1000006];
    inline void build(int n) {
        pw[0] = 1;
 
        for (int i = 1; i <= n; i++) {
            pw[i] = 1ll * pw[i - 1] * b % mod;
            s[i] = (s[i - 1] + 1ll * pw[i - 1] * a[i]) % mod;
        }
    }
    inline int query(int l, int r) {
        return s[r] < s[l - 1] ? s[r] + mod - s[l - 1] : s[r] - s[l - 1];
    }
    inline int lcs(int i, int j, int up = 1 << 30) {
        if (a[i] != a[j])
            return 0;
 
        if (i > j)
            swap(i, j);
 
        if (!i)
            return 0;
 
        if (1ll * query(1, i) * pw[j - i] % mod == query(j - i + 1, j))
            return i;
 
        int l = 2, r = min(i, up), ans = 1;
 
        if (1ll * query(i - r + 1, i) * pw[j - i] % mod == query(j - r + 1, j))
            return r;
 
        while (l <= r) {
            int mid = (l + r) >> 1;
 
            if (1ll * pw[j - i] * query(i - mid + 1, i) % mod ==
                    1ll * query(j - mid + 1, j))
                ans = mid, l = mid + 1;
            else
                r = mid - 1;
        }
 
        return ans;
    }
    inline int lcp(int i, int j, int up = 1 << 30) {
        if (a[i] != a[j])
            return 0;
 
        if (i > j)
            swap(i, j);
 
        if (j > n)
            return 0;
 
        int l = 2, r = min(up, n - j + 1), ans = 1;
 
        if (1ll * query(i, i + r - 1) * pw[j - i] % mod == query(j, j + r - 1))
            return r;
 
        while (l <= r) {
            int mid = (l + r) >> 1;
 
            if (1ll * pw[j - i] * query(i, i + mid - 1) % mod == query(j, j + mid - 1))
                ans = mid, l = mid + 1;
            else
                r = mid - 1;
        }
 
        return ans;
    }
} // namespace has
 
using has::lcp;
using has::lcs;
struct runs {
    int l, r, p;
    bool operator < (const runs &j) const {
        return l != j.l ? l < j.l : r < j.r;
    }
    bool operator ==(const runs &j) const {
        return l == j.l && r == j.r && p == j.p;
    }
};

std::vector<runs> q;

inline void get_runs() {
    static int st[1000001];
    st[0] = n + 1;
    using has::a;
 
    for (int i = n, top = 0, lt = 0; i; i--) {
        while (top) {
            int x = std::min(st[top] - i, st[top - 1] - st[top]);
            lt = lcp(i, st[top], x);
 
            if ((lt == x && st[top] - i < st[top - 1] - st[top]) ||
                    (lt < x && a[i + lt] < a[st[top] + lt]))
                top--, lt = 0;
            else
                break;
        }
 
        int j = st[top];
        st[++top] = i;
        int x = lcs(i - 1, j - 1, n), y;
 
        if (x < j - i) {
            y = lcp(i, j, n);
 
            if (x + y >= j - i) {
                q.push_back(runs{i - x, j + y - 1, j - i});
            }
        }
    }
}

int main() {
    scanf("%d%d", &n, &k);
    scanf("%s", s + 1);

    for (int i = 1; i <= n; i++)
        has::a[i] = 25 - (s[i] - 'a');
    has::build(n), get_runs();
    for (int i = 1; i <= n; i++)
        has::a[i] = 25 - has::a[i];
    has::build(n), get_runs();

    int ans = 0;
    for(auto [l, r, p]: q) {
        // printf("[%d, %d, %d]\n", l, r, p);
        int len = r - l + 1, part = len / p;
        ans = std::max(ans, part / k * k * p);
    }
    printf("%d\n", ans);
    return 0;
}

L. Recharge

简单贪心分讨题,优先用 \(2\) 然后如果 \(k\) 是奇数就补上一个 \(1\),直到把某一种数用完

#include<cstdio>
#include<iostream>
#define int long long
#define RI register int
#define CI const int&
using namespace std;
const int INF=1e18;
int t,k,x,y;
signed main()
{
    for (scanf("%lld",&t);t;--t)
    {
        scanf("%lld%lld%lld",&k,&x,&y);
        int c2=k/2,c1=k-c2*2,c=min(c1?x/c1:INF,c2?y/c2:INF);
        int ans=c; x-=c1*c; y-=c2*c;
        while (x>0&&y>0&&x+2*y>=k)
        {
            int c2=min(k/2,y),tmp=c2*2; y-=c2;
            if (k-tmp<=x) x-=k-tmp,++ans;
            else if ((k-tmp+1)/2<=y) y-=(k-tmp+1)/2,++ans;
        }
        if (x) ans+=x/k; else ans+=y/((k+1)/2);
        printf("%lld\n",ans);
    }
    return 0;
}

Postscript

多校我唯唯诺诺,省赛我重拳出击,这就是虐菜大师啊

posted @ 2024-08-10 19:06  空気力学の詩  阅读(378)  评论(4编辑  收藏  举报