2025 ICPC 南昌邀请赛暨江西省赛 个人补题笔记

赛事信息

这场 vp 出了 7 道题,整体难度偏简单,下面先把我能补的+赛场上写的写一下。


A. Nezha Naohai

题意:

哪吒在海里闹了三次事:

  • 第一次闹事耗时 \(A\) 小时
  • 第二次闹事耗时 \(B\) 小时
  • 第三次闹事耗时 \(C\) 小时

每在海里闹事 \(1\) 小时,就会产生 \(D\) 个投诉。
需要计算:三次闹事总共产生了多少个投诉?

思路+代码:

  感觉这道题只要读懂题就能写

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

int main() {
    int a, b, c, d;
    cin >> a >> b >> c >> d;
    cout << (a + b + c) * d << endl;
    return 0;
}


C. Osiris

题意:

\(A\)\(B\) 两个人进行扑克赌局:

  • 双方各持 \(5\) 张初始手牌,\(A\) 可对 \(k = 1,2,\dots,5\) 选择弃掉 \(k\) 张牌并重抽 \(k\) 张(不放回)。
  • 比较双方手牌点数和:\(A\) 点数和更大则赢 \(k\) 枚筹码,点数和更小则输 \(k\) 枚筹码,点数和相等则筹码数量不变。
  • \(A\) 要对每个 \(k\) 选择最优弃牌策略,求能获得的最大期望筹码数,结果对 \(998244353\) 取模。

输入:\(A\)\(5\) 张初始手牌(合法输入为 \(A\)\(2\)~\(10\)\(J\)\(Q\)\(K\))。

输出:\(5\) 行,分别对应 \(k = 1\)\(k = 5\) 时的最大期望筹码数(结果对 \(998244353\) 取模)。

思路+代码:

  仔细观察可以发现,这本质上就是搜素,但如果直接搜素的话就是 \(C_{47}^{10}=5178066751\),最后肯定会超时,所以需要将搜索优化为 \(DP\)

  我们想要的是方案数,而这个方案数取决于两个人选的牌的数量以及各自的得分,所以我们可以定义一个四维 \(DP\),设 \(dp[i][x][j][y]\)\(A\)\(i\) 张牌得分为 \(x\) ,且 \(B\)\(j\) 张牌得分为 \(y\) 的方案数。这个方案数如何计算呢?我们不妨考虑如何转移到这个状态,对于当前大小为 \(a\) 的一张牌,我们可以考虑把这张牌给 \(A\) 或者 \(B\)。如果转移到 \(dp[i][x][j][y]\) 这一状态肯定是由 \(dp[i-1][x-a][j][y]\)\(dp[i][x][j-1][y-a]\)两个状态来转移。多张牌考虑多重背包问题即可。

AC 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const ll mod=998244353;
ll dp[6][65][6][65];
ll qmi(ll a,ll b,ll c){
    ll res=1;
    while(b){
        if(b&1)
            res=res*a%c;
        a=a*a%c;
        b>>=1;
    }
    return res;
}
map<string,int>id;
int cnt[15];

void init(){
    id["A"]=1;
    for(int i=2;i<=9;i++){
        char ch='0'+i;
        string r;
        r+=ch;
        id[r]=i;
    }
    id["10"]=10;
    id["J"]=11;
    id["Q"]=12;
    id["K"]=13;
    for(int i=1;i<=13;i++)
        cnt[i]=4;
}

