2025 ICPC 南昌邀请赛暨江西省赛 个人补题笔记
赛事信息
- 题目链接:https://codeforces.com/gym/105911
- 赛事榜单:https://board.xcpcio.com/icpc/50th/nanchang-invitational
这场 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\)。
- 代谢量满足递推关系:
其中初始值 \(r_0, c_0\) 和参数 \(p\ (0 < p < 1)\) 已知。
- Dew 有 \(k\) 天会放纵饮食,这 \(k\) 天的 \(r_i\) 是固定的;其余天的 \(r_i\) 可自由选择,但必须满足 \(L \le r_i \le R\)。
- 目标是合理安排非放纵日的 \(r_i\),最大化 \(n\) 天内总代谢与总摄入的差值:
思路+代码:
我们可以推一下公式,推导如下:
所以求和公式可以变成:\(\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\) 会变为:
(\(\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]\) 来考虑,转移方程为:
由于经过通道数最多不会超过 \(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\) 个时间段空闲
你需要对日程执行恰好一次操作:
- 选择区间 \([l,r]\ (1 \le l \le r \le n)\),要求该区间内恰好包含 \(k\) 个约会时间段(即 \(k\) 个 \(1\))。
- 任意修改 \([l,r]\) 内所有时间段的状态(\(0\) 变 \(1\) 或 \(1\) 变 \(0\),可多次修改)。
- 修改后必须保证:区间 \([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\)。
顺时针旋转的方向序列为:
问:使所有石像都面向前方(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;
}

浙公网安备 33010602011771号