XMU区域赛选拔赛题解
B.是谁打了奶奶
Description
最近发生了一起骇人听闻的打奶奶事件,凶手就是——惊奇队长。
惊奇队长是在电车上打的奶奶,那么我们就来看一个和电车有关的问题。
某市修建了若干条单向通行的电车轨道,每条轨道连接了两个生活区,形成一个n个点m条边的有向图。
现在要规划若干条电车线路,满足如下条件:
1.每条电车线路必须是一个环,也就是说,电车能沿着这条线路循环运行。
2.每个生活区必须恰好在一条电车线路上。
3.每条轨道至多在一条线路上。
所以我们感兴趣的是,能否安排出满足条件的方案?
Format
- Input
每个测试点包含至多100组输入数据,请处理至文件结束。
对于每组数据,第一行两个整数n和m(n≤1000,m≤2000n\leq1000,m\leq2000n≤1000,m≤2000),表示有向图的点数和边数。
接下来m行,每行两个整数u,v(1≤u,v≤n)u,v(1\leq{u},v\leq{n})u,v(1≤u,v≤n),表示有一条通行方向从u到v的电车轨道。
至多5组数据满足n>100,m>200n>100,m>200n>100,m>200,图中可能有重边或自环。 - Output
按照输入顺序,对于每组数据输出一行。
如果可以安排出这样的方案,输出“Yes”,否则输出“No”(不含引号)。
Sample 1
- Input
4 5
1 2
2 3
3 1
2 4
4 1
- Output
No
题解
思维题。在一个环覆盖中,每个点仅有其前驱结点与后继结点。所以我们可以把每个点uuu拆成u1,u2u_1,u_2u1,u2,对于每条边,连边变成(u1,v2)(u_1,v_2)(u1,v2)。在一个环上,每个点的u1,u2u_1,u_2u1,u2都会连有且只有一次。我们惊喜的发现:这不就是一个裸的二分图匹配问题了吗!如果二分图的最大匹配为nnn,就是YesYesYes,否则NoNoNo。
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
const int maxn=1005;
int n,m;
int head[maxn],eg_num;
struct edge
{
int nex;
int to;
}eg[2*maxn];
void add(int a,int b)
{
eg[++eg_num].nex=head[a];
head[a]=eg_num;
eg[eg_num].to=b;
}
int comb[maxn],dfn[maxn],tim;
int dfs(int rt)
{
for(int i=head[rt];i;i=eg[i].nex)
{
int to=eg[i].to;
if(dfn[to]==tim) continue;
dfn[to]=tim;
if(!comb[to]||dfs(comb[to]))
{
comb[to]=rt;
return 1;
}
}
return 0;
}
int main()
{
while(~scanf("%d%d",&n,&m))
{
int ans=0;
eg_num=0;tim=0;
memset(head,0,sizeof(head));
memset(dfn,0,sizeof(dfn));
memset(comb,0,sizeof(comb));
for(int i=1,a,b;i<=m;i++)
{
scanf("%d%d",&a,&b);
add(a,b);
}
for(int i=1;i<=n;i++)
{
++tim;
if(dfs(i)) ans++;
}
if(ans==n) printf("Yes\n");
else printf("No\n");
}
return 0;
}
D.塔子哥数数
Description
某日药水哥直播的时候,教塔子哥背九九乘法表。当塔子哥连背了几遍“九六七十二”之后,药水哥有点绝望,便想从更基础的问题教起——数数,现在请你也和塔子哥一起数。
首先在正整数集上定义函数g(n)g(n)g(n):
1.g(1)=1g(1)=1g(1)=1
2.如果n能表示成若干个两两不同质数的乘积,那么g(n)=1g(n)=1g(n)=1,否则g(n)=0g(n)=0g(n)=0
现在有一张纸上面,从左到右写了g(1),g(2),...,g(n)g(1),g(2),...,g(n)g(1),g(2),...,g(n),药水哥问塔子哥这个数是多少,也就是把g(1)g(2)g(3)...g(n)g(1)g(2)g(3)...g(n)g(1)g(2)g(3)...g(n)连起来看成一个10进制数,其中g(1)是最高位,那么这个数是多少。
这个数可能很大,请输出这个数对于1000000007取模的结果。
Format
- Input
每个测试点仅包含一组输入数据,一行一个正整数n(1≤\leq≤n≤\leq≤101110^{11}1011) - Output
输出一行一个正整数代表本题答案。
Sample 1
- Input
4
- Output
1110
题解
数论好题,考察莫比乌斯函数的本质。我们先来回忆一下莫比乌斯函数的定义:
- 当n不等于1时,n所有因子的莫比乌斯函数值的和为0。
即:∑d∣xμ(d)={1x=10x>1\sum_{d|x}\mu(d)= \begin{cases} 1& \text{x=1}\\ 0 & \text{x>1} \end{cases}d∣x∑μ(d)={10x=1x>1 - 从通俗上讲,它有如下几个特征:
1)莫比乌斯函数μ(n)的定义域是正整数
2)μ(1)=1
3)当n存在平方因子时,μ(n)=0
4)当n是素数或奇数个不同素数之积时,μ(n)=-1
5)当n是偶数个不同素数之积时,μ(n)=1
μ(x)={1x=1(−1)kx=p1p2...pk0其余情况\mu(x)= \begin{cases} 1& \text{x=1}\\ (-1)^k & \text{x=$p_1p_2...p_k$}\\ 0 & \text{其余情况} \end{cases}μ(x)=⎩⎪⎨⎪⎧1(−1)k0x=1x=p1p2...pk其余情况
我们惊喜地发现,g(x)g(x)g(x)就是μ(x)\mu(x)μ(x)的绝对值!妈妈我会线性推!再一看数据范围,101110^{11}1011。。。想了半天,决定跳过此题。
还是对数据不够敏感。这种数据范围显然应当是根号做法。正难则反,我们可以先考虑所有位是111,再刨除g(i)=0g(i)=0g(i)=0的位置。
1. 计算所有位是1的n位数对100000000710000000071000000007取模
容易观察到,S(n)=S(n)=S(n)=10n−19\frac{10^n-1}{9}910n−1。
所以简简单单地求个快速幂和9的逆元就好啦!
2. 计算g(i)=0g(i)=0g(i)=0的位置贡献的值
由g(x)g(x)g(x)的定义,我们可以看出所有22,32,...,k2(k2≤n)2^2,3^2,...,k^2(k^2\leq n)22,32,...,k2(k2≤n)的倍数要被刨除。对于k2k^2k2,我们要刨除的总和:
由于g(k2),g(2k2)...g(k^2),g(2k^2)...g(k2),g(2k2)...从高位向地位排列,因此这是一个以10n−k210^{n-k^2}10n−k2为首项、10−k210^{-k^2}10−k2为公比、⌊⌊⌊nk2\frac{n}{k^2}k2n⌋⌋⌋为项数的等比数列。因此,
S(k)=10n−k2(1−10−k2∗⌊nk2⌋)1−10−k2S(k)=\frac{10^{n-k^2}(1-10^{-k^2*⌊\frac{n}{k^2}⌋})}{1-10^{-k^2}}S(k)=1−10−k210n−k2(1−10−k2∗⌊k2n⌋)
3. 用容斥原理排除重复计算
如果减去每一个S(k)S(k)S(k),显然会有重复。比如考虑k=6k=6k=6,就已经在k=2,k=3k=2,k=3k=2,k=3减去过两次了,因此还要再加上S(6)S(6)S(6)。考虑k=30k=30k=30,在k=2,3,5k=2,3,5k=2,3,5共减了三次,又在k=6,10,15k=6,10,15k=6,10,15加了3次,因此要减去S(30)S(30)S(30)。。。想到了什么?莫比乌斯函数!每个kkk的容斥系数就是它的莫比乌斯函数值!可以用数学归纳法简单证明:
- k=2,3k=2,3k=2,3时满足容斥系数−1=μ(2)=μ(3)-1=\mu(2)=\mu(3)−1=μ(2)=μ(3)
- 每个数计算之前已经扣除了除1以外的所有约数各一次。若前x−1x-1x−1个数的容斥系数均为其对应的莫比乌斯函数:我们知道计算xxx之前已经计算了除xxx本身和1之外的所有约数各一次,对应的容斥系数均为其莫比乌斯函数。我们要求每个kkk只扣除一次。由于1不在考虑范围以及μ(1)=1\mu(1)=1μ(1)=1,因此xxx的容斥系数为:
−1−[∑d∣xμ(d)−μ(1)−μ(x)]=μ(x)-1-[\sum_{d|x}\mu(d)-\mu(1)-\mu(x)]=\mu(x)−1−[∑d∣xμ(d)−μ(1)−μ(x)]=μ(x)
4. 得到答案
ans=10n−19+∑k=2k2≤nμ(k)∗10n−k2(1−10−k2∗⌊nk2⌋)1−10−k2ans=\frac{10^n-1}{9}+\sum_{k=2}^{k^2\leq{n}}\mu(k)*\frac{10^{n-k^2}(1-10^{-k^2*⌊\frac{n}{k^2}⌋})}{1-10^{-k^2}}ans=910n−1+∑k=2k2≤nμ(k)∗1−10−k210n−k2(1−10−k2∗⌊k2n⌋)
#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;
const int mod=1000000007;
const int maxn=1e6;
ll n;
int mu[maxn],prime[maxn],num[maxn],p_num;
//求莫比乌斯函数板子
void get_mu()
{
mu[1]=1;
for(int i=2;i<maxn;i++)
{
if(!num[i])
{
prime[++p_num]=i;
mu[i]=-1;
}
for(int j=1;j<=p_num&&i*prime[j]<maxn;j++)
{
num[i*prime[j]]=1;
if(i%prime[j]) mu[i*prime[j]]=-mu[i];
else
{
mu[i*prime[j]]=0;
break;
}
}
}
}
//快速幂板子
int ksm(int a,ll b)
{
int ans=1,base=a;
while(b>0)
{
if(b&1) ans=1LL*ans*base%mod;
base=1LL*base*base%mod;
b>>=1;
}
return ans;
}
//求逆元板子
void ex_gcd(int a,int b,int &x,int &y)
{
if(b==0) {x=1; y=0;}
else
{
ex_gcd(b,a%b,y,x);
y-=a/b*x;
}
}
int ni(int a,int p)
{
int x=0,y=0;
ex_gcd(a,p,x,y);
x=(x%p+p)%p;
return x;
}
int main()
{
scanf("%lld",&n);
ll ans=1LL*(ksm(10,n)-1)*ni(9,mod)%mod;
get_mu();
for(ll i=2;i*i<=n;i++)
{
int tmp1=(ksm(10,1LL*i*i*(n/(i*i)))-ksm(10,i*i*(n/(i*i)-1))+mod)%mod;
int tmp2=(ksm(10,1LL*i*i*(n/(i*i)))-1+mod)%mod;
int tmp3=ksm(10,n-i*i);
int tmp4=1LL*mu[i]*tmp2*tmp3%mod*ni(tmp1,mod)%mod;
if(mu[i]>0) ans=(ans+tmp4)%mod;
else if(mu[i]<0) ans=(ans+tmp4+mod)%mod;
}
printf("%lld\n",ans);
return 0;
}
F.故乡的樱花树
Description
又到了故乡樱花盛开的时节,川桑想把樱花树移栽到双流机场,因为他相信那个女人有一天会回来的,那时候要有童话般的场景。
樱花树可以看成n个点n-1条边的无向连通图,为了长途运输,它的直径不能太大,这里的直径就是图中最长的简单路径的长度(路径上边权之和)。
现在,已经知道樱花树上所有的边长度之和为m,请你为每条边分配一个边权EiE_iEi,使得所有EiE_iEi的和等于m,并且樱花树的直径最小。
你只需要输出最小的树的直径保留6位小数的结果。
Format
- Input
每个测试点仅包含一组输入数据。
第一行两个正整数n和m(2≤\leq≤n≤\leq≤100000100000100000,1≤\leq≤m≤\leq≤10910^9109)
接下来n-1行,每行两个正整数x,y(1≤\leq≤x,y≤\leq≤n),表示x号节点和y号节点之间存在一条边。
保证输入数据为一棵樱花树。 - Output
一行一个实数,保留6位小数,为最小的直径。
Sample 1
- Input
4 3
1 2
1 3
1 4
- Output
2.000000
题解
直径的两端是叶子结点。我们来贪心一下:所有直径的长度都相等。
- 证明:如果不是所有路径的长度都相等,那么必有一条(或多条)最长路径、和一条(或多条)最短路径。我们将最长路径的部分权值分给最短路径,答案必定会减小。
这是一个纳什均衡。
一个显然的构造方案:设k为叶子结点的个数,那么给每个叶子结点赋权值mk\frac{m}{k}km、其余结点赋权值0,显然所有的树上路径权值之和即为2mk\frac{2m}{k}k2m。
并不严密的正确性证明:对于一颗固定的树,其所有树上路径是确定的,也就是说统计所有树上路径时,每个点被计算的次数是确定的。每条树上路径的权值之和等于总权值和树上路径数\frac{总权值和}{树上路径数}树上路径数总权值和,因此我们要让总权值和尽量小,即给经过次数多的点尽量少的权值。易知父亲结点经过的次数要多于儿子节点。因此将所有权值赋给儿子节点最优。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
const int maxn=100005;
int n,m;
int tim[maxn],num;
int main()
{
scanf("%d%d",&n,&m);
for(int i=1,x,y;i<n;i++)
{
scanf("%d%d",&x,&y);
tim[x]++;tim[y]++;
}
for(int i=1;i<=n;i++)
{
if(tim[i]==1) num++;
}
printf("%.6lf\n",2.0*m/num);
return 0;
}
G.凶手找到了
Description
感谢移动互联网,每当发生一件坏事的时候,我们总是能够立刻找到凶手。
事实上,大家也都知道,所有的坏事都是同一个人干的,每100件坏事就有6324件是他干的。
现在又有一件坏事发生了,请你告诉大家凶手的名字。
Format
- Input
每个测试点仅包含一组输入数据,一行一个字符串,长度不超过1000000。
这个字符串可能包含大小写英文字母,空格,英文逗号、英文句号、英文问号和英文感叹号,描述一件坏事。 - Output
一行一个字符串,凶手的名字。
Sample 1
- Input
Who has killed Albus Dumbledore?
- Output
Xiaochuan Sun
题解
签到题不多说。
#include <iostream>
#include <cstdio>
using namespace std;
char s[1000005];
int main()
{
gets(s);
printf("Xiaochuan Sun\n");
return 0;
}
H.抽象思维派系
Description
学习抽象学派,最关键的是要掌握抽象思维,将一切都抽象起来。
生活处处有抽象,比如我们耳熟能详的“扫雷”,就有一个更加抽象的版本:
给出一个无向图,请你支持如下两种操作:
1.在a号节点上放b个地雷。
2.查询a号节点所有相邻节点(不包括a号节点自己)的地雷总数。
两个节点相邻当且仅当它们之间有边,所有节点一开始都没有地雷。
Format
- Input
每个测试点仅包含一组测试数据。
第一行三个整数n,m,Qn,m,Qn,m,Q(n,m,Qn,m,Qn,m,Q≤\leq≤200000200000200000),表示无向图的点数,边数和操作总数。
接下来mmm行,每行两个整数u,u,u,(1≤u,v≤n1\leq u,v\leq n1≤u,v≤n),表示u号节点和v号节点之间有一条无向边。
接下来Q行,每行是如下两种形式之一。
1 a b,表示在a号节点上放b个地雷(111≤\leq≤aaa≤\leq≤n,111≤\leq≤bbb≤\leq≤10910^9109)。
2 a,表示查询a号节点所有相邻节点(不包括a号节点自己)的地雷总数(111≤\leq≤aaa≤\leq≤nnn)。
输入的无向图可能有重边,但一定没有自环。 - Output
按照输入顺序,对于每个查询操作,输出一行一个整数,表示本次查询的答案。
Sample 1
- Input
4 6 4
1 2
1 3
1 4
2 3
2 4
3 4
1 1 4
2 4
1 2 4
2 3
- Output
4
8
题解
非常具有启发性的一道题。
直接暴力显然是过不了的。不断修改、查询度数很大的点,复杂度O(qm)O(qm)O(qm),直接爆炸。我们T了一发之后感觉是道数据结构题,又没想到该怎么维护,就搁置下来了。
我们分析一下:一共m条边连接了2m次点,因此度数超过2m\sqrt{2m}2m的点的数目不超过2m\sqrt{2m}2m。我们便可以将所有的点分为这么两类:度数小于2m\sqrt{2m}2m的点为0类,否则为1类。
当放地雷的时候,如果是0类点就暴力将所有相邻点的答案加上放的地雷数;如果是1类点就在这个点上打标记。放单个地雷的复杂度便是O(m)O(\sqrt{m})O(m)。
我们注意到所有拥有标记的点都是1类点。因此我们可以重新构图:每个点只与原先相连的点中的1类点连接。
当查询点时候,ans等于自身的ans(来自于相邻的0类点暴力赋予)再加上所有相邻1类点的标记总数。由于1类点点数量小于2m\sqrt{2m}2m,因此单个查询的复杂度也是O(m)O(\sqrt{m})O(m)。
于是我们得到了O(qm)O(q\sqrt{m})O(qm)的算法。
算法的核心就是:巧妙利用分类讨论,将 O(1)O(1)O(1) 查询 O(m)O(m)O(m) 放置或者O(1)O(1)O(1)放置O(m)O(m)O(m)查询均摊到 O(m)O(\sqrt{m})O(m) 的查询与放置。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <set>
typedef long long ll;
using namespace std;
const int maxn=200005;
int n,m,q;
int head[maxn],eg_num;
int n_head[maxn],n_eg_num;
set < pair<int,int> > s;//记录每条边
set < pair<int,int> >::iterator it;
struct node
{
ll ans;//每个点通过暴力获得的答案
int deg;//点点度数
int flag;//0或1类点
ll tag;//1类点的标记
}nd[maxn];
struct edge
{
int nex;
int to;
}eg[2*maxn],n_eg[2*maxn];
void add(int a,int b)
{
eg[++eg_num].nex=head[a];
head[a]=eg_num;
eg[eg_num].to=b;
}
//重新构造图的时候用
void n_add(int a,int b)
{
n_eg[++n_eg_num].nex=n_head[a];
n_head[a]=n_eg_num;
n_eg[n_eg_num].to=b;
}
int main()
{
scanf("%d%d%d",&n,&m,&q);
for(int i=1,a,b;i<=m;i++)
{
scanf("%d%d",&a,&b);
//排除重边,顺便记录下所有的边
it=s.find(make_pair(a,b));
if(it==s.end())
{
add(a,b);add(b,a);
nd[a].deg++;nd[b].deg++;
s.insert(make_pair(a,b));
s.insert(make_pair(b,a));
}
}
//按照点的度数做标记
int lim=sqrt(2*m);
for(int i=1;i<=n;i++)
{
if(nd[i].deg>lim) nd[i].flag=1;
else nd[i].flag=0;
}
//重构图:每个点只连接1类点
for(it=s.begin();it!=s.end();it++)
{
int a=it->first,b=it->second;
if(nd[a].flag) n_add(b,a);
}
//开始询问
for(int i=1,tmp,a,b;i<=q;i++)
{
scanf("%d",&tmp);
//添加地雷
if(tmp==1)
{
scanf("%d%d",&a,&b);
//如果这个点的度数小,就把所有相邻点的答案增加
if(nd[a].flag==0)
{
for(int i=head[a];i;i=eg[i].nex)
{
int to=eg[i].to;
nd[to].ans+=b;
}
}
//否则给这个点自身添加标记
else
{
nd[a].tag+=b;
}
}
//开始询问
else
{
scanf("%d",&a);
//每个点的答案等于自身点答案再加上所有相邻的、1类点的标记和
ll ans=nd[a].ans;
for(int i=n_head[a];i;i=n_eg[i].nex)
{
int to=n_eg[i].to;
ans+=nd[to].tag;
}
printf("%lld\n",ans);
}
}
return 0;
}
I.南海观音
Description
参拜南海观音回来之后,小孙明白了很多。
世界是永世的轮回,每个轮回对应一种缘分的组合,迟早,每个人都会尝遍所有的组合。
好在,不用等到所有的轮回结束,我们就可以用我们有限的智慧预测所有的组合,比如下面这道题。
对于一个无向图,若其中某个点的度数为deg,那么它的价值是basedegbase^{deg}basedeg ,一个无向图的价值是它所有点的价值和。
请统计所有n个点的不同的简单无向图(即没有重边和自环的无向图)的价值和,两个无向图不同当且仅当存在两个点,在一个无向图中有边直接相连,而在另一个无向图中没有。
Format
- Input
每个测试点仅包含一组输入数据。
第一行两个正整数n,base(1≤\leq≤n,base≤\leq≤10910^9109),含义如题面所述。 - Output
输出一行一个正整数,为题目所求答案对998244353取模的结果。
Sample 1
- Input
3 1
- Output
24
题解
显而易见,所有点对于无向图来说都是对称的。也就是说所有点对于价值和的贡献是相同的,因此我们只需要考虑单个点。
- n个点的无向图中,任意两个点可以连一条边,因此有n(n−1)2\frac{n(n-1)}{2}2n(n−1)条边,选择连或不连,由乘法原理可知共2n(n−1)22^\frac{n(n-1)}{2}22n(n−1)种无向图。
所以任取一个点,剩下的n−1n-1n−1个点共2(n−1)(n−2)22^\frac{(n-1)(n-2)}{2}22(n−1)(n−2)种无向图。 - 设这个点的度数为degdegdeg,即这个点连出了degdegdeg条边。对于每种无向图,都有Cn−1degC_{n-1}^{deg}Cn−1deg种连法
- 这个点会连出去0至n−1n-1n−1条边。因此对于这n−1n-1n−1个点的每种无向图,选取的点贡献价值:
∑deg=0n−1\sum_{deg=0}^{n-1}∑deg=0n−1 basedegbase^{deg}basedeg ∗*∗ Cn−1degC_{n-1}^{deg}Cn−1deg
细心的同学肯定能就发现了——然而比赛里面我推到这卡住了,队友救了我一命,这不就是二项式定理吗!
然后就愉快的得出了上式的化简形式:(base+1)n−1{(base+1)}^{n-1}(base+1)n−1
所以推得最终的答案:
两个快速幂完事!
#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;
const int mod=998244353;
int n,base;
ll ksm(int a,ll b)
{
int ans=1,base=a;
while(b>0)
{
if(b&1) ans=1LL*ans*base%mod;
base=1LL*base*base%mod;
b>>=1;
}
return ans;
}
int main()
{
scanf("%d%d",&n,&base);
ll tmp=1LL*(n-1)*(n-2)/2;
int ans=1LL*n*ksm(2,tmp)%mod*ksm(base+1,n-1)%mod;
printf("%d\n",ans);
return 0;
}
J.玩蛇
Description
这个题和孙哥实在扯不上什么关系。
Alice和Bob来到了一个充满着多头蛇的世界,可是他们对蛇并不在乎,他们只想玩游戏。
现在有n条多头蛇,每条多头蛇都有一些奇怪的性质:
对于一条aia_iai 头蛇
1.它有着1号,2号,3号…aia_iai号共aia_iai个头
2.砍掉编号为x的头,若x<aia_iaix<a,该蛇将分裂成一条aia_iai−1-1−1头蛇,一条aia_iai−2-2−2头蛇…一条aia_iai−-−xxx头蛇。若x=x=x= aia_iai,则该蛇将会被击杀。
现在有n条多头蛇,第i条多头蛇有aia_iaia个头
Alice和Bob轮流对他们进行砍头,谁不能砍了谁就输了。
因为Alice字典序比较靠前所以Alice先砍,请问在双方都采取最优策略的情况下,最后谁能赢得胜利?
Format
- Input
每个测试点包含多组输入数据。
第一行一个正整数T(T≤\leq≤100100100),表示数据组数。
对于每组数据,第一行一个正整数n(1≤\leq≤n≤\leq≤100010001000)。
接下来一行n个正整数,第i个整数表示aia_iai (1≤\leq≤aia_iai≤\leq≤101810^{18}1018) - Output
按照输入顺序,对于每组数据输出一行,如果Alice获得胜利,请输出"Alice",否则请输出"Bob"(不加引号)。
Sample 1
- Input
1
2
1 1
- Output
Bob
题解
博弈论。题意是x可以变成从x-1开始到任意大于0的数这连续的若干个数,或者x变成0。
很明显是nim游戏的变种,求出sg函数最后异或起来。一看数据范围,1≤\leq≤aia_iai≤\leq≤101810^{18}1018,wtf?? 线性推都不让?不管了,先打一波表为敬。加个前缀和优化,O(n2)O(n^2)O(n2)做法美滋滋
#include <iostream>
#include <cstdio>
#include <cstring>
using namespace std;
int sg[205],mex[205],beg[205];
int main()
{
sg[1]=1;sg[2]=2;
beg[1]=1^2;beg[2]=2;
for(int i=3;i<=200;i++)
{
memset(mex,0,sizeof(mex));
for(int j=1;j<i;j++) mex[beg[j]]++;
for(int j=1;;j++)
{
if(!mex[j])
{
sg[i]=j;
break;
}
}
for(int j=1;j<i;j++) beg[j]^=sg[i];
beg[i]=sg[i];
}
for(int i=1;i<=200;i++) printf("sg[%d]: %d\n",i,sg[i]);
return 0;
}
然后得到了如下的东东,就快乐地打印代码把电脑交给队友。
sg[1]: 1
sg[2]: 2
sg[3]: 1
sg[4]: 4
sg[5]: 1
sg[6]: 2
sg[7]: 1
sg[8]: 8
sg[9]: 1
sg[10]: 2
sg[11]: 1
sg[12]: 4
sg[13]: 1
sg[14]: 2
sg[15]: 1
sg[16]: 16
sg[17]: 1
sg[18]: 2
sg[19]: 1
sg[20]: 4
sg[21]: 1
sg[22]: 2
sg[23]: 1
sg[24]: 8
sg[25]: 1
sg[26]: 2
sg[27]: 1
sg[28]: 4
sg[29]: 1
sg[30]: 2
sg[31]: 1
sg[32]: 32
定睛一看,sg函数值不就是约数里有2的几次方就是几吗?蒟蒻并不精通树状数组,没想到lowbit仍然沉浸在打表找到规律的喜悦中的我,就得到一个快乐的O(T∗n∗loga)O(T*n*loga)O(T∗n∗loga)的做法。其实现在还没太明白为啥会T掉。直到精通树状数组的队友提醒了我,我还保险起见加了个快速度入,才A了这题。
#include <iostream>
#include <cstdio>
typedef long long ll;
using namespace std;
int t,n;
inline ll read()
{
ll f=1,x=0;char ch=getchar();
while(ch>'9'||ch<'0') {if(ch=='-') f=-1;ch=getchar();}
while(ch>='0'&&ch<='9') {x=10*x+(ch-'0');ch=getchar();}
return f*x;
}
int main()
{
scanf("%d",&t);
while(t--)
{
scanf("%d",&n);
ll ans=0,tmp;
for(int i=1;i<=n;i++)
{
tmp=read();
ans^=tmp^(-tmp);
}
if(ans==0) printf("Bob\n");
else printf("Alice\n");
}
return 0;
}