int main(){
    init();
    vector<int>num;
    ll sa=0;
    for(int i=1;i<=5;i++){
        string s;
        cin>>s;
        cnt[id[s]]--;
        num.push_back(id[s]);
        sa=sa+id[s];
    }
    sort(num.begin(),num.end());
    dp[0][0][0][0]=1;
    for(int c=1;c<=13;c++){
        for(int num=1;num<=cnt[c];num++)
            for(int i=5;i>=0;i--){
                for(int j=5;j>=0;j--){
                    for(int x=64;x>=0;x--){
                        for(int y=64;y>=0;y--){
                                if(c<=x&&i>0)
                                    dp[i][x][j][y]=(dp[i][x][j][y]+dp[i-1][x-c][j][y])%mod;
                                if(c<=y&&j>0)
                                    dp[i][x][j][y]=(dp[i][x][j][y]+dp[i][x][j-1][y-c])%mod;
                        }
                    }
                }
            }
    }
    for(int k=1;k<=5;k++){
        ll ans=0,cnt1=0,cnt2=0,cnt=0;
        sa=sa-num[k-1];
        for(int x=0;x<=64;x++){
            for(int y=0;y<=64;y++){
                cnt=(cnt+dp[k][x][5][y])%mod;
                if(x+sa>y)
                    cnt1=(cnt1+dp[k][x][5][y])%mod;
                if(x+sa<y)
                    cnt2=(cnt2+dp[k][x][5][y])%mod;                           
            }
        }
        ans=(((k)*cnt1%mod+(-k)*cnt2%mod)%mod*qmi(cnt,mod-2,mod)%mod+mod)%mod;
        cout<<ans<<'\n';
    }
    return 0;
}



D. Virtuous Pope

题意:

给定一个三维长方体(长 \(a\)、宽 \(b\)、高 \(c\)),内部有 \(n\) 条线段(每条线段的两个端点均位于长方体表面)。

\(Dio\) 会选择一个垂直于坐标轴 \(x\)\(y\)\(z\) 其中之一的平面发起攻击。
问:\(Dio\) 选择哪个平面,能使同时被该平面穿过的线段数量最多?(即求与该平面相交的线段数量的最大值)

思路+代码:

  思路其实很直接,只需要关注一个维度分别分析即可,比如考虑垂直于坐标轴 \(x\) 的平面时,要想线段穿过平面,就只需要考虑 \(x\) 轴方向带来的影响,至于 \(y\) 轴方向和 \(z\) 轴方向因为并不影响线段是否穿过这个平面,所以这时候其实并不关心。

  假设这个平面在 \(x\) 轴位于 \(x_{i}\) 的位置,我们考虑线段穿过这个平面的线段时候,只考虑线段两个端点在 \(x\) 轴方向是否在 \(x_{i}\) 的两端(也就是 \(x_{i}\) 是否在 \([x_{1},x_{2}]\) 的范围内 (假设\(x_{1}<x_{2}\)))。同理,考虑 \(y\) 轴方向和 \(z\) 轴方向也是同一个道理,只需要分别对三个维度求最值即可。

  对同一个维度分析,本质上就是求区间相交的最大数量,假设区间相交的最大数量的一个解区间为 \([l,r]\)(也就是 \([l,r]\) 内的每一个点都是区间相交的最大数量的一个点),可以容易看出,解区间中 \(r\) 的值必然是某个线段区间的右端点(如果 \(r\) 不是某个线段区间的右端点,则必然存在一个正数 \(a\),使得所有包含 \(r\) 点的区间 \([l_{i},r_{i}]\) 必然能包含 \(r+a\) 这一点,这样 \(r\) 就不是解区间的右端点,互相矛盾)。

  因此我们可以把所有线段区间 \([l_{i},r_{i}]\) 的右端点当作答案来考虑,这样就只需要考虑哪些区间包含这个右端点 \(r_{ans}\),首先区间 \([l_{i},r_{i}]\) 要想包含 \(r_{ans}\) ,需要满足:\(l_{i}≤r_{ans}≤r_{i}\) ,我们可以先对 \(r_{i}\) 进行从小到大的排序,然后统计所有 \(l_{i}≤r_{ans}\) 的数量,最后减去 \(r_{i}<r_{ans}\) 数量,剩下的就是答案。

AC 代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+100;
typedef pair<int,int> PII;
#define l first
#define r second
int n,a,b,c;
PII x[N],y[N],z[N];
int ans;

bool cmp(PII a,PII b){
    return a.r<b.r;
}

