2025.4.23 2023 Hubei Provincial Collegiate Programming Contest

比赛链接

Solved: 8/13

Upsolved: 9/13

Rank: 4

Rank(vp): 48


M. Different Billing

题意:求 \(x+y+z=n, 1000y+2500z=m\) 的一组正整数解。

显然有解时 \(y\) 取值只有 \(0,1,2,3,4\)。直接枚举。

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

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    int n, m;
    cin >> n >> m;
    if (m%500 != 0)
    {
        cout << "-1\n";
        return 0;
    }
    for (int i=0; i<5; ++i)
    {
        if ((m - i * 1000) % 2500 == 0)
        {
            int j = (m - i * 1000) / 2500;
            if (i+j <= n)
            {
                cout << n-i-j << ' ' << i << ' ' << j << '\n';
                return 0;
            }
        }
    }
    cout << "-1\n";
}

C. Darkness I

题意:\(n\times m\) 的矩形网格,初始有一些格子染黑了。每秒所有周围四个格子中有两个以上黑格子的白格子会变黑,求初始最少有多少个格子可以使最终所有格子都变黑。

不妨设 \(n\leq m\)。先在左边的 \(n\times n\) 放一条对角线,后面隔一列放一个,最后一列必须放一个。答案为 \(m - \lfloor\frac{n-m}2\rfloor\)

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

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    int n, m;
    cin >> n >> m;
    if (n > m) swap(n, m);
    cout << m - (m-n)/2 << '\n';
}

H. Binary Craziness

题意:给一张图,设点 \(i\) 的度数为 \(d_i\),求 \(\sum_{i=1}^n\sum_{j=1}^n (d_i\oplus d_j)(d_i\&d_j)(d_i|d_j)\) 的值。\(n\leq 10^6\)

按位考虑的这辈子有了

注意到 \(\sum d_i = 2n\),所以不同的 \(d_i\) 只有 \(O(\sqrt n)\) 个。相同的 \(d_i\) 贡献相等,所以直接枚举即可。

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

const int N = 1e6+5, mod = 998244353;
int n, m, x, y, a[N];
map<int, int> c;
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    for (int i=1; i<=m; ++i) cin >> x >> y, ++a[x], ++a[y];
    for (int i=1; i<=n; ++i) ++c[a[i]];
    ll ans = 0;
    for (auto &[x, cx]: c)
        for (auto &[y, cy]: c)
            (ans += 1ll * cx * cy % mod * (x^y) % mod * (x|y) % mod * (x&y)) %= mod;
    cout << ans * ((mod+1)/2) % mod<< '\n';
}

K. Dice Game

题意:\(n+1\) 个人扔骰子,第一个人作弊每次都会扔 \(x\),其他人均匀在 \(1\)\(m\) 的整数中随机。每轮如果某人扔出唯一的最小值则他是唯一输家,否则所有扔出最小值的人重新扔一次,直到决出输家。

概率论练习题。答案是 \((\frac{m-x}{m-1})^n\)

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

const int N = 1e6+5, mod = 998244353;
int n, m;
ll p, q;
ll qpow(ll x, int y)
{
    ll r=1;
    for (; y; y>>=1)
    {
        if (y&1) r = r * x % mod;
        x = x * x % mod;
    }
    return r;
}
ll inv(ll x)
{
    return qpow(x, mod-2);
}
int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n >> m;
    q = inv(m-1);
    for (int i=1; i<=m; ++i)
    {
        cout << qpow(1ll * (m-i) * q % mod, n) << ' ';
    }
    cout << '\n';
}

F. Inverse Manacher

题意:给一个只有ab的字符串manacher数组,还原这个串。

显然我们只需知道当前字符和上一个是否相等。只需判断间隔符位置的manacher是否大于1即可。

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

