寒假Day5
1.百日旅行(动归SOLVE法)
Solution
0和1表示当前天数所作出的选择是吃饭还是旅行,如果是吃饭,肯定是由旅行或连续吃饭转移过来的,
我们就可枚举是从那个j天开始旅行转移过来的,
同理可以求旅行,最后取个min就行了
#include<bit/stdc++.h>
#define ll long long
#define inf 100000000000000
using namespace std;
ll dp[2][200001],N,P,Q;
int main()
{
cin>>N>>P>>Q;
for(ll i=1;i<=N;i++)dp[0][i]=dp[1][i]=inf;
for(ll i=1;i<=N;i++)
for(ll j=0;j<i;j++)
{
dp[0][i]=min(dp[0][i],dp[1][j]+P*(i-j)*(i-j));
dp[1][i]=min(dp[1][i],dp[0][j]+Q*(i-j));
}
printf("%lld\n",min(dp[0][N],dp[1][N]));
return 0;
}
2.线性筛素数
线性筛素数
它们保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次,所以时间复杂度是O(n)
#include<cstdio>
#include<cstring>
using namespace std;
const int N=10000005;
int n,m,cnt,x;
int Prime[N];
bool check[N];
int main()
{
memset(check,true,sizeof(check));
check[1]=false;
scanf("%d%d",&n,&m);
for(int i=2;i<=n;i++){
if(check[i]) Prime[++cnt]=i;
for(int j=1;j<=cnt;j++){
if(i*Prime[j]>n)break;
check[i*Prime[j]]=false;
if(i%Prime[j]==0)break;
}
}
while(m--)
{
scanf("%d",&x);
if(check[x])printf("Yes\n");
else printf("No\n");
}
return 0;
}
3.LCA模板
#define RG register
#include<cstdio>
#include<iostream>
using namespace std;
const int N=500010;
int n,m,s,cnt;//n点数,m边数,s根节点
int dep[N],f[N][25],last[N];//dep深度,f[i][j]表示第i个节点的2^j祖先的序号
inline int read()
{
RG int x=0,w=1;RG char ch=getchar();
while(ch<'0'||ch>'9'){if(ch=='-')w=-1;ch=getchar();}
while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
return x*w;
}
struct edge{//邻接表
int to,next;
}e[2*N];
void insert(int u,int v)//连边
{
e[++cnt]=(edge){v,last[u]};last[u]=cnt;
e[++cnt]=(edge){u,last[v]};last[v]=cnt;
}
void dfs(int now)//dfs预处理出每个点的深度以及每个点的父亲
{
for(int i=last[now];i;i=e[i].next)
{
int v=e[i].to;
if(dep[v])continue;
dep[v]=dep[now]+1;
f[v][0]=now;//f[i][0]即父亲
dfs(v);
}
}
void init()
{
dep[s]=1;//根节点深度置为1
dfs(s);
for(int j=1;j<=20;j++)//注意j循环在i循环外
for(int i=1;i<=n;i++)
f[i][j]=f[f[i][j-1]][j-1];
}
int LCA(int a,int b)
{
if(dep[b]>dep[a])swap(a,b);
for(int i=20;i>=0;i--)if(f[a][i]&&dep[f[a][i]]>=dep[b])a=f[a][i];//倍增上跳,使a与b位于同一深度
if(a==b)return a;//特判b是a的祖先情况
for(int i=20;i>=0;i--)
if(f[a][i]&&f[b][i]&&f[a][i]!=f[b][i]){a=f[a][i];b=f[b][i];}//倍增上跳
return f[a][0];//注意返回值,因为只跳到了LCA的儿子处
}
int main()
{
n=read();m=read();s=read();
for(int i=1;i<n;i++)insert(read(),read());
init();
while(m--)printf("%d\n",LCA(read(),read()));
return 0;
}
4.青蛙的约会
题目描述
两只青蛙在网上相识了,它们聊得很开心,于是觉得很有必要见一面。它们很高兴地发现它们住在同一条纬度线上,于是它们约定各自朝西跳,直到碰面为止。可是它们出发之前忘记了一件很重要的事情,既没有问清楚对方的特征,也没有约定见面的具体位置。不过青蛙们都是很乐观的,它们觉得只要一直朝着某个方向跳下去,总能碰到对方的。但是除非这两只青蛙在同一时间跳到同一点上,不然是永远都不可能碰面的。为了帮助这两只乐观的青蛙,你被要求写一个程序来判断这两只青蛙是否能够碰面,会在什么时候碰面。
我们把这两只青蛙分别叫做青蛙A和青蛙B,并且规定纬度线上东经0度处为原点,由东往西为正方向,单位长度1米,这样我们就得到了一条首尾相接的数轴。设青蛙A的出发点坐标是x,青蛙B的出发点坐标是y。青蛙A一次能跳m米,青蛙B一次能跳n米,两只青蛙跳一次所花费的时间相同。纬度线总长L米。现在要你求出它们跳了几次以后才会碰面
Solution
关于这个题难的并不是线性方程的推导
其实是入门的扩展欧几里德定理的运用
exgcd到底是干什么?
他得出来的东西到底是什么??
有什么卵用吗???
LL gcd(LL a,LL b,int &xx,int &yy)
{
if(b==0)
{
xx=1;yy=0;
return a;
}
LL r=gcd(b,a%b,xx,yy);
int t=xx;
xx=yy;
yy=t-(a/b)*yy;
return r;
}
拿一个exgcd的板子来说明,我们的得到的r是a和b的最大公约数,
它会自动的得出一组解满足原不定方程组的结集.
这个返回的解是任意的,所以例如青蛙的约会这道题,我们需要在对得出来的x0进行操作让它取min值,exgcd的用处记住!!!是用来求不定方程的通解
#include<bits/stdc++.h>
#define ll long long
using namespace std;
ll gcd(ll a,ll b) {return b==0?a:gcd(b,a%b);}
void exgcd(ll a,ll b,ll &x,ll &y){
if(b==0){x=1;y=0;return;}
exgcd(b,a%b,x,y);
ll t=x;x=y;y=t-a/b*y;
}
int main(){
ll x,y,m,n,l,a,b,c,t,X,Y;
scanf("%lld%lld%lld%lld%lld",&x,&y,&m,&n,&l);
a=n-m;b=l;c=x-y; t=gcd(a,b);
if(c%t!=0){printf("Impossible\n");return 0;}
exgcd(a,b,x,y);
x=x*c/t;b=abs(b/t);
x=(x%b+b)%b;
printf("%lld\n",x);
}
5.乘法逆元模板
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL n,q,x,y;
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(b==0)
{
x=1;y=0;
return a;
}
LL r=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-(a/b)*y;
return r;
}
int main(){
scanf("%d%d",&n,&q);
for(int i=1;i<=n;i++){
exgcd(i,q,x,y);
x=(x%q+q)%q;
printf("%d\n",x);
}
return 0;
}
6.洗牌
为了表彰小联为Samuel星球的探险所做出的贡献,小联被邀请参加Samuel星球近距离载人探险活动。
由于Samuel星球相当遥远,科学家们要在飞船中度过相当长的一段时间,小联提议用扑克牌打发长途旅行中的无聊时间。玩了几局之后,大家觉得单纯玩扑克牌对于像他们这样的高智商人才来说太简单了。有人提出了扑克牌的一种新的玩法。
对于扑克牌的一次洗牌是这样定义的,将一叠N(N为偶数)张扑克牌平均分成上下两叠,取下面一叠的第一张作为新的一叠的第一张,然后取上面一叠的第一张作为新的一叠的第二张,再取下面一叠的第二张作为新的一叠的第三张……如此交替直到所有的牌取完。
如果对一叠6张的扑克牌1 2 3 4 5 6,进行一次洗牌的过程如下图所示:
从图中可以看出经过一次洗牌,序列1 2 3 4 5 6变为4 1 5 2 6 3。当然,再对得到的序列进行一次洗牌,又会变为2 4 6 1 3 5。
游戏是这样的,如果给定长度为N的一叠扑克牌,并且牌面大小从1开始连续增加到N(不考虑花色),对这样的一叠扑克牌,进行M次洗牌。最先说出经过洗牌后的扑克牌序列中第L张扑克牌的牌面大小是多少的科学家得胜。小联想赢取游戏的胜利,你能帮助他吗?
Solution
这道题目看懂后模拟后
你会发现一个神奇的规律
每洗一次牌,位置就变成x×2(mod n+1)
所以转移m次就会变成(x*2^m)(mod(n+1))
那么也就是说对于第k 张牌,满足x*2^m≡k (mod(n+1))
就可以用exgcd来完美一波
#include<bits/stdc++.h>
#define LL long long
using namespace std;
LL n,l,m,x,y;
LL fast(LL a,LL n,LL p)
{
LL s=1;
while(n)
{
if(n&1)
s=(s*a)%p;
a=(a*a)%p;
n=n>>1;
}
return s;
}
LL exgcd(LL a,LL b,LL &x,LL &y)
{
if(b==0)
{
x=1;y=0;
return a;
}
LL r=exgcd(b,a%b,x,y);
int t=x;
x=y;
y=t-(a/b)*y;
return r;
}
int main(){
scanf("%d%d%d",&n,&m,&l);
n++;
LL p=fast(2,m,n);
LL r=exgcd(p,n,x,y);
while(x<0) x+=n;
x=(l/r)*x%n;
cout<<x<<endl;
return 0;
}