void solve(PII a[]){
    sort(a+1,a+1+n,cmp);
    priority_queue<int,vector<int>,greater<int> >q;
    int cnt=0,d=0,r=a[1].r;
    for(int i=1;i<=n;i++)
        q.push(a[i].l);
    for(int i=1;i<=n;i++){
        if(a[i].r!=r){
            d=i-1;
        }
        r=a[i].r;
        while(q.size()&&q.top()<=r){
            q.pop();
            cnt++;
        }
        ans=max(ans,cnt-d);
    }
}

int main(){
    cin>>n>>a>>b>>c;
    for(int i=1;i<=n;i++){
        cin>>x[i].l>>y[i].l>>z[i].l>>x[i].r>>y[i].r>>z[i].r;
        if(x[i].l>x[i].r)
            swap(x[i].l,x[i].r);
        if(y[i].l>y[i].r)
            swap(y[i].l,y[i].r);
        if(z[i].l>z[i].r)
            swap(z[i].l,z[i].r);       
    }    
    solve(x);
    solve(y);
    solve(z);
    cout<<ans<<'\n';
    return 0;
}


E. God’s String on This Wonderful World

题意:

给定只由小写字母组成的字符串 \(s\)、正整数 \(k\)\(q\) 次区间查询 \([x_i, y_i]\),对每个查询,统计该区间内所有满足:子串长度为 \(k\) 的倍数,且每个字符出现次数都能被 \(k\) 整除 的子串数量(这类子串重排后可成为 \(k\) 重串)。

思路+代码:

  假设区间 \([l_{x},r_{x}]\) 是满足条件的子串,当且仅当在 \([l_{x},r_{x}]\) 内部,对于任意字符 \(c\) ,其出现的次数都为 \(k\) 的倍数。根据同余转换一下,就是对于任意字符 \(c\) ,设其在 \([1,id]\) 出现次数为 \(cnt_{id,c}\) ,那么上述转换一下就是需要满足 \(cnt_{r,c} ≡ cnt_{l-1,c} \pmod{k}\)。因此对于每一次的查询 \([x_i, y_i]\) ,我们只需要确定在 \([x_i-1, y_i]\) 内部,有多少满足 \(∀c∈[a,z],cnt_{l,c} ≡ cnt_{r,c} \pmod{k}\) 即可。

  对于 \(∀c∈[a,z]\),我们设\(cnt_{l,c} = cnt_{r,c}~mod~k\),然后对于 $∀c∈[a,z],cnt_{l,c}=x_{c} $ 这一个整体定义一个哈希值。然后利用莫队维护这个哈希值的数量,并根据其求答案即可。

AC 代码
#include <bits/stdc++.h>
using namespace std;
const int N=3e5+100;
typedef long long ll;
struct node{
    int l,r,id;
};
node p[N];
int n,k,q;
string s;
vector<int>c;
map<vector<int>,int>cnt;
int id[N];
int idx,block;
ll sum[N],res,ans[N];

bool cmp(node a,node b){
    if(a.l/block!=b.l/block)
        return a.l<b.l;
    return a.r<b.r;
}

void add(int x){
    res+=sum[x];
    sum[x]++;
}

void del(int x){    
    sum[x]--;
    res-=sum[x];
}


int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cin>>n>>k>>q;
    c.resize(26,0);
    cin>>s;
    s=" "+s;
    cnt[c]=++idx;
    id[0]=idx;
    for(int i=1;i<=n;i++){
        c[s[i]-'a']++;
        c[s[i]-'a']%=k;
        if(!cnt[c])
            cnt[c]=++idx;
        id[i]=cnt[c];
    }  
    for(int i=1;i<=q;i++){
        cin>>p[i].l>>p[i].r;
        p[i].l--;
        p[i].id=i;
    }
    block=sqrt(n);
    sort(p+1,p+1+q,cmp);
    int l=1,r=0;
    for(int i=1;i<=q;i++){
        while(l>p[i].l){
            add(id[--l]);
        }
        while(r<p[i].r){
            add(id[++r]);
        }
        while(l<p[i].l){
            del(id[l++]);
        }
        while(r>p[i].r){
            del(id[r--]);
        }
        ans[p[i].id]=res;
    }

    for(int i=1;i<=q;i++)
        cout<<ans[i]<<'\n';
    return 0;
}