const int N = 2e6+5;
int n, f[N];
int ans[N], cnt=0;

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for (int i=0; i<=2*n+1; ++i) cin >> f[i];
    ans[1] = 0;
    for (int i=2; i<=n; ++i)
    {
        if (f[i*2-1] == 1) ans[i] = ans[i-1]^1;
        else ans[i] = ans[i-1];
    }
    for (int i=1; i<=n; ++i) cout << char(ans[i]+'a');
    cout << '\n';
}

J. Expansion

题意:有 \(n\) 个物品,每个物品有一个价值(可正可负),每一步可以选择拿下一个物品或保持不动,同时(无论是否拿下一个)获得已有物品的价值之和。问最少多少步可以拿完所有物品,同时保证任意时刻已获得的总价值非负。

贪心,在单步可获得的价值(即价值的前缀和)更高之前,一直停在上一个前缀最大值的位置,到能拿中间这些物品之后再开始拿。

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

const int N = 1e5+5;
int n, a[N];
ll s[N];

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    cin >> n;
    for (int i=1; i<=n; ++i) cin >> a[i];
    for (int i=1; i<=n; ++i) s[i] = s[i-1] + a[i];
    if (s[n] < 0)
    {
        cout << "-1\n";
        return 0;
    }
    for (int i=1; i<=n; ++i) if (s[i] != 0)
    {
        if (s[i] < 0)
        {
            cout << "-1\n";
            return 0;
        }
        else break;
    }
    int pos = 0;
    ll ans = 0, sum = 0, L = 0, minL = 0;
    for (int i=1; i<=n; ++i)
    {
        if (i == n || s[i] > s[pos])
        {
            ll t = pos ? (max(-minL - sum, 0ll) + s[pos] - 1) / s[pos] : 0;
            ans += t + i - pos, sum += s[pos] * t + L;
            pos = i, L = minL = s[i];
        }
        else L += s[i], minL = min(minL, L);
    }
    cout << ans << '\n';
}

I. Step

题意:求最小的 \(k\) 使得 \(\binom {k+1}2\)\(L\) 的倍数。\(L\leq 10^{18}\)

\(2L\) 质因数分解,其质因子最多只有 \(14\) 个。

因为 \(k(k+1)\)\(2L\) 的倍数而 \(k\)\(k+1\) 互质,所以两个数分别包含 \(2L\) 的两个不交的质因子集合。
\(2^{14}\) 枚举其中一个包含的集合,则只需解一个方程 \(ax=k,by=k+1\)\(a,b\) 已知)即可。exgcd。

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

const int N = 1e7+5;
int n, x;

bool np[N];
int pri[N], cnt=0, p[N];
void sieve(int n)
{
    for (int i=2; i<=n; ++i)
    {
        if (!np[i]) pri[++cnt] = i, p[i] = i;
        for (int j=1; j<=cnt && i*pri[j] <= n; ++j)
        {
            p[i*pri[j]] = pri[j];
            np[i*pri[j]] = 1;
            if (!(i%pri[j])) break;
        }
    }
}

void exgcd(ll a, ll b, ll& x, ll& y)
{
	if(!b) return x=1, y=0, void();
	exgcd(b, a%b, y, x);
	y -= x * (a/b);
}

map <int, int> mp;
vector <ll> s;

int main()
{
    ios::sync_with_stdio(0); cin.tie(0);
    sieve(1e7);
    cin >> n;
    for (int i=1; i<=n; ++i)
    {
        cin >> x;
        map <int, int> ps;
        while (x>1) ps[p[x]]++, x /= p[x];
        for (auto &[x,y]: ps) mp[x] = max(mp[x], y);
    }
    mp[2]++;
    ll L = 1;
    for (auto &[x, y]: mp)
    {
        ll pc = 1;
        for (int i=0; i<y; ++i) pc *= x, L *= x;
        s.push_back(pc);
    }
    int m = s.size();
    ll ans = L*2;
    for (int i=0; i < 1<<m; ++i)
    {
        ll a = 1, b = 1, x, y;
        for (int j=0; j<m; ++j)
        {
            if (i >> j & 1) a *= s[j];
            else b *= s[j];
        }
        exgcd(a, b, x, y);
        x = (-x % b + b) % b;
        if (x) ans = min(ans, a*x);
    }
    cout << ans << '\n';
}

