1 条题解

  • 0
    @ 2026-4-5 14:45:04

    Card Flip 详细题解(对标标程 + 博弈论核心)

    这是一道 贪心排序 + 博弈论(Nim 游戏 / 奇偶性判断) 的经典题,数据范围 n,m5×105n,m \le 5\times10^5,要求 O(nlogn+m)O(n\log n + m) 算法。

    一、题目翻译与规则解析

    题意

    nn双面牌mm单面牌

    1. 双面牌:正面数字 aia_i,背面数字 bib_i
    2. 单面牌:只有正面数字 cic_i; 所有数字互不重复,且恰好覆盖 12n+m1 \sim 2n+m

    初始所有牌正面朝上,两人轮流操作(Petya 先手),每回合必须执行恰好一种操作

    1. 移除当前桌上最小数字的牌;
    2. 翻转:若当前最小数字的牌是双面牌且正面朝上,可以翻转它(翻面后显示 bib_i)。

    拿走最后一张牌的人获胜,判断谁必胜。


    二、核心博弈结论(破题关键)

    1. 关键观察

    • 所有数字严格唯一,全局最小值一定是当前必须处理的牌
    • 单面牌无法翻转,只能被移除;
    • 双面牌只有是当前最小值且正面朝上时,才能选择翻转;
    • 翻转的本质:把一个小数字替换成一个大数字,推迟它被移除的时间

    2. 终极结论(对标标程)

    1. 把双面牌按正面数字 aia_i 升序排序
    2. 找到最左侧的关键位置 lstlst:满足 d[lst].bd[lst].b 大于所有右侧双面牌的 aa(即从 lstlst 开始,右侧所有双面牌都无法翻转,只能按顺序移除);
    3. 统计单面牌中比 d[lst].ad[lst].a 小的数量 ansans
    4. 最终判断:
      • (ans+lst)%2=1(ans + lst) \% 2 = 1先手(First)胜
      • 否则 → 后手(Second)胜

    三、标程逻辑逐行拆解

    1. 数据结构与输入

    struct db{ ll a,b; } d[500010];  // 存储双面牌:a正面,b背面
    ll n,m,c[500010];               // c:单面牌
    ll lst,ans;
    
    • 存储所有双面牌的 aia_i(正面)、bib_i(背面);
    • 存储单面牌 cic_i

    2. 排序(核心第一步)

    sort(d+1,d+n+1,comp);  // 按双面牌正面数字 a_i 升序排序
    

    ✅ 必须按正面数字从小到大排序,因为游戏始终处理当前最小值

    3. 找关键位置 lstlst

    lst=n;
    for(int i=n-1;i>=1;i--)
        if(d[i].b < d[lst].a) lst=i;
    
    • 从右往左遍历,维护最右侧合法位置 lstlst
    • 规则:d[i].b<d[lst].ad[i].b < d[lst].a 时,更新 lst=ilst=i
    • 含义:lstlst 左边的牌可以翻转,lstlst 及右边的牌无法翻转,只能按顺序移除

    4. 统计小单面牌数量

    for(int i=1;i<=m;i++)
        if(c[i] < d[lst].a) ans++;
    

    统计d[lst].ad[lst].a 小的单面牌数量,这些牌会被优先移除。

    5. 胜负判断

    if((ans+lst)%2) cout<<"First"<<endl;
    else cout<<"Second"<<endl;
    
    • 总可确定步数 = 小单面牌数 ansans + 关键位置 lstlst
    • 奇数 → 先手胜,偶数 → 后手胜

    四、样例演算(第一个样例)

    输入: n=2, m=1n=2,\ m=1 双面正面:5,35,3 双面背面:1,21,2 单面:44

    步骤1:排序双面牌(按 aia_i 升序)

    原双面:(5,1)(5,1)(3,2)(3,2) 排序后:(3,2)\boldsymbol{(3,2)}(5,1)\boldsymbol{(5,1)}

    步骤2:找 lstlst

    • lst=2lst=2
    • i=1i=1d[1].b=2<d[2].a=5d[1].b=2 < d[2].a=5lst=1lst=1

    步骤3:统计 ansans

    d[lst].a=3d[lst].a=3,单面牌 4>34>3ans=0ans=0

    步骤4:判断

    (0+1)%2=1(0+1)\%2=1First 胜,与样例一致。


    五、算法复杂度

    • 排序双面牌:O(nlogn)O(n\log n)
    • lstlstO(n)O(n)
    • 统计单面牌:O(m)O(m) 总复杂度:O(nlogn+m)\boldsymbol{O(n\log n + m)},完美通过 5×1055\times10^5 数据。

    六、标程完整注释版

    #include<bits/stdc++.h>
    using namespace std;
    typedef long long ll;
    
    // 双面牌:a正面,b背面
    struct db{
    	ll a,b;
    } d[500010];
    
    ll n,m,c[500010];  // c:单面牌
    ll lst,ans;        // lst:关键位置,ans:小单面牌数量
    
    // 按正面数字升序排序
    bool comp(db a,db b){
    	return a.a < b.a;
    }
    
    int main(){
    	cin>>n>>m;
    	// 输入双面牌正面
    	for(int i=1;i<=n;i++) cin>>d[i].a;
    	// 输入双面牌背面
    	for(int i=1;i<=n;i++) cin>>d[i].b;
    	// 输入单面牌
    	for(int i=1;i<=m;i++) cin>>c[i];
    
    	// 按正面数字排序
    	sort(d+1,d+n+1,comp);
    
    	// 找关键位置 lst
    	lst = n;
    	for(int i=n-1;i>=1;i--)
    		if(d[i].b < d[lst].a) lst = i;
    
    	// 统计比 d[lst].a 小的单面牌数量
    	ans = 0;
    	for(int i=1;i<=m;i++)
    		if(c[i] < d[lst].a) ans++;
    
    	// 奇偶性判断胜负
    	if((ans + lst) % 2 == 1)
    		cout<<"First"<<endl;
    	else
    		cout<<"Second"<<endl;
    
    	return 0;
    }
    

    总结

    1. 核心思想:排序双面牌 → 找关键不可翻转位置 lstlst → 统计小单面牌;
    2. 胜负规则(ans+lst)(ans+lst) 奇数先手胜,偶数后手胜;
    3. 复杂度O(nlogn+m)O(n\log n + m),通过全部测试点。
    • 1

    信息

    ID
    6386
    时间
    1000ms
    内存
    256MiB
    难度
    10
    标签
    递交数
    1
    已通过
    1
    上传者