F. Caloric Difference

题意:

红龙 Dew 的主人要求她控制热量摄入。

在接下来的 \(n\) 天里:

  • \(i\) 天,Dew 摄入热量 \(r_i\),代谢质量 \(c_i\)
  • 代谢量满足递推关系:

\[c_i = p \times c_{i-1} + (1-p) \times r_{i-1} \]

其中初始值 \(r_0, c_0\) 和参数 \(p\ (0 < p < 1)\) 已知。

  • Dew 有 \(k\) 天会放纵饮食,这 \(k\) 天的 \(r_i\) 是固定的;其余天的 \(r_i\) 可自由选择,但必须满足 \(L \le r_i \le R\)
  • 目标是合理安排非放纵日的 \(r_i\),最大化 \(n\) 天内总代谢与总摄入的差值:

\[\max \sum_{i=1}^n (c_i - r_i) \]

思路+代码:

我们可以推一下公式,推导如下:

\[c_i = p \times c_{i-1} + (1-p) \times r_{i-1} \]

\[c_i - c_{i-1} = (1-p)\,(r_{i-1} - c_{i-1}) \]

\[c_i - c_{i-1} = (p-1)\,(c_{i-1} - r_{i-1}) \]

\[c_{i-1} - r_{i-1} = \frac{c_i - c_{i-1}}{p-1} \]

所以求和公式可以变成:\(\quad \sum_{i=1}^m (c_i - r_i) = \sum_{i=1}^m \frac{c_{i+1} - c_i}{p-1} = \frac{c_{m+1} - c_1}{p-1}= \frac{c_{1} - c_{m+1}}{1-p}\)
因此,我们只需要让 \(c_{1} - c_{m+1}\) 尽可能大即可,也就是让 \(c_{m+1}\) 尽可能小,根据 \(c_i = p \times c_{i-1} + (1-p) \times r_{i-1}\) 只需让 \(r_{i}\) 尽可能小即可。

AC 代码
#include <bits/stdc++.h>
using namespace std;
const int N=1e5+100;
typedef double ld;
int n,k;
ld r[N],c[N],L,R,p;

void solve(){
    cin>>n>>k;
    cin>>r[0]>>c[0]>>p>>L>>R;
    for(int i=1;i<=n;i++)
        r[i]=L;
    while(k--){
        int u;
        ld x;
        cin>>u>>x;
        r[u]=x;
    }
    for(int i=1;i<=n+1;i++)
        c[i]=p*c[i-1]+(1-p)*r[i-1];
    printf("%0.10lf\n",(c[1]-c[n+1])/(1-p));
}

int main(){
    int t;
    cin>>t;
    while(t--){
        solve();
    }
    return 0;
}


G. Exploration

题意:

Alice 在一个危险的巨大迷宫中探险。这个迷宫有 \(n\) 个洞穴(编号 \(1 \sim n\))和 \(m\)单向通道(通道中的陷阱使得走过之后难以返回)。
每条通道表示为 \((u, v)\),其中 \(u\) 是起点洞穴,\(v\) 是终点洞穴,且带有难度值 \(d_{(u,v)}\)。整个洞穴系统可视为一张加权有向图

初始时,Alice 拥有初始体力(或称为“能量”)\(x\)。当她通过一条难度为 \(d\) 的通道时,体力 \(x\) 会变为:

\[\left\lfloor \frac{x}{d} \right\rfloor \]

\(\lfloor \cdot \rfloor\) 表示向下取整)。当体力降至 \(0\) 时,体力耗尽,无法继续探索。

