Prelude

题目链接:萌萌哒传送门♪(*)


Subtask 1 & 2

这是什么鬼题面。。。

首先要看出,这就是一个基环树博弈。

具体题意:给出一个基环内向树,一个棋子初始在\(1\)号节点,双方轮流操作,设棋子所在节点为\(u\),每次可以从所有指向\(u\)的节点中选择一个,把棋子移动过去,不能操作者输,问先手是否有必胜策略,或者是否平局。

但是,这个图是可以变的,每次询问,假如我选择环上的两个点\(u\)和\(v\),把\(u\)的出边指向\(v\),那么游戏的结果如何?

考虑如果只有一次询问,不修改的话怎么做。

这么裸的博弈随便做吧?先假设把环上的所有边断开,然后dp求出每个节点的状态,最后再考虑环上的情况,当环上所有点都是必败态的时候就是平局,否则两个人沿着环走,第一个走到必胜节点的人获胜。

求解一次是\(O(n)\)的,一共\(q\)次询问,总时间复杂度\(O(nq)\)。


Subtask 3 & 4

首先我们可以预处理求出离环上每个点最近的必胜点是哪个,我这里用\(left_{i}\)表示。

每次对原图做修改,相当于把环缩小到原来的环的一个片段。

假如我们选了环上的\(u\)和\(v\)两个节点,并且把\(u\)的出边连接到\(v\),那么\(f_{u}\)到\(v\)这一段的dp值都可能会改变。

我们可以用倍增预处理,用\(jump[x][s][k]\)表示假如点\(x\)的状态为\(s\),那么\(x\)之后第\(2^{k}\)个点的状态是什么,这样,查询一个点的状态就是\(O(\log n)\)的了。

对于点\(1\)不在环上的情况,我们可以直接查询,\(O(\log n)\)解决。

如果点\(1\)在环上,我们因为有之前预处理的结果,所以只需要求出点\(v\)的新的dp值(\(O(\log n)\)),然后大力分类讨论(\(O(1)\))解决。

总体复杂度就是\(O(n \log n)\),能过87分辣~


Subtask 5

LCA告诉我可以线性做,然而我并不会。

瓶颈是倍增,怎么去掉倍增?怎么降低查询的复杂度?

我们离线所有的询问,这样就可以用并查集,在\(O(n \alpha(n))\)的时间内处理出所有的“对点\(v\)的dp值的询问”。

这样总体复杂度就变成了\(O(n \alpha(n))\)辣~

因为我比较懒,所以离线询问的时候对询问排了序,复杂度还是\(O(n \log n)\)的,但是这个\(O(n \log n)\)比倍增的\(O(n \log n)\)要小得多了,也是能AC的。


Conclusion

为了处理方便,我们通常把环拆开,拆成一条链来处理。

于是就少不了大量的分类讨论,我写了200行。

这件事告诉我们,LCA题不可做(╯‵□′)╯︵┻━┻。


Code