B. Mode

题意:设 \(f(n)\)\(n\) 的十进制表示中出现次数最多的数字的出现次数,求 \(\sum_{i=l}^r f(i)\)

数位 dp,设 \(f(n,c)\) 为后 \(n\) 位数任意且前面的数字集合为 \(c\)\(f\) 之和。dp 方程显然。
然而这样 \(c\) 的不同状态数超过 \(10^7\) 个,无法承受。

当我们计算 \(f(n,c)\) 时,我们并不关心每个数出现了多少次,只有 \(0\) 出现一次和只有 \(9\) 出现一次答案是一样的。所以我们可以将 \(c\) 排序后再记录(或者对 \(c\) 再进行一遍统计之后再记录)。这样状态数就只有几千种了。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define all(x) (x).begin(),(x).end()

map <pair<int, vector<int>>, ll> f;
vector <int> num;
ll dfs(int pos, vector <int> c, bool lim, bool lead)
{
    if (pos<0)
    {
        if (lead) return 1;
        return *max_element(all(c));
    }
    if (!lim)
    {
        sort(all(c));
        if (!lead && f.count({pos, c})) return f[{pos, c}];
    }
    ll res = 0;
    for (int i=0, up = lim ? num[pos]: 9; i<=up; ++i)
    {
        if (i || !lead) ++c[i];
        res += dfs(pos-1, c, lim && i==up, lead && !i);
        if (i || !lead) --c[i];
    }
    if (!lim && !lead) f[{pos, c}] = res;
    return res;
}
ll calc(ll x)
{
    if (x<0) return 0;
    num.clear();
    while (x)
    {
        num.push_back(x%10);
        x /= 10;
    }
    vector <int> c(10);
    return dfs(num.size()-1, c, 1, 1);
}

void solve()
{
    ll l, r; cin >> l >> r;
    cout << calc(r) - calc(l-1) << '\n';
}

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

A. Prime Magic

题意:有一个序列,每次可以选择一个长度为奇质数的区间加一或减一,求最少操作多少次可使整个序列单调不降。

将原序列差分则转化为每次可使 \(b_i\) 加一 \(b_{i+p}\) 减一,或 \(b_i\) 减一 \(b_{i+p}\) 加一。这是一个经典的费用流模型,然而边数 \(O(n^2)\),百万级别的边数显然是跑不过的。

根据哥德巴赫猜想可知,每个偶合数可分解为两个质数的和,每个奇合数可分解为三个质数的和。特别地 \(2=5-3,4=7-3,1=3+5-7\)。即任意两个 \(b\) 之间的转移至多只需三次。

因此我们先把一次可转移的费用求出来(类二分图匹配问题),剩下的两次三次的转移是普遍的,可以在残量网络上直接统计。

赛时懒得动脑子直接跑了三次网络流,复杂度是没问题的(因为增广一次就结束了)。

#include <bits/stdc++.h>
using namespace std;
using ll = long long;
#define all(x) (x).begin(),(x).end()

const int N = 2005;
bool np[N];
int pri[N], cnt=0;
void sieve(int n)
{
    np[1] = 1;
    for (int i=2; i<=n; ++i)
    {
        if (!np[i]) pri[++cnt] = i;
        for (int j=1; j<=cnt && i*pri[j] <= n; ++j)
        {
            np[i*pri[j]] = 1;
            if (!(i%pri[j])) break;
        }
    }
}