例如:若初始体力为 \(9\),通过难度 \(2\) 的通道后体力变为 \(4\)\(\lfloor \frac{9}{2} \rfloor = 4\));再通过一条难度 \(2\) 的通道后体力变为 \(2\)\(\lfloor \frac{4}{2} \rfloor = 2\));最后通过一条难度 \(3\) 的通道后体力变为 \(0\)\(\lfloor \frac{2}{3} \rfloor = 0\)),此时体力耗尽。

由于 Alice 方向感很差,探索过程中她不知道自己所在位置或通道走向。因此她有 \(Q\) 个问题:
\(i\) 个问题:若从洞穴 \(p_i\) 出发,初始体力为 \(x_i\),每次随机选择可走的通道(可重复选择已走过的通道),求在体力耗尽前必须经过的最少通道数
(注:重复走过的通道仍会使体力按 \(x \to \lfloor \frac{x}{d} \rfloor\) 变化)

保证每个洞穴至少有 \(1\) 条出边(通道的起点和终点可以是同一个洞穴)。

思路+代码:

  可以发现, \(2^{30}>10^{9}\) , 所以经过通道数最多不会超过 \(30\) 次。我们可以很容易知道每个节点跑一次的最大难度值,可以根据以上信息来维护一个状态。

  设 \(dp[u][j]\) 为节点 \(u\) 走了 \(j\) 个通道的最大难度值,假设节点 \(v\) 是节点 \(u\) 的相邻节点,那么假设考虑节点 \(u\) 走了 \(j\) 个通道,且第一步走到节点 \(v\) 的时候时,可以直接利用 \(dp[v][j-1]\) 来考虑,转移方程为:

\[dp[u][j]=max(dp[u][j],dp[v][j-1]*d_{u,v}) \]

  由于经过通道数最多不会超过 \(30\) 次,所以无论是时间还是空间都在条件范围内,最后对于每个查询直接暴力枚举该节点经过通道的数最即可。

AC 代码
#include <bits/stdc++.h>
using namespace std;
const int N=2e5+100;
typedef long long ll;
typedef pair<int,ll> PII;
#define l first
#define r second
int n,m,q;
vector<PII>eg[N];
ll dp[N][40];

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n>>m>>q;
    for(int i=1;i<=m;i++){
        int u,v;
        ll w;
        cin>>u>>v>>w;
        eg[u].push_back({v,w});
    }

    for(int i=1;i<=n;i++)
        dp[i][0]=1;
    
    for(int j=1;j<=30;j++){
        for(int u=1;u<=n;u++){
            if(dp[u][j-1]==0||dp[u][j-1]>1e9)
                continue;
            for(auto [v,w]:eg[u]){
                dp[u][j]=max(dp[u][j],dp[v][j-1]*w);
                if(dp[u][j]>1e9)
                    break;
            }
        }
    }
    while(q--){
        int p;
        ll x;
        cin>>p>>x;
        for(int i=1;;i++){
            if(dp[p][i]>x){
                cout<<i<<'\n';
                break;
            }
        }
    }
    return 0;
}


I. Dating Day

题意:

TreeQwQ 一天有 \(n\) 个时间段,用二进制串 \(s\) 描述日程:

  • \(s_i = 1\):第 \(i\) 个时间段在约会
  • \(s_i = 0\):第 \(i\) 个时间段空闲
    你需要对日程执行恰好一次操作
  1. 选择区间 \([l,r]\ (1 \le l \le r \le n)\),要求该区间内恰好包含 \(k\) 个约会时间段(即 \(k\)\(1\))。
  2. 任意修改 \([l,r]\) 内所有时间段的状态(\(0\)\(1\)\(1\)\(0\),可多次修改)。
  3. 修改后必须保证:区间 \([l,r]\) 内仍恰好有 \(k\) 个约会时间段。
    求执行操作后,TreeQwQ 可能得到的不同日程数量,结果对 \(998244353\) 取模。
    (两个日程不同当且仅当存在某个时间段,一个是约会状态,另一个是空闲状态)