#include <cstring>
#include <algorithm>
#include <cstdio>
#include <queue>
#include <cctype> using namespace std;
const int MAXN = 1000010;
const int LOGN = 21;
int _w; int read() {
int x = 0, ch;
while( isspace(ch = getchar()) );
do x = x * 10 + ch - '0';
while( isdigit(ch = getchar()) );
return x;
} int n, q, f[MAXN]; int lp[MAXN], lpsz, pos[MAXN]; // lp为环上的每个节点,lpsz为环的大小,pos为原图中的节点在环中的位置
void find_loop() {
int u = 1;
while( !pos[u] ) {
pos[u] = ++lpsz;
lp[lpsz] = u;
u = f[u];
}
int sz = 0;
for( int i = pos[u]; i <= lpsz; ++i )
lp[++sz] = lp[i];
lpsz = sz;
for( int i = 1; i <= n; ++i ) pos[i] = 0; // 不在环中的节点pos == 0
for( int i = 1; i <= lpsz; ++i )
pos[lp[i]] = i;
} int pa[MAXN<<1]; // 并查集相关
int find( int u ) {
return pa[u] == u ? u : pa[u] = find(pa[u]);
} int dp[MAXN], deg[MAXN], left[MAXN];
queue<int> bfsq;
int toend[MAXN][2], to1[MAXN][2]; // to1[x][s]表示当x点的状态为s的时候,点1的状态是什么,toend类似,表示序列上最后一个点的信息
void prelude() {
find_loop();
for( int i = 1; i <= n; ++i )
++deg[f[i]];
for( int i = 1; i <= n; ++i )
if( !deg[i] )
bfsq.push(i);
while( !bfsq.empty() ) {
int u = bfsq.front(); bfsq.pop();
if( !dp[u] ) dp[f[u]] = 1;
if( --deg[f[u]] == 0 )
bfsq.push(f[u]);
}
for( int i = 1; i <= lpsz; ++i )
if( dp[lp[i]] ) left[i] = i;
else left[i] = left[i-1];
for( int i = 1; i < lpsz; ++i ) {
pa[i] = i+1+lpsz;
pa[i+lpsz] = i+1 + dp[lp[i+1]] * lpsz;
}
pa[lpsz] = lpsz;
pa[lpsz+lpsz] = lpsz+lpsz;
for( int i = 1; i <= lpsz; ++i ) {
toend[i][0] = find(i) > lpsz;
toend[i][1] = find(i+lpsz) > lpsz;
}
if( pos[1] ) {
for( int i = 1; i != pos[1]; ++i ) {
pa[i] = i+1+lpsz;
pa[i+lpsz] = i+1 + dp[lp[i+1]] * lpsz;
}
pa[pos[1]] = pos[1];
pa[pos[1]+lpsz] = pos[1]+lpsz;
for( int i = 1; i != pos[1]; ++i ) {
to1[i][0] = find(i) > lpsz;
to1[i][1] = find(i+lpsz) > lpsz;
}
to1[pos[1]][0] = 0;
to1[pos[1]][1] = 1;
}
for( int i = 1; i <= lpsz; ++i ) {
pa[i] = i;
pa[i+lpsz] = i+lpsz;
}
} int calc( int t, int v, int p ) {
static int right = 1;
if( t > p ) { // 把环拆成序列就要大力分类讨论。。。
v = toend[t][v];
t = 1;
v = !v || dp[lp[1]];
}
if( p == pos[1] )
return to1[t][v];
while( right < p ) { // 对所有询问按照查询位置排序
pa[right] = right+1+lpsz;
pa[right+lpsz] = right+1 + dp[lp[right+1]] * lpsz;
++right;
}
return find(t + v*lpsz) > lpsz;
} int solve( int l, int r ) {
if( !pos[1] ) return dp[1]; // 点1不在环上
l = pos[l], r = pos[r];
int p = pos[1];
if( l >= r ) { // 下面都是分类讨论。。。
swap(l, r);
if( p < l || p > r ) { // 点1不在环上
int t = r == lpsz ? 1 : r+1;
return calc(t, dp[lp[t]], p);
} else {
int dpl = dp[lp[l]];
if( l > 1 || r < lpsz ) {
int t = r == lpsz ? 1 : r+1;
dpl = calc(t, dp[lp[t]], l);
}
if( left[p] >= l+1 ) {
int q = left[p];
return (p-q+1)&1;
} else if( dpl ) {
return (p-l+1)&1;
} else if( left[r] >= l+1 ) {
int q = left[r];
return (p-l+1+r-q+1)&1;
} else {
return 2;
}
}
} else {
if( p > l && p < r ) {
int t = l+1;
return calc(t, dp[lp[t]], p);
} else if( p <= l ) {
int t = l+1;
int dpr = calc(t, dp[lp[t]], r);
if( left[p] ) {
int q = left[p];
return (p-q+1)&1;
} else if( left[lpsz] >= r+1 ) {
int q = left[lpsz];
return (lpsz-q+1+p)&1;
} else if( dpr ) {
return (p+lpsz-r+1)&1;
} else if( left[l] ) {
int q = left[l];
return (lpsz-r+1+p+l-q+1)&1;
} else {
return 2;
}
} else {
int t = l+1;
int dpr = calc(t, dp[lp[t]], r);
if( left[p] >= r+1 ) {
int q = left[p];
return (p-q+1)&1;
} else if( dpr ) {
return (p-r+1)&1;
} else if( left[l] ) {
int q = left[l];
return (p-r+1+l-q+1)&1;
} else if( left[lpsz] >= r+1 ) {
int q = left[lpsz];
return (p-r+1+l+lpsz-q+1)&1;
} else {
return 2;
}
}
}
} int qu[MAXN], qv[MAXN]; // 存放询问的地方
int rk[MAXN], ans[MAXN]; bool cmp_qv( int i, int j ) { // 对询问按照点在序列上的位置排序
i = pos[qv[i]], j = pos[qv[j]];
return i < j;
} int main() {
n = read(), q = read();
for( int i = 1; i <= n; ++i )
f[i] = read();
prelude();
for( int i = 0; i < q; ++i )
qu[i] = read(), qv[i] = read(), rk[i] = i;
sort(rk, rk+q, cmp_qv);
for( int i = 0; i < q; ++i )
ans[rk[i]] = solve(qu[rk[i]], qv[rk[i]]);
for( int i = 0; i < q; ++i )
printf( "%d\n", ans[i] );
return 0;
}

