最近发生了一起骇人听闻的打奶奶事件,凶手就是——惊奇队长。
惊奇队长是在电车上打的奶奶,那么我们就来看一个和电车有关的问题。
某市修建了若干条单向通行的电车轨道,每条轨道连接了两个生活区,形成一个n个点m条边的有向图。
现在要规划若干条电车线路,满足如下条件:
1.每条电车线路必须是一个环,也就是说,电车能沿着这条线路循环运行。
2.每个生活区必须恰好在一条电车线路上。
3.每条轨道至多在一条线路上。
所以我们感兴趣的是,能否安排出满足条件的方案?
4 5
1 2
2 3
3 1
2 4
4 1
No
思维题。在一个环覆盖中,每个点仅有其前驱结点与后继结点。所以我们可以把每个点 u u u拆成 u 1 , u 2 u_1,u_2 u1,u2,对于每条边,连边变成 ( u 1 , v 2 ) (u_1,v_2) (u1,v2)。在一个环上,每个点的 u 1 , u 2 u_1,u_2 u1,u2都会连有且只有一次。我们惊喜的发现:这不就是一个裸的二分图匹配问题了吗!如果二分图的最大匹配为 n n n,就是 Y e s Yes Yes,否则 N o No No。
#include
#include
#include
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;
}
某日药水哥直播的时候,教塔子哥背九九乘法表。当塔子哥连背了几遍“九六七十二”之后,药水哥有点绝望,便想从更基础的问题教起——数数,现在请你也和塔子哥一起数。
首先在正整数集上定义函数 g ( n ) g(n) g(n):
1. g ( 1 ) = 1 g(1)=1 g(1)=1
2.如果n能表示成若干个两两不同质数的乘积,那么 g ( n ) = 1 g(n)=1 g(n)=1,否则 g ( n ) = 0 g(n)=0 g(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取模的结果。
4
1110
数论好题,考察莫比乌斯函数的本质。我们先来回忆一下莫比乌斯函数的定义:
μ ( x ) = { 1 x=1 ( − 1 ) k x= p 1 p 2 . . . p k 0 其余情况 \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)的绝对值!妈妈我会线性推!再一看数据范围, 1 0 11 10^{11} 1011。。。想了半天,决定跳过此题。
还是对数据不够敏感。这种数据范围显然应当是根号做法。正难则反,我们可以先考虑所有位是 1 1 1,再刨除 g ( i ) = 0 g(i)=0 g(i)=0的位置。
容易观察到, S ( n ) = S(n)= S(n)= 1 0 n − 1 9 \frac{10^n-1}{9} 910n−1。
所以简简单单地求个快速幂和9的逆元就好啦!
由 g ( x ) g(x) g(x)的定义,我们可以看出所有 2 2 , 3 2 , . . . , k 2 ( k 2 ≤ n ) 2^2,3^2,...,k^2(k^2\leq n) 22,32,...,k2(k2≤n)的倍数要被刨除。对于 k 2 k^2 k2,我们要刨除的总和:
由于 g ( k 2 ) , g ( 2 k 2 ) . . . g(k^2),g(2k^2)... g(k2),g(2k2)...从高位向地位排列,因此这是一个以 1 0 n − k 2 10^{n-k^2} 10n−k2为首项、 1 0 − k 2 10^{-k^2} 10−k2为公比、 ⌊ ⌊ ⌊ n k 2 \frac{n}{k^2} k2n ⌋ ⌋ ⌋为项数的等比数列。因此,
S ( k ) = 1 0 n − k 2 ( 1 − 1 0 − k 2 ∗ ⌊ n k 2 ⌋ ) 1 − 1 0 − k 2 S(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⌋)
如果减去每一个 S ( k ) S(k) S(k),显然会有重复。比如考虑 k = 6 k=6 k=6,就已经在 k = 2 , k = 3 k=2,k=3 k=2,k=3减去过两次了,因此还要再加上 S ( 6 ) S(6) S(6)。考虑 k = 30 k=30 k=30,在 k = 2 , 3 , 5 k=2,3,5 k=2,3,5共减了三次,又在 k = 6 , 10 , 15 k=6,10,15 k=6,10,15加了3次,因此要减去 S ( 30 ) S(30) S(30)。。。想到了什么?莫比乌斯函数!每个 k k k的容斥系数就是它的莫比乌斯函数值!可以用数学归纳法简单证明:
a n s = 1 0 n − 1 9 + ∑ k = 2 k 2 ≤ n μ ( k ) ∗ 1 0 n − k 2 ( 1 − 1 0 − k 2 ∗ ⌊ n k 2 ⌋ ) 1 − 1 0 − k 2 ans=\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
#include
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;
}
又到了故乡樱花盛开的时节,川桑想把樱花树移栽到双流机场,因为他相信那个女人有一天会回来的,那时候要有童话般的场景。
樱花树可以看成n个点n-1条边的无向连通图,为了长途运输,它的直径不能太大,这里的直径就是图中最长的简单路径的长度(路径上边权之和)。
现在,已经知道樱花树上所有的边长度之和为m,请你为每条边分配一个边权 E i E_i Ei,使得所有 E i E_i Ei的和等于m,并且樱花树的直径最小。
你只需要输出最小的树的直径保留6位小数的结果。
4 3
1 2
1 3
1 4
2.000000
直径的两端是叶子结点。我们来贪心一下:所有直径的长度都相等。
一个显然的构造方案:设k为叶子结点的个数,那么给每个叶子结点赋权值 m k \frac{m}{k} km、其余结点赋权值0,显然所有的树上路径权值之和即为 2 m k \frac{2m}{k} k2m。
#include
#include
#include
#include
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;
}
感谢移动互联网,每当发生一件坏事的时候,我们总是能够立刻找到凶手。
事实上,大家也都知道,所有的坏事都是同一个人干的,每100件坏事就有6324件是他干的。
现在又有一件坏事发生了,请你告诉大家凶手的名字。
Who has killed Albus Dumbledore?
Xiaochuan Sun
签到题不多说。
#include
#include
using namespace std;
char s[1000005];
int main()
{
gets(s);
printf("Xiaochuan Sun\n");
return 0;
}
学习抽象学派,最关键的是要掌握抽象思维,将一切都抽象起来。
生活处处有抽象,比如我们耳熟能详的“扫雷”,就有一个更加抽象的版本:
给出一个无向图,请你支持如下两种操作:
1.在a号节点上放b个地雷。
2.查询a号节点所有相邻节点(不包括a号节点自己)的地雷总数。
两个节点相邻当且仅当它们之间有边,所有节点一开始都没有地雷。
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
4
8
非常具有启发性的一道题。
直接暴力显然是过不了的。不断修改、查询度数很大的点,复杂度 O ( q m ) O(qm) O(qm),直接爆炸。我们T了一发之后感觉是道数据结构题,又没想到该怎么维护,就搁置下来了。
我们分析一下:一共m条边连接了2m次点,因此度数超过 2 m \sqrt{2m} 2m的点的数目不超过 2 m \sqrt{2m} 2m。我们便可以将所有的点分为这么两类:度数小于 2 m \sqrt{2m} 2m的点为0类,否则为1类。
当放地雷的时候,如果是0类点就暴力将所有相邻点的答案加上放的地雷数;如果是1类点就在这个点上打标记。放单个地雷的复杂度便是 O ( m ) O(\sqrt{m}) O(m)。
我们注意到所有拥有标记的点都是1类点。因此我们可以重新构图:每个点只与原先相连的点中的1类点连接。
当查询点时候,ans等于自身的ans(来自于相邻的0类点暴力赋予)再加上所有相邻1类点的标记总数。由于1类点点数量小于 2 m \sqrt{2m} 2m,因此单个查询的复杂度也是 O ( m ) O(\sqrt{m}) O(m)。
于是我们得到了 O ( q m ) 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
#include
#include
#include
#include
#include
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;
}
参拜南海观音回来之后,小孙明白了很多。
世界是永世的轮回,每个轮回对应一种缘分的组合,迟早,每个人都会尝遍所有的组合。
好在,不用等到所有的轮回结束,我们就可以用我们有限的智慧预测所有的组合,比如下面这道题。
对于一个无向图,若其中某个点的度数为deg,那么它的价值是 b a s e d e g base^{deg} basedeg ,一个无向图的价值是它所有点的价值和。
请统计所有n个点的不同的简单无向图(即没有重边和自环的无向图)的价值和,两个无向图不同当且仅当存在两个点,在一个无向图中有边直接相连,而在另一个无向图中没有。
3 1
24
显而易见,所有点对于无向图来说都是对称的。也就是说所有点对于价值和的贡献是相同的,因此我们只需要考虑单个点。
所以推得最终的答案:
#include
#include
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;
}
这个题和孙哥实在扯不上什么关系。
Alice和Bob来到了一个充满着多头蛇的世界,可是他们对蛇并不在乎,他们只想玩游戏。
现在有n条多头蛇,每条多头蛇都有一些奇怪的性质:
对于一条 a i a_i ai 头蛇
1.它有着1号,2号,3号… a i a_i ai号共 a i a_i ai个头
2.砍掉编号为x的头,若x< a i a_i aix a i a_i ai − 1 -1 −1头蛇,一条 a i a_i ai − 2 -2 −2头蛇…一条 a i a_i ai − - − x x x头蛇。若 x = x= x= a i a_i ai,则该蛇将会被击杀。
现在有n条多头蛇,第i条多头蛇有 a i a_i aia个头
Alice和Bob轮流对他们进行砍头,谁不能砍了谁就输了。
因为Alice字典序比较靠前所以Alice先砍,请问在双方都采取最优策略的情况下,最后谁能赢得胜利?
1
2
1 1
Bob
博弈论。题意是x可以变成从x-1开始到任意大于0的数这连续的若干个数,或者x变成0。
很明显是nim游戏的变种,求出sg函数最后异或起来。一看数据范围,1 ≤ \leq ≤ a i a_i ai ≤ \leq ≤ 1 0 18 10^{18} 1018,wtf?? 线性推都不让?不管了,先打一波表为敬。加个前缀和优化, O ( n 2 ) O(n^2) O(n2)做法美滋滋
#include
#include
#include
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 ∗ l o g a ) O(T*n*loga) O(T∗n∗loga)的做法。其实现在还没太明白为啥会T掉。直到精通树状数组的队友提醒了我,我还保险起见加了个快速度入,才A了这题。
#include
#include
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;
}