思路+代码:

  假设区间 \([l,r]\) 内部有 \(k\) 个 1, 则若想考虑的情况尽可能全面,则需要让这个区间在保证内部有 \(k\) 个 1的同时尽可能大。然后若是只考虑这个区间造成的不同日程数量则答案是 \(C_{r-l+1}^{k}\) (本质上就是这 \(r-l+1\) 天选择 \(k\) 天约会)。

  然后对于上述这样的两个区间 \([l_{1},r_{1}]\)\([l_{2},r_{2}]\) (设 \(r_{2}>r_{1}>l_{2}>l_{1}\) ),且两个区间的交集 \([l_{2},r_{1}]\) 里面有 \(k-1\) 个 1,若想求两个区间 \([l_{1},r_{1}]\)\([l_{2},r_{2}]\) 共同贡献的不同日程数量的答案就需要消去两个区间公共区间 \([l_{2},r_{1}]\) 多进行计算的部分。可以容易发现,公共区间多进行计算的部分就是 \([l_{2},r_{1}]\) 里面最后出现恰好 \(k-1\) 个 1 的情况。

  因为区间 \([l_{1},r_{1}]\) 可以只让 \([l_{2},r_{1}]\) 里面 \(k-1\) 个 1 进行排列组合,区间 \([l_{2},r_{2}]\) 也可以只让 \([l_{2},r_{1}]\) 里面 \(k-1\) 个 1 进行排列组合,这无疑是多余计算的,其他部分两个区间互不干涉,所以最后两部分的总贡献为 \(C_{r_{1}-l_{1}+1}^{k}+C_{r_{2}-l_{2}+1}^{k}-C_{r_{1}-l_{2}+1}^{k-1}\) 即可。

  有了以上两个基本思路,从最左边的区间向右扩展即可。

AC 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N=2e5+100;
const ll mod=998244353;
ll id[N];
ll A[N];

ll qmi(ll a,ll b,ll c){
    ll res=1;
    while(b){
        if(b&1)
            res=res*a%c;
        a=a*a%c;
        b>>=1;
    }
    return res;
}

void init(){
    A[0]=1;
    for(int i=1;i<N;i++)
        A[i]=(A[i-1]*i)%mod;
}

ll C(ll x,ll y){
    return A[y]*qmi(A[x],mod-2,mod)%mod*qmi(A[y-x],mod-2,mod)%mod;
}

void solve(){
    int n,k;    
    string s;
    cin>>n>>k;
    cin>>s;
    s=" "+s;
    int len=0;
    for(int i=1;i<=n;i++)
        if(s[i]=='1')
            id[++len]=i;
    id[len+1]=n+1;
    ll ans=0;
    for(int i=k;i<=len;i++){
        int r=id[i+1]-1,l=id[i-k]+1;
        ans=(ans+C(k,r-l+1)%mod)%mod;
        if(i>k){
            l=id[i-k]+1;
            r=id[i]-1;
            ans=((ans-C(k-1,r-l+1)%mod)%mod+mod)%mod;
        }
    }
    cout<<ans<<'\n';
}

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


K. Rotation

题意:

你有 \(n\) 尊石像,每尊石像都有一个鼻子,可面向 4 个方向:前(front)、右(right)、后(back)、左(left)。
每次操作可以选择以下两种之一:

  • 按某一尊石像的鼻子,使其余所有石像顺时针旋转 \(90^\circ\)
  • 按你自己的鼻子,使所有石像顺时针旋转 \(90^\circ\)

顺时针旋转的方向序列为:

\[\text{front} \to \text{right} \to \text{back} \to \text{left} \]

问:使所有石像都面向前方(front)所需的最少操作次数是多少

