国庆训练10.6

First

tag:博弈,中等

题面

2025: [POI2009]石子游戏Kam
时间限制:10秒 内存限制:162MB

题目描述
  有N堆石子,除了第一堆外,每堆石子个数都不少于前一堆的石子个数。两人轮流操作每次操作可以从一堆石子中移走任意多石子,但是要保证操作后仍然满足初始时的条件谁没有石子可移时输掉游戏。问先手是否必胜。
输入
  第一行u表示数据组数。对于每组数据,第一行n表示石子堆数,第二行N个数ai表示第i堆石子的个数(a1<=a2<=……<=an)。 1<=u<=10, 1<=n<=1000, 0<=ai<=10000

输出
  u行,若先手必胜输出TAK,否则输出NIE。
样例输入

2
2
2 2
3
1 2 4

样例输出

NIE
TAK

思路

题解
  博弈题,本题整体的思路为:本题博弈→阶梯博弈→尼姆博弈。具体过程如下:
  因为在移动时要保证满足初始条件,则第i堆可移动的石子数为:ai-ai-1(令bi=ai-ai-1)而若将第i堆移走m个石子则,第i+1堆可移动石子数目也+m,所以将第i堆移走m个石子就相当于从bi移动m个石子至bi+1;由此:原问题就可以转化为一个倒着的阶梯博弈问题,而阶梯博弈又可以转化为奇数项的尼姆博弈问题(移动偶数项相当于直接拿走:对方将m个石子从偶数项移到奇数项,you can 再从该奇数项move m个石子到下一个偶数项,最终会移动0即地面)即:
  a:      1   2   3   ……   n
  b:      1   2   3   ……   n
  阶梯博弈: n   n-1   n-2   ……  1   0
  尼姆博弈:    ……  5  3  1
  所以,只需要将a→b,在将b从n开始隔一个取一个的所有值做xor,判断ans是否为0(0:先手必败,else:先手必胜)

源码

#include <iostream>
using namespace std;
int main() {
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int u,n,a[1005],i,b[1005],ans;
    cin>>u;
    while(u--){
        cin>>n;
        for(i=1;i<=n;i++){//输入原始数据,a
            cin>>a[i];
        }
        ans=0;
        for(i=n;i>0;i-=2){//将原问题转化为阶梯博弈,a→b
            b[i]=a[i]-a[i-1];
                ans^=b[i];//将阶梯博弈转化为尼姆博弈,求xor
        }
        ans==0?cout<<"NIE\n":cout<<"TAK\n";//判断ans是否为0,得出答案
    }
    return 0;
}
/*
2
2
2 2
3
1 2 4
*/

Second

tag:并查集,图论,中等

题面

2026: [POI2008]CLO
时间限制:10秒 内存限制:162MB

题目描述
  Byteotia城市有n个 towns m条双向roads. 每条 road 连接 两个不同的 towns ,没有重复的road. 你要把其中一些road变成单向边使得:每个town都有且只有一个入度。
输入
  第一行输入n m.1 <= n<= 100000,1 <= m <= 200000 下面M行用于描述M条边.

输出
  TAK或者NIE 常做POI的同学,应该知道这两个单词的了…
样例输入

4 5
1 2
2 3
1 3
3 4
1 4

样例输出

TAK

思路

题解
  首先说一下,我的做题过程。本来以为是一道简单的图论+贪心题,但是却一直wa,百思不得其解……,在网上搜了题解,才明白过来,自己有一个图论的知识点,理解错了:无向边不贡献出度,也不贡献入度but me think 无向边贡献出度,贡献入度
  这道题用并查集做,(建图的话,数据量太大,存储不下),首先,初始化:N个节点,每个建立一个集合,第i个节点所在集合编号为i;然后处理m条边,只要两个节点之间有边,就把他们放到一个集合里面,如果加的边的两个节点在同一个集合里,那么这个集合标记为true(初始所有集合为false);处理完所以边之后,判断:只要有一个集合为false,则ans=NIE;只有所有集合均为true,ans=TAK。

源码

/************并查集***************/
#include<iostream>
#include <cstring>
#define mem(a,b) memset(a,b,sizeof(a))
using namespace std;
const int N=1e5+5;
int fa[N];//f[i]表示节点i与谁在一个集合,初始f[i]=i,
//如:f[1]=2,表示1与2在一个集合,但2有可能在其他集合,所以i不一定在集合2
bool can[N];//can[i]表示集合i是否可行,初始值均为false
int find(int x){//核心函数,查找节点x在哪个集合
    if (fa[x]==x)   return x;//在本身集合,直接返回
    fa[x]=find(fa[x]);//在其他集合,看其他集合是否加入新集合,并更新
    return fa[x];//返回所在集合
}
int main(){
    ios::sync_with_stdio(0);
    cin.tie(0);cout.tie(0);
    int n,m,i,x,y,a,b;
    cin>>n>>m;
    mem(can, false);//初始化can[],均为false
    for (i=1;i<=n;i++)  fa[i]=i;//初始化f[],f[i]=i
    for (i=1;i<=m;i++){
        cin>>a>>b;
        x=find(a),y=find(b);//分别查找a,b所在集合
        x!=y?(fa[x]=y,can[y]|=can[x]):can[fa[x]]= true;//若a,b在同一集合,则该集合改为true;
        //否则,将a所在集合x加入b所在集合y,并修改集合y的can值,只要集合x、y有一个为true则该集合为true;
    }
    for (i=1;i<=n;i++){
        if (!can[find(i)]){//只要有一个节点所在集合为false,那么就不行(NIE)
            cout<<"NIE\n";return 0;
        }
    }
    cout<<"TAK\n";//所有节点所在集合为true,才可以(TAK)
    return 0;
}
/*
4 4
1 2
1 3
1 4
2 4
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值