【题解】【LibreOJ Beta Round #5】游戏 LOJ 531 基环树 博弈论的更多相关文章

  1. 题解-CSA Beta Round#1 Number Elimination

    Problem CSA-Beta Round#3 题意概要:给定 \(n\) 个数组成的序列,定义一次操作: 在当前序列中选择两个数,将其中较小的数从序列中删除(若两个数相同,则删除在序列中更靠前的) ...

  2. $loj530\ [LibreOJ\ \beta\ Round \#5]$ 最小倍数 数论

    正解:数论 解题报告: 传送门$QwQ$! 不想做题,来水点儿简单点的$QwQ$. 一个显然的点在于可以直接对不同质因子分别算$n_{min}$最后取$max$. 这个正确性还是蛮显然的?因为只要有$ ...

  3. $loj526\ [LibreOJ\ \beta\ Round\ \#4]$ 子集 图论

    正解:图论 解题报告: 传送门$QwQ$ 发现最大团不好求,于是考虑求最大独立集.也就把所有$gcd(i,j)\cdot gcd(i+1,j+1)=1$的点之间连边,然后求最大独立集. 发现依然不可做 ...

  4. [LOJ#531]「LibreOJ β Round #5」游戏

    [LOJ#531]「LibreOJ β Round #5」游戏 试题描述 LCR 三分钟就解决了问题,她自信地输入了结果-- > -- 正在检查程序 -- > -- 检查通过,正在评估智商 ...

  5. [loj#539][LibreOJ NOIP Round #1]旅游路线_倍增_dp

    「LibreOJ NOIP Round #1」旅游路线 题目链接:https://loj.ac/problem/539 题解: 这个题就很神奇 首先大力$dp$很好想,因为可以把一维放到状态里以取消后 ...

  6. Codeforces Beta Round #62 题解【ABCD】

    Codeforces Beta Round #62 A Irrational problem 题意 f(x) = x mod p1 mod p2 mod p3 mod p4 问你[a,b]中有多少个数 ...

  7. Codeforces Beta Round #83 (Div. 1 Only)题解【ABCD】

    Codeforces Beta Round #83 (Div. 1 Only) A. Dorm Water Supply 题意 给你一个n点m边的图,保证每个点的入度和出度最多为1 如果这个点入度为0 ...

  8. LibreOJ NOI Round #2 Day 1

    LibreOJ NOI Round #2 Day 1 T1: 别被定义弄晕了 反着做,A->1/A+B 取倒数没法做,所以变成a/b,维护2*2的矩阵 区间?不用线段树,不用倍增 存在逆矩阵,直 ...

  9. Codeforces Beta Round #80 (Div. 2 Only)【ABCD】

    Codeforces Beta Round #80 (Div. 2 Only) A Blackjack1 题意 一共52张扑克,A代表1或者11,2-10表示自己的数字,其他都表示10 现在你已经有一 ...

随机推荐

  1. Python基础灬高阶函数(lambda,filter,map,reduce,zip)

    高阶函数 lambda函数 关键字lambda表示匿名函数,当我们在传入函数时,有些时候,不需要显式地定义函数,直接传入匿名函数更方便. lambda函数省略函数名,冒号前为参数,冒号后函数体. # ...

  2. C语言零碎知识点

    1.  int整形在64位和32位计算机中都占4个字节. 指针在64位占8个字节,32位占4个字节. 2.  数组下标从0开始,a[0]开始,链表下标从1开始,a[1]开始. 3. 条件运算符(con ...

  3. Python爬虫入门(1-2):综述、爬虫基础了解

    大家好哈,最近博主在学习Python,学习期间也遇到一些问题,获得了一些经验,在此将自己的学习系统地整理下来,如果大家有兴趣学习爬虫的话,可以将这些文章作为参考,也欢迎大家一共分享学习经验. Pyth ...

  4. Cross origin requests are only supported for protocol schemes: http, data, chrome,chrome-extension的问题

    Cross origin requests are only supported for protocol schemes: http, data, chrome,chrome-extension的问 ...

  5. loadrunner socket协议问题归纳(4)---buffer接收变长和定长的数据

    测试场景:聊天系统 用户登录后,要先向服务器发送用户名,然后可以发送聊天信息,同时也可以接受聊天信息. 如果接受的字符为定长时,可以设定接受长度.recv buf2 66 #include " ...

  6. Linux学习——操作文件与目录

    1. ls:列出文件及目录信息. 命令格式:ls [选项] ... 常用选项: -a 显示指定目录下所有子目录与文件,包括隐藏文件. -A 显示指定目录下所有子目录与文件,包括隐藏文件.但不列出“.” ...

  7. 2017年软件工程第八次作业-互评Alpha版本

    B.Thunder——爱阅app(测评人:方铭) 一.基于NABCD评论作品,及改进建议 每个小组评论其他小组Alpha发布的作品:1.根据(不限于)NABCD评论作品的选题:2.评论作品对选题的实现 ...

  8. Python:Python的运行过程

    1.Python是什么 和Java以及c#一样,Python也是一门基于虚拟机的语言.熟悉Java开发的人在命令行执行一个Java程序的过程通常如下: javac hello.java java he ...

  9. CentOS7安装Consul集群

    1.准备4台服务器 linux1 192.168.56.101 linux2 192.168.56.102 linux3 192.168.56.103 linux4 192.168.56.104 2. ...

  10. 0302IT行业虽吃香,能完全享受这块“香"的也很难

    面对现今严峻的就业形势,越来越多的人希望通过职业技能培训或者学历提升来提高自己的综合技能以便能够顺利地应聘到自己理想中的工作. 在2014年十大最热门行业和职业排行榜中IT行业最吃香.在十大行业里,I ...