思路+代码:

  我们最终肯定是把所有石像都统一为一个方向的,这步操作必然需要操作1,我们不妨分析一下,假设朝向 \(a\) 方向的石像有 \(x\) 个,下一步顺时针旋转 \(90^\circ\) 朝向 \(a\) 方向的石像有 \(y\) 个,假设我们对朝向 \(a\) 方向的其中一个石像进行操作1,那么就会让原本朝向 \(a\) 方向的石像数量变为 \(x-1\) 个,原本下一步顺时针旋转 \(90^\circ\) 朝向 \(a\) 方向的石像有 \(y+1\) 个,据此递推,可以发现合理的进行操作1可以让所有石像的方向都统一。

  根据以上这个思路,我们可以按最开始的方向分为四个集合,数量分别为 \(x_{0}\)\(x_{1}\)\(x_{2}\)\(x_{3}\) 四个集合,每次操作1本质上是两个相邻集合(即 \(x_{i}\)\(x_{(i+1)~mod~4}\) )的数量交换,可以让 \(x_{(i+1)~mod~4}\) 的数量全部转移到 \(x_{i}\) 上面,最后转移到最后一个集合上,统计转移了多少次,然后判断最后落在哪里,然后进行操作2。

  对于位置 \(i\) 而言,设其他位置转化到位置 \(i\) 的操作次数 \(cnt_{i}=x_{(i+1)~mod~4}+2*x_{(i+2)~mod~4}+3*x_{(i+3)~mod~4}\) ,然后位置 \(i\) 由于操作1被转移到位置 \((i+cnt_{i})~mod~4\) 上,最后答案就是 \(cnt_{i}+(4-(i+cnt_{i})~mod~4)\)

AC 代码
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
ll a[4],ans;
int n;

void find(int id){
    ll cnt=0;
    cnt=a[(id+1)%4]+2*a[(id+2)%4]+3*a[(id+3)%4];
    ans=min(ans,cnt+(4-(id+cnt)%4)%4);
}

int main(){
    ios::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);
    cin>>n;
    for(int i=1;i<=n;i++){
        int x;
        cin>>x;
        a[x]++;
    }
    ans=1e18;
    find(0);
    find(1);
    find(2);
    find(3);
    cout<<ans<<'\n';
    return 0;
}


M. Divide coins

题意:

Little T 和 Little J 用 \(n\) 枚初始正面朝上的硬币玩游戏:
两人约定数 \(k\),Little T 随机翻转 \(k\) 枚硬币至背面朝上(Little J 始终不知道硬币的具体哪个是正的)。
Little J 为每枚硬币分配四种操作之一:

  • \(1\):放入第一堆(不翻转)
  • \(2\):放入第一堆并翻转
  • \(3\):放入第二堆(不翻转)
  • \(4\):放入第二堆并翻转

Little J 获胜当且仅当最终两堆正面硬币数相等。

任务:判断是否存在固定操作序列,使 Little J 对任意 \(k\) 枚被翻硬币都必胜。若存在则输出操作串(\(1\)\(4\)),否则输出 \(-1\)

思路+代码:

  结论很简单,把 \(k\) 枚硬币放第二堆不翻开,再把剩余 \(n-k\) 枚硬币放第一堆都翻开即可,证明思路如下:

    假设第二堆有 \(c\) 枚背面朝下的硬币,则必然有 \(k-c\) 枚正面朝上的硬币。 这样第一堆翻转前就有 \(k-c\) 枚背面朝下的硬币以及 \(n-2*k+c\) 枚正面朝上的硬币,翻转后就有 \(k-c\) 枚背面朝上的硬币以及 \(n-2*k+c\) 枚正面朝下的硬币。可以发现这个思路肯定是正确的。

AC 代码
#include <bits/stdc++.h>
using namespace std;
int n,k;

int main(){
    cin>>n>>k;
    for(int i=1;i<=n;i++){
        if(i<=k)
            cout<<3;
        else
            cout<<2;
    }
    cout<<'\n';
    return 0;
}
posted @ 2026-04-02 10:10  潇冉沐晴  阅读(72)  评论(0)    收藏  举报