【atcoder 10】【博弈论+树形dp】【对一棵树每次从棋子所在节点移走一个石头,将棋子移到与当前节点相邻的节点】

传送门:

http://agc010.contest.atcoder.jp/tasks/agc010_f

题意:

给出一棵N个节点的树,每个节点有Ai个石头
Takahashi先选择一个节点v放置一个棋子,然后Takahashi和Aoki轮流操作(Takahashi先)
.从棋子所在节点移走一个石头
.将棋子移到与当前节点相邻的节点
不能进行操作者输(当棋子所在节点没有石头的时候不能进行操作),求所有能够使Takahashi赢的节点v

思路:

找规律得:
把棋子从权值小的点,移动到权值大的点毫无意义,分分钟被对手赶回来。。。。。
既然如此,顺着数列的单调性把树走一遍。。。。。。

sg[x]: 棋子的起点在u,在u的子树中游戏,先手是否能胜利 (0:败 1:胜)。
对于节点u的所有儿子v: if(a[v] < a[u] && sg[v] == 0) sg[u] = 1;
证明:
a): 当u存在一个儿子v,满足 a[v] < a[u] && sg[v] == 0 时。
因为,sg[v]=0,所以从v开始,在v的子树中游戏,先手必败。
因为 a[v] < a[u],所以在节点u开始且先手的那位可以把棋子移动到v,
强迫对手在v的子树游戏,若对手把棋子从v移回u,将棋子再从u移回v即可。
b): 若u不存在这样的儿子,因为无法转移到必败态,所以此时,sg[u] = 0
因为不会回头,所以可以直接这么dp!(无后效性是成立的)
对所有点dfs一遍就搞定了

代码:

#include <iostream>
#include <stdio.h>
#include <algorithm>
#include <string.h>
using  namespace  std;

#define ff first
#define ss second
#define pb push_back
#define ll long long
#define mod 1000000007
#define ull unsigned long long
#define mst(ss,b) memset(ss,b,sizeof(ss));
#define pl(x) cout << #x << "= " << x << endl;
const int inf = 0x3f3f3f3f;
const int N = 3e3+10;

vector<int>E[N];
int n, a[N], sg[N];

void dfs(int u, int fa){
    sg[u] = 0;
    for(auto v : E[u]){
        if(v == fa || a[v] >= a[u])continue;
        dfs(v, u);
        if(!sg[v])sg[u] = 1;
    }
}

int  main(){
    scanf("%d", &n);
    for(int i=1; i<=n; i++)scanf("%d", &a[i]);
    for(int i=1; i<n; i++){
        int u, v; scanf("%d%d", &u, &v);
        E[u].pb(v);
        E[v].pb(u);
    }
    for(int i=1; i<=n; i++){
        dfs(i, -1);
        if(sg[i])printf("%d ", i);
    }
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值