【原创】2019.10.10模拟赛 病毒分裂 疫情延迟 避难向导

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++kn1=k1kn11。然后再模一下合数。

又是我喜闻乐见的模合数。

但是不用怕,加减乘模合数都没有影响,只有除法算逆元的时候不能够费马小定理不能够筛逆元罢了,我们还有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=n1ki 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(2n11)+k2n1solve(2n11)=(k2n1+1)solve(2n11)
然后就递归解决了。
也可以继续推式子化简。

马略了。
马不略了, 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>disyou1dishe>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} disheyou1这条路径居然比你们直径长。
在这里插入图片描述
这是你直径的问题,你好好给我反思。

好的,求出直径,求出每个点的 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,vlca,于是我们可以把问题分解为 在 u → l c a 在u \to lca ulca上找里面离 u u u S > q S\gt q S>q的点,在 v → l c a v\to lca vlca上找里面离 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;wint w,好像没得问题,结果呢,就把我读入时的 w w w给变了。
orz YMX,给的数据太良心了,居然把我的这个错误给逮住了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值