2019.10.10
说在前面
orz ymx大佬 出的题目太巧秒了
orz ymx大佬
orz
虽然您已经大学了,但我还是orz您。
其实我多年前就已经看过不一定做过这几个题了,这几个题对我当初(现在也)弱小的心灵造成了极大的冲击,原来一套比赛题是这个亚子的!
病毒分裂
题目描述
A 学校的实验室新研制出了一种十分厉害的病毒。由于这种病毒太难以人工制造 了,所以专家们在一开始只做出了一个这样的病毒。 这个病毒被植入了特殊的微型芯片,使其可以具有一些可编程的特殊性能。最重 要的一个性能就是,专家们可以自行设定病毒的分裂能力K,假如现在有x 个病 毒,下一个分裂周期将会有Kx 个一模一样的病毒。你作为该实验室的数据分析 员,需要统计出在分裂到第N 个周期前,一共有多少个病毒单体进行了分裂。一 开始时总是只有一个病毒,这个局面算作第一个周期。由于答案可能很大,专家 们只需要你告诉他们对给定的P 取模后的答案。
输入格式
一行三个整数,依次是K, N, P。
输出格式
一行一个整数,你的答案(对P 取模)
输入样例
5 3 7
输出样例
6
提示
样例一解释:第一个周期有1 个病毒,产生了一次分裂。第二个周期有1*5=5 个病毒,这五个病毒都会分裂。所以第三个周期前一共进行了1+5 等于6 次分裂。 答案即为6 mod 7 = 6
数据范围: 1 < n < 1 0 18 , 1 < k , p < 2 31 1<n<10^{18}, 1<k,p<2^{31} 1<n<1018,1<k,p<231
分析
喜闻乐见的等比数列求和,答案显然等于 1 + k + k 2 + ⋯ + k n − 1 = k n − 1 − 1 k − 1 \large 1+k+k^{2}+\cdots+k^{n-1}=\frac{k^{n-1}-1}{k-1} 1+k+k2+⋯+kn−1=k−1kn−1−1。然后再模一下合数。
又是我喜闻乐见的模合数。
但是不用怕,加减乘模合数都没有影响,只有除法算逆元的时候不能够费马小定理不能够筛逆元罢了,我们还有exgcd!
于是就有了我如下的90分算法。
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
inline void Read(ll &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
ll gcd(ll a,ll b){return b?gcd(b,a%b):a;}
ll exgcd(ll a,ll b,ll &x,ll &y)
{
if(b==0){x=1,y=0;return a;}
ll w=exgcd(b,a%b,y,x);
y-=x*(a/b);
return w;
}
/*
ll mul(ll a,ll b,ll c)
{
ll ans=0;
while(b)
{
if(b&1) ans=(ans+a)%c;
b>>=1; a=(a<<1)%c;
}
return ans;
}
ll msm(ll a,ll b,ll c)
{
if(b==0) return 1;
if(b==1) return a%c;
ll ans=msm(a,b/2,c)%c;
ans=mul(ans,ans,c)%c;
if(b%2) ans=mul(ans,a,c)%c;
return ans%c;
}
*/
ll mul(ll a,ll b,ll c)
{
return ((a*b-(ll)((long double)a/c*b+1e-8)*c)%c+c)%c;
}
ll ksm(ll a,ll b,ll c)
{
ll ans=1; a%=c;
while(b)
{
if(b&1) ans=mul(ans,a,c);//(ans*=a)%=c;
a=mul(a,a,c),//(a*=a)%=c,
b>>=1;
}
return ans;
}
ll k,n,p,x,y;
int main()
{
Read(k),Read(n),Read(p);
if(n==1) return puts("1"),0;
if(k==1) return cout<<(n-1)%p<<endl,0;
exgcd((k-1)/gcd(k-1,p),p/gcd(k-1,p),x,y),((x%=p)+=p)%=p;
cout<<((ksm(k,n-1,p)-1)*x%p+p)%p<<endl;
}
可以看见我做了种种尝试,但long long的力量是有极限的(unsigned也是),所以all ended up in vain。除非你像Alster大佬一样写高精度,而我因为已经瞎折腾了2hours,所以赶快去做后面的题了。
正解是矩阵或者分治,矩阵太强了,萌新在这里贴一个firecrazy神仙的大佬代码就过了(已获得转载许可)
#include<cstdio>
#include<cstring>
int mod;long long n;
struct matrix{int s[5][5];}a1,k;
matrix operator *(matrix a,matrix b)
{
matrix c;memset(c.s,0,sizeof c.s);
for(int i=1;i<=2;i++)for(int j=1;j<=2;j++)
for(int k=1;k<=2;k++)c.s[i][k]=(c.s[i][k]+1ll*a.s[i][j]*b.s[j][k])%mod;
return c;
}
int main()
{
//freopen("split.in","r",stdin);
//freopen("split.out","w",stdout);
a1.s[1][1]=a1.s[1][2]=1;k.s[1][1]=k.s[1][2]=1;
scanf("%d%lld%d",&k.s[2][2],&n,&mod);n-=2;
while(n){if(n&1)a1=a1*k;k=k*k;n>>=1;}
printf("%d\n",a1.s[1][2]%mod);
}
分治也不是很难想,就是说:定义
s
o
l
v
e
(
n
)
=
∑
i
=
0
i
=
n
−
1
k
i
solve(n)=\sum_{i=0}^{i=n-1}k^i
solve(n)=∑i=0i=n−1ki则
s
o
l
v
e
(
n
)
=
s
o
l
v
e
(
n
−
1
2
−
1
)
+
k
n
−
1
2
s
o
l
v
e
(
n
−
1
2
−
1
)
=
(
k
n
−
1
2
+
1
)
s
o
l
v
e
(
n
−
1
2
−
1
)
solve(n)=solve(\frac{n-1}{2}-1)+k^{\frac{n-1}{2}}solve(\frac{n-1}{2}-1)=(k^{\frac{n-1}{2}}+1)solve(\frac{n-1}{2}-1)
solve(n)=solve(2n−1−1)+k2n−1solve(2n−1−1)=(k2n−1+1)solve(2n−1−1)。
然后就递归解决了。
也可以继续推式子化简。
马略了。
马不略了,
b
e
c
a
u
s
e
我
没
有
马
嘶
\xcancel{because我没有马嘶}
because我没有马嘶
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
typedef long long ll;
inline void Read(ll &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
ll n,k,p;
ll ksm(ll a,ll b,ll c)
{
ll ans=1; a%=c;
while(b)
{
if(b&1) (ans*=a)%=c;
(a*=a)%=c,b>>=1;
}
return ans;
}
ll solve(ll pos)
{
if(!pos) return 0;
if(pos&1) return (ksm(k,pos-1,p)+solve(pos-1))%p;
return (ksm(k,pos>>1,p)+1)*solve(pos>>1)%p;
}
int main()
{
Read(k),Read(n),Read(p),k%=p;
cout<<solve(n-1)<<endl;
}
反正都是0ms。
疫情延迟
题目描述
由于A 学校生物实验室里那个不负责的数据分析员,实验室的病毒威力被错误估 算,导致了可怕的病毒泄漏,现在病毒即将在校园内传播开来。 校园里一共有n 个建筑物,生物实验室总是位于一号建筑物且在0 时刻受到病毒 入侵。这n 个建筑物由m 条单向道路相连(也有可能有建筑物被孤立)。每条道 路有两个信息:它的长度,它是多少年前修建的。当一个建筑物被病毒入侵,从 被入侵的时刻起,病毒会从所有由这个建筑物通向其他建筑物的道路按着这条道 路的方向以1 个单位每秒的速度行进。 校长得知这个事情后,决定放弃这个学校逃跑。校长总是位于编号为n 的行政楼, 从零时刻开始需要共T 秒来逃出行政楼,且逃出行政楼即视为逃出了这个学校。 也就是说,如果病毒入侵行政楼的时间不小于T,则校长能够成功逃离。 有些时候,校长没有足够的时间逃离,因为病毒到达行政楼的时间太快了。 为了让校长能够逃离学校,不得不拆除校园内的一些道路以延缓行政楼的被入侵 时间(拆除道路视为在0 时刻被拆的道路全部消失)。当然,如果使得病毒根本 无法到达行政楼,也是可以的。 但是,拆除道路会影响学校的历史气息,且破坏程度定义为拆除的道路中最古老 的一条的年龄。请求出保证校长能够安全撤离的情况下,最小的破坏程度。
输入格式
第一行包含三个整数:n, m, T。 接下来m 行,每行描述一条有向道路。每行4 个整数,si, ti, Li, Yi,分别表 示这条道路的起点,终点,长度,这条道路的年龄。
输出格式
如果不需要拆除任何道路,输出一行两个数:-1 和行政楼的被感染时刻(当然 这个时刻大于等于T),以空格隔开。 否则输出一行包含一个数:最小的破坏程度。
输入样例
5 5 15
1 2 6 35
2 4 8 40
1 3 6 45
3 4 3 25
4 5 5 50
输出样例
25
提示
对于20%的数据,n, m<=10
对于60%的数据,n, m<=100
对于100%的数据,n<=20000, m<=100000.
数据保证在不拆除任何道路的情况下,从1 号楼到n 号楼一定存在路径。
分析
很简单,二分答案,二分我们拆掉的最老的路是多老的,然后跑最短路,跑最短路的时候,只能跑那些比我们二分的答案还老的路,然后看要到达终点所花的时间是否比T大。
代码
#include<queue>
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
const int MAXN=102030,MAXM=202030,inf=947483647;
bool vis[MAXN];
int n,m,t,u,v,w,z[MAXN];
priority_queue< pair<int,int> > Q;
int cnt,head[MAXN],nxt[MAXM],to[MAXM],val[MAXM],dis[MAXN],tim[MAXN];
inline void addedge(int u,int v,int w,int z)
{
nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,val[cnt]=w,tim[cnt]=z;
}
inline void dijkstar(int s,int lim)
{
while(!Q.empty()) Q.pop();
for(int i=1;i<=n;i++) dis[i]=inf,vis[i]=0;
dis[s]=0,vis[s]=1,Q.push(make_pair(0,1));
while(!Q.empty())
{
u=Q.top().second,Q.pop(),vis[u]=0;
for(int i=head[u];i;i=nxt[i])
if(tim[i]>lim && dis[to[i]]>dis[u]+val[i])
{
dis[to[i]]=dis[u]+val[i];
if(!vis[to[i]]) Q.push(make_pair(-dis[to[i]],to[i])),vis[to[i]]=1;
}
}
}
int main()
{
Read(n),Read(m),Read(t);
for(int i=1;i<=m;i++) Read(u),Read(v),Read(w),Read(z[i]),addedge(u,v,w,z[i]);
dijkstar(1,0);
if(dis[n]>=t) return printf("-1 %d\n",dis[n]),0;
sort(z+1,z+1+m);
int lef=1,rig=m,mid;
while(lef<rig)
{
dijkstar(1,z[mid=(lef+rig)>>1]);
if(dis[n]>=t) rig=mid;
else lef=mid+1;
}
printf("%d\n",z[lef]);
}
避难向导
题目描述
“特大新闻,特大新闻!全国爆发了一种极其可怕的病毒,已经开始在各个城市 中传播开来!全国陷入了巨大的危机!大量居民陷入恐慌,想要逃到其它城市以 避难!经调查显示,该病毒来自于C 市的A 学校的一次非法的……” “哎。”你关上电视,叹了口气。作为A 学校的校长,你一天前为了保命,独自 逃离了A 学校,抛弃了全校师生,包括那个曾经帮你计算并拆除道路的工程师。 你良心受到了巨大的谴责,因此决定做出一些补救,回答一些逃难的人提出的询 问。 已知该国一共有n 个城市,并且1 号城市是首都。(n-1)条双向的公路连接这些 城市,通过这些公路,任意两个城市之间存在且仅存在一条路径。每条公路有一 个长度。如果一个城市只与一条公路相连,则称它为边境城市。 该国政府有一个奇怪的规定:每个城市有一个封闭系数di,定义di 为离这个城 市最远的边境城市到这个城市的距离。市民们认为,一个城市的安全系数Si 和 它的封闭系数有很重要的联系。a,b,c 是该国的幸运数字,所以大家公认一个 城市的安全系数Si = (di + a) * b mod c。 市民们一共会提出m 次询问。每个询问包含三个信息,xi,yi 和qi。xi 是询问 者所在的城市编号。你要为这个询问者在xi 到yi 的必经之路上找出一个离xi 最近的避难城市,并且要求这个避难城市的安全系数大于等于qi。如果存在这 样的城市(包含xi 和yi),则输出城市编号,否则输出一行包括一个数-1。
输入格式
第一行五个数:依次是n, m, a, b, c。 接下来n-1 行描述公路的信息。每行三个数,前两个数代表这条公路连接的两个 城市的编号,第三个数表示这条公路的长度。 再接下来m 行,每行描述一个询问,包含三个数xi, yi 和qi。
输出格式
对于每个询问,输出一行包含一个整数,存在符合要求的城市则输出城市编号, 不存在则输出-1。
输入样例
7 6 5 6 20
1 2 4
2 4 2
2 5 3
1 3 5
3 6 6
6 7 7
7 5 15
3 4 5
5 4 2
4 5 2
6 6 10
3 5 19
输出样例
6
3
2
4
6
-1
提示
分析
没想到吧,有朝一日我Crloss能A掉T3而错掉T1、T2。
想想我当时考试结束的最后60s内看出自己的错然后改掉,没测样例,直接删掉freopen前面的注释然后建子文件夹压压缩包然后A掉,多么刺激,老刺激了。
先考虑安全值怎么求,要求
S
S
S我们就要求
d
d
d,
d
d
d是离我最远的点。
这也是经典用法了,这个点必定是树的直径的两个端点之一,证明就反证一下就好了,如果他——某个不是你们直径端点离我比到你们到我的距离远,即
d
i
s
h
e
>
d
i
s
y
o
u
1
,
d
i
s
h
e
>
d
i
s
y
o
u
2
dis_{he}\gt dis_{you_1},dis_{he}\gt dis_{you_2}
dishe>disyou1,dishe>disyou2,那么就有
d
i
s
h
e
+
d
i
s
y
o
u
2
>
d
i
s
y
o
u
1
+
d
i
s
y
o
u
2
dis_{he}+dis_{you_2} \gt dis_{you_1}+dis_{you_2}
dishe+disyou2>disyou1+disyou2,另一边也是如此,发现
d
i
s
h
e
→
y
o
u
1
dis_{he\to you_1}
dishe→you1这条路径居然比你们直径长。
这是你直径的问题,你好好给我反思。
好的,求出直径,求出每个点的
d
d
d,求出每个点的
S
S
S。
接下来的问题是找离我最近的,
S
>
q
S\gt q
S>q的点了。
如上图,给的三元组是
(
u
,
v
,
q
)
(u,v,q)
(u,v,q)。
我们求出
u
,
v
的
l
c
a
u,v的lca
u,v的lca,于是我们可以把问题分解为
在
u
→
l
c
a
在u \to lca
在u→lca上找里面离
u
u
u最近的
S
>
q
S\gt q
S>q的点,在
v
→
l
c
a
v\to lca
v→lca上找里面离
v
v
v最远(那就是这半边离
u
u
u最近)的
S
>
q
S\gt q
S>q的点这两个类似的问题。
这个怎么找,这个可以倍增二分答案,也可以树剖rmq(orz Master Yi orz Freopen)。
准确来说是二分查找?
orz YMX 树上二分答案真的是太巧妙了,我弱小的心灵被震撼到了。
然后随便搞搞。
代码
#include<cstdio>
#include<cstring>
#include<iostream>
#include<algorithm>
using namespace std;
inline void Read(int &p)
{
p=0;
char c=getchar();
while(c<'0' || c>'9') c=getchar();
while(c>='0' && c<='9')
p=p*10+c-'0',c=getchar();
}
typedef long long ll;
const int MAXN=102030,MAXM=202030,LOG=20;
bool vis[MAXN];
ll len,dis[MAXN];
int ark,crl,arr[MAXN];
int dep[MAXN],cut[MAXN],f[LOG][MAXN],mx[LOG][MAXN];
int n,q,a,b,c,u,v,w,y,cnt,head[MAXN],nxt[MAXM],to[MAXM],val[MAXM];
inline void addedge(int u,int v,int w)
{
nxt[++cnt]=head[u],head[u]=cnt,to[cnt]=v,val[cnt]=w;
}
void dfs(int pos,int cut,ll sig,int &pts)
{
dis[pos]=max(dis[pos],sig);
if(crl<sig) crl=sig,pts=pos;
for(int i=head[pos];i;i=nxt[i])
if(to[i]!=cut) dfs(to[i],pos,sig+val[i],pts);
}
void build_tree(int pos)
{
vis[pos]=1;
for(int i=head[pos];i;i=nxt[i])
if(!vis[to[i]]) cut[to[i]]=pos,dep[to[i]]=dep[pos]+1,build_tree(to[i]);
}
inline void init()
{
for(int i=1;i<=n;i++) f[0][i]=cut[i],mx[0][i]=max(arr[i],arr[cut[i]]);
for(int j=1;j<LOG;j++)
for(int i=1;i<=n;i++)
f[j][i]=f[j-1][f[j-1][i]],mx[j][i]=max(mx[j-1][i],mx[j-1][f[j-1][i]]);
}
inline int getk(int x,int k)
{
for(int i=0;i<LOG;i++)
if(k&(1<<i)) x=f[i][x];
return x;
}
inline int getd(int x,int d)
{
return getk(x,dep[x]-d);
}
inline int lca(int u,int v)
{
if(dep[u]<dep[v]) y=u,u=v,v=y;
u=getd(u,dep[v]);
if(u==v) return u;
for(int i=LOG-1;i>=0;i--)
if(f[i][u]!=f[i][v])
u=f[i][u],v=f[i][v];
return cut[u];
}
int near(int x,int y,int lim)
{
if(dep[x]-dep[y]<=1)
{
if(arr[x]>=lim) return x;
if(arr[y]>=lim) return y;
return -1;
}
int len=dep[x]-dep[y],xm=0,pos=x;
for(int j=0;(1<<j)<=len;j++)
if((1<<j)&len) xm=max(xm,mx[j][pos]),pos=f[j][pos];
if(xm<lim) return -1;
len/=2,pos=x;
for(int j=0;(1<<j)<=len;j++)
if((1<<j)&len) pos=f[j][pos];
int ans=near(x,pos,lim);
if(ans!=-1) return ans;
return near(f[0][pos],y,lim);
}
int far(int x,int y,int lim)
{
if(dep[x]-dep[y]<=1)
{
if(arr[y]>=lim) return y;
if(arr[x]>=lim) return x;
return -1;
}
int len=dep[x]-dep[y],xm=0,pos=x;
for(int j=0;(1<<j)<=len;j++)
if((1<<j)&len) xm=max(xm,mx[j][pos]),pos=f[j][pos];
if(xm<lim) return -1;
len/=2,pos=x;
for(int j=0;(1<<j)<=len;j++)
if((1<<j)&len) pos=f[j][pos];
int ans=far(f[0][pos],y,lim);
if(ans!=-1) return ans;
return far(x,pos,lim);
}
int main()
{
Read(n),Read(q),Read(a),Read(b),Read(c);
for(int i=2;i<=n;i++) Read(u),Read(v),Read(w),addedge(u,v,w),addedge(v,u,w);
dfs(1,0,0,u),crl=0,dfs(u,0,0,v),dfs(v,0,0,ark);
for(int i=1;i<=n;i++) arr[i]=1ll*(dis[i]+a)%c*b%c;
build_tree(1),init();
while(q--)
{
Read(u),Read(v),Read(w),crl=lca(u,v);
ark=near(u,crl,w);
if(ark==-1) ark=far(v,crl,w);
printf("%d\n",ark);
}
}
说一下我的错误吧,(第一次是因为没有build_tree)大家看我的读入询问的时候,我的三元组是
(
u
,
v
,
w
)
,
三
个
全
局
变
量
(u,v,w),三个\bold{全局变量}
(u,v,w),三个全局变量,大家看我的lca,第一行是不是交换两个节点
局
部
变
量
u
,
v
局部变量u,v
局部变量u,v?之前我写的是
w
=
u
,
u
=
v
,
v
=
w
;
(
全
局
变
量
w
,
因
为
这
样
不
需
要
i
n
t
w
,
就
压
了
一
行
了
)
w=u,u=v,v=w;(全局变量w,因为这样不需要int~w,就压了一行了)
w=u,u=v,v=w;(全局变量w,因为这样不需要int w,就压了一行了),好像没得问题,结果呢,就把我读入时的
w
w
w给变了。
orz YMX,给的数据太良心了,居然把我的这个错误给逮住了。