const int V = 2005, E = 4e6+5, inf = 2147483647;
struct MF
{
    int S,T,totv,tote;
    int fir[V],to[E],nxt[E],w[E];
    void adde(int x,int y,int z,int z2){
        to[++tote]=y,nxt[tote]=fir[x],fir[x]=tote,w[tote]=z;
        to[++tote]=x,nxt[tote]=fir[y],fir[y]=tote,w[tote]=z2;
    }
    void clear(){
        memset(fir,0,sizeof(fir)),tote=1;
    }
    int dis[V],q[V];
    bool bfs(){
        int l=1,r=0;
        memset(dis,0,sizeof(int)*(totv+1));
        dis[q[++r]=S]=1;
        while(l<=r){
            int u=q[l++];
            for(int i=fir[u];i;i=nxt[i])if(w[i]&&!dis[to[i]]){
                dis[q[++r]=to[i]]=dis[u]+1;
                if(to[i]==T)return 1;
            }
        }return 0;
    }
    int cur[V];
    int dfs(int u,int flow){
        if(u==T||!flow)return flow;
        int nowf=flow;
        for(int& i=cur[u];i;i=nxt[i])
            if(w[i]&&dis[to[i]]==dis[u]+1){
                int f=dfs(to[i],min(nowf,w[i]));
                w[i]-=f,w[i^1]+=f;
                if(!(nowf-=f))return flow;
            }
        return flow-nowf;
    }
    ll solve(){
        ll res=0,f;
        while(bfs()){
            memcpy(cur,fir,sizeof(fir));
            while(f=dfs(S,inf))res+=f;
        }
        return res;
    }
} G;

int n, a[N], b[N], id[N];

void solve()
{
    cin >> n;
    for (int i=1; i<=n; ++i) cin >> a[i];
    for (int i=n; i; --i) a[i] -= a[i-1];
    a[n+1] = inf;
    G.totv = n+1, G.S = ++G.totv, G.T = ++G.totv;
    ll ans = 0;

    G.clear();
    for (int i=1; i<=n; ++i)
    {
        if (a[i] > 0) G.adde(G.S, i, a[i], 0);
        else if (a[i] < 0) G.adde(i, G.T, -a[i], 0);
        id[i] = G.tote-1;
    }
    G.adde(G.S, n+1, inf, 0);
    for (int i=1; i<=n; ++i)
        for (int j=i+3; j<=n+1; j+=2)
            if (!np[j-i] && 1ll * a[i] * a[j] < 0)
            {
                if (a[i] > 0) G.adde(i, j, inf, 0);
                else G.adde(j, i, inf, 0);
            }
    ans += G.solve();
    for (int i=1; i<=n; ++i)
    {
        b[i] = G.w[id[i]];
    }

    G.clear();
    for (int i=1; i<=n; ++i)
    {
        if (a[i] > 0) G.adde(G.S, i, b[i], a[i]-b[i]);
        else if (a[i] < 0) G.adde(i, G.T, b[i], -a[i]-b[i]);
        id[i] = G.tote-1;
    }
    G.adde(G.S, n+1, inf, 0);
    for (int i=1; i<=n; ++i)
        for (int j=i+2; j<=n+1; j+=2)
            if (1ll * a[i] * a[j] < 0)
            {
                if (a[i] > 0) G.adde(i, j, inf, 0);
                else G.adde(j, i, inf, 0);
            }
    ans += G.solve() * 2;
    for (int i=1; i<=n; ++i)
    {
        b[i] = G.w[id[i]];
    }

    G.clear();
    for (int i=1; i<=n; ++i)
    {
        if (a[i] > 0) G.adde(G.S, i, b[i], a[i]-b[i]);
        else if (a[i] < 0) G.adde(i, G.T, b[i], -a[i]-b[i]);
        id[i] = G.tote-1;
    }
    G.adde(G.S, n+1, inf, 0);
    for (int i=1; i<=n; ++i)
        for (int j=i+1; j<=n+1; j+=2)
            if (np[j-i] && 1ll * a[i] * a[j] < 0)
            {
                if (a[i] > 0) G.adde(i, j, inf, 0);
                else G.adde(j, i, inf, 0);
            }
    ans += G.solve() * 3;

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

int main()
{
    sieve(2000);
    ios::sync_with_stdio(0); cin.tie(0);
    int T;
    cin >> T;
    while (T--) solve();
}
posted @ 2025-04-23 17:38  EssnSlaryt  阅读(35)  评论(0)    收藏  举报