兔农
(传送门)
题意
F[n]=F[n-1]+F[n-2],if (n >=3 && F[n] % G == 1) F[n]--
给出N、G、P,求F[N] % P的值。
分析
算法一:记录F[i]%k和F[i]%p的值,暴力得75分。
算法二:对于F[i]%k,把减1得0的位置标出,以这些0为界分段,发现:
1.每段开头为相同两数,恰是上一段的最末一位非0数。只有k-1种余数,所以如果有循环,不超过k段。2.斐波那契数列为fib[i],假如某段首数字为x,那这一段第i个数为x*fib[i]% k。记这一段长度为len,则有x*fib[len]==1(mod k)。
找整个数列的循环结构:求x的逆元得到fib[len],由fib[len]得知len,用x*fib[len-1]%k算出下一段的段首,重复操作直到发现循环
实现:1.扩展欧几里得或者欧拉定理2.预处理indfib[y]数组,表示斐波那契数列中模k余y的数第一次出现的下标3.预处理fib[i]模k的值。暴力算fib数组并记录indfib[],发现循环即停止。假如第1步不存在逆元,或第2步不存在符合的len,那么这一段永不会终止(如k=8时),那就不存在循环,可当作最后一段无穷大。
考虑用矩阵乘法计算f[n]%p:
构造3*3矩阵A使(Ai-1,Ai,1)变为(Ai,Ai-1+Ai,1),矩阵B使其变为(Ai-1,Ai – 1,1)
令初始矩阵为(1,0,1),通过不断右乘A和B实现累加、减1两种操作。
代码
#include <bits/stdc++.h>
using namespace std;
const int maxn=1000000+5;
long long n,k,p;
long long fib[maxn*10],indfib[maxn],inv[maxn];
long long exgcd(long long a,long long b,long long &lx,long long &ly)
{
long long x=0,y=1,q,t;lx=1,ly=0;
while(b)
{
q=a/b;
t=b;b=a%b;a=t;
t=x;x=lx-q*x;lx=t;
t=y;y=ly-q*y;ly=t;
}
return a;
}
void buildinv()
{
long long tmp1,tmp2;
inv[1]=1;
for(long long i=2;i<k;i++)
{
if(exgcd(i,k,tmp1,tmp2)!=1) inv[i]=0;
else inv[i]=tmp1<0?tmp1+k:tmp1;
}
}
long long ord[maxn],lensum[maxn],vis[maxn];
long long tot=0;
struct Matrix
{
long long a[3][3];
void cl()
{
memset(a,0,sizeof(a));
}
void init()
{
cl();
a[0][0]=a[1][1]=a[2][2]=1;
}
};
Matrix mul(Matrix a,Matrix b)
{
Matrix c;
for(int i=0;i<3;i++)
for(int j=0;j<3;j++)
{
c.a[i][j]=0;
for(int k=0;k<3;k++)c.a[i][j]+=a.a[i][k]*b.a[k][j];
c.a[i][j]%=p;
}
return c;
}
Matrix mpow(Matrix a,long long t)
{
Matrix c; c.init();
while(t)
{
if(t&1)c=mul(c,a);
a=mul(a,a);
t>>=1;
}
return c;
}
Matrix s[maxn],A,B;
int main()
{
cin>>n>>k>>p;
fib[0]=0,fib[1]=fib[2]=1;
for(long long i=3;;i++)
{
fib[i]=fib[i-1]+fib[i-2];
if(fib[i]>=k) fib[i]-=k;
if(!indfib[fib[i]]) indfib[fib[i]]=i;
if(fib[i]==1 && fib[i-1]==1) break;
}
buildinv();
long long cur=1;
lensum[0]=0;
A.cl(); A.a[0][1]=A.a[1][0]=A.a[1][1]=A.a[2][2]=1;
B.cl(); B.a[0][0]=B.a[1][1]=B.a[2][2]=1;B.a[2][1]=-1;
while(1)
{
if(vis[cur]) break;
if(!inv[cur] || !indfib[inv[cur]]) break;
tot++;
vis[ord[tot]=cur]=tot;
lensum[tot]=lensum[tot-1]+indfib[inv[cur]];
s[tot]=mpow(A,indfib[inv[cur]]);
s[tot]=mul(s[tot],B);
cur=1ll*cur*fib[indfib[inv[cur]]-1]%k;
}
Matrix N;
N.init();
if(vis[cur])
{
int pre=vis[cur]-1;
Matrix M;
for(int i=1;i<=pre && n>=lensum[i];i++)
N=mul(N,s[i]);
if(i<=pre)
{
M=mpow(A,n-lensum[i-1]);
N=mul(N,M);
}
else
{
long long cylen=lensum[tot]-lensum[pre];
n-=lensum[pre];
Matrix C; C.init();
for(int i=pre+1;i<=tot;i++)
C=mul(C,s[i]);
C=mpow(C,n/cylen);
N=mul(N,C);n%=cylen;
for(int i=pre+1;n>=lensum[i]-lensum[pre];i++)
N=mul(N,s[i]);
M=mpow(A,n-(lensum[i-1]-lensum[pre]));
N=mul(N,M);
}
}
else
{
Matrix M;
for(int i=1;i<=tot && n>=lensum[i];i++)
N=mul(N,s[i]);
M=mpow(A,n-lensum[i-1]);
N=mul(N,M);
}
long long ans=(N.a[0][1]+N.a[2][1])%p;
if(ans<0) ans+=p;
cout<<ans<<'\n';
return 0;
}
智能车比赛
(传送门)
题意
n个矩形区域拼接,每个矩形的边都平行于坐标轴,第i个矩形区域的左下角和右上角坐标分别为(xi,1,yi,1)和(xi,2,yi,2)。
题目保证:xi,1<xi,2=xi+1,1,且yi,1<yi,2,相邻两个矩形有的边,智能车可以通过这部分穿梭于矩形区域。最快时间从S到T。
分析
从每一个矩形的顶点包括起点开始检查能不能到其它点,记录长度。最短路径上的点一定是在矩形的顶点上,因为是不断往前走,用dp来做最短路。检查方法是先假定一个视野的上下界,然后检查每个顶点是否在视野范围内(叉积),在就更新距离。从每个矩形交界处判断是否会阻挡视野,会就更新上下界,直到视野为0或检查到终点所在矩形。做完所有点找到答案。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2000+5;
int n;
int xl[MAXN],Yd[MAXN],xr[MAXN],Yu[MAXN];
int xs,ys,xt,yt;
inline double dis(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
inline int cross(int x,int y,int x1,int y1,int x2,int y2)
{
long long tmp=(y2-y)*(x1-x)-(y1-y)*(x2-x);
if(tmp>0) return 1;
if(tmp<0) return -1;
return 0;
}
double d[MAXN][2];
int x[MAXN],yu[MAXN],yd[MAXN];
int tot=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d%d%d",&xl[i],&Yd[i],&xr[i],&Yu[i]);
scanf("%d%d%d%d",&xs,&ys,&xt,&yt);
if(xs>xt)
{swap(xs,xt);swap(ys,yt);}
double v;
scanf("%lf",&v);
int st,ed;
for(st=n;st>=1;st--)
if(xs>=xl[st] && xs<=xr[st] && ys>=Yd[st] && ys<=Yu[st]) break;
for(ed=1;ed<=n;ed++)
if(xt>=xl[ed] && xt<=xr[ed] && yt>=Yd[ed] && yt<=Yu[ed]) break;
for(int i=st;i<ed;i++)
{
x[++tot]=xr[i];
yu[tot]=min(Yu[i],Yu[i+1]);
yd[tot]=max(Yd[i],Yd[i+1]);
}
x[++tot]=xt;
yu[tot]=yd[tot]=yt;
x[0]=xs;yu[0]=yd[0]=ys;
d[0][0]=d[0][1]=0.0;
for(int i=1;i<=tot;i++)
d[i][0]=d[i][1]=100000000000.0;
for(int j=0;j<tot;j++)
{
int upx=x[j],upy=yu[j],downx=x[j],downy=yu[j];
for(int i=j+1;i<=tot;i++)
{
if(cross(x[j],yu[j],upx,upy,x[i],yu[i])<=0)
{
if(cross(x[j],yu[j],downx,downy,x[i],yu[i])>=0)
d[i][0]=min(d[i][0],d[j][0]+dis(x[j],yu[j],x[i],yu[i]));
upx=x[i];
upy=yu[i];
}
if(cross(x[j],yu[j],downx,downy,x[i],yd[i])>=0)
{
if(cross(x[j],yu[j],upx,upy,x[i],yd[i])<=0)
d[i][1]=min(d[i][1],d[j][0]+dis(x[j],yu[j],x[i],yd[i]));
downx=x[i];
downy=yd[i];
}
if(cross(x[j],yu[j],upx,upy,downx,downy)>0) break;
}
upx=x[j],upy=yd[j],downx=x[j],downy=yd[j];
for(int i=j+1;i<=tot;i++)
{
if(cross(x[j],yd[j],upx,upy,x[i],yu[i])<=0)
{
if(cross(x[j],yd[j],downx,downy,x[i],yu[i])>=0)
d[i][0]=min(d[i][0],d[j][1]+dis(x[j],yd[j],x[i],yu[i]));
upx=x[i];
upy=yu[i];
}
if(cross(x[j],yd[j],downx,downy,x[i],yd[i])>=0)
{
if(cross(x[j],yd[j],upx,upy,x[i],yd[i])<=0)
d[i][1]=min(d[i][1],d[j][1]+dis(x[j],yd[j],x[i],yd[i]));
downx=x[i];
downy=yd[i];
}
if(cross(x[j],yd[j],upx,upy,downx,downy)>0) break;
}
}
printf("%.10lf\n",d[tot][0]/v);
return 0;
}
阿狸的打字机
(传送门)
题意
输入字母,这个字母加在凹槽的最后。按'B',凹槽最后一个字母消失。按'P'打印出凹槽中现有的所有字母并换行。输入(x,y)(显示第x个打印的字符串在第y个打印的字符串中出现了多少次。
分析
提前dfs出整个fail树上每个节点最先到达的时间和最后到达时间,因为是dfs所以开始时间到结束时间内都是在同一颗子树上,用树状数组去维护。在建图时边读入建立tire,遇P就标记,遇到B就回退到当前节点的父亲节点,遇到小写字母就建立tire树。处理时遇到P就在x开始时间到x结束时间内求有多少个点,遇到B退回父亲并在当前节点的开始时间处开始删除1,遇到小写字母就在当前节点开始时间处加1。若每次没有B,那比x先插入的点一定不会到x的子树中(len(后面的串))=len(前面)),若有B子树中比x靠前的点已删掉,所以x开始时间-x结束时间求和就是答案。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=100000+10;
struct node
{
int next[26],fail,father;
} ac[MAXN];
struct rec
{
int y,n;
}e1[MAXN],e2[MAXN];
int head1[MAXN],head2[MAXN],tot1,tot2;
int ls[MAXN],le[MAXN],num[MAXN],quary[MAXN][3],tree[MAXN];
int tn,t,m,len;
char str[MAXN];
void Buildfail()
{
queue<int> Q;
ac[0].fail=-1;
Q.push(0);
while(!Q.empty())
{
int u=Q.front(); Q.pop();
for(int i=0;i<26;i++)
{
int tmp=ac[u].next[i],p;
if(tmp)
{
for(p=ac[u].fail;p!=-1;p=ac[p].fail)
if(ac[p].next[i])
{
ac[tmp].fail=ac[p].next[i];
break;
}
if(p==-1) ac[tmp].fail=0;
Q.push(tmp);
}
}
}
return;
}
void dfs(int k)
{
ls[k]=++t;
for(int i=head1[k];i;i=e1[i].n)
dfs(e1[i].y);
le[k]=t;
}
void Buildtree()
{
for(int i=1;i<=tn;i++)
{
e1[++tot1].y=i;
e1[tot1].n=head1[ac[i].fail];
head1[ac[i].fail]=tot1;
}
dfs(0);
}
int lowbit(int x)
{
return x&(-x);
}
int getsum(int k)
{
int ans=0;
for(int i=k;i>0;i-=lowbit(i))
ans+=tree[i];
return ans;
}
void inc(int k,int det)
{
for(int i=k;i<=t;i+=lowbit(i))
tree[i]+=det;
}
int main()
{
scanf("%s",str);
int p=0,sum=0,top=-1;
len=strlen(str);
for(int i=0;i<len;i++)
{
if(str[i]=='P')
num[++sum]=p;
if(str[i]=='B')
p=ac[p].father;
if('a'<=str[i] && str[i]<='z')
{
int index=str[i]-'a';
if(!ac[p].next[index])
{
ac[++tn].father=p;
ac[p].next[index]=tn;
}
p=ac[p].next[index];
}
}
Buildfail();
Buildtree();
scanf("%d",&m);
for(int i=1;i<=m;i++)
{
scanf("%d%d",&quary[i][0],&quary[i][1]);
quary[i][0]=num[quary[i][0]];
quary[i][1]=num[quary[i][1]];
e2[++tot2].y=i;
e2[tot2].n=head2[quary[i][1]];
head2[quary[i][1]]=tot2;
}
p=0;
for(int i=0;i<len;i++)
{
if(str[i]=='P')
for(int j=head2[p];j;j=e2[j].n)
quary[e2[j].y][2]=getsum(le[quary[e2[j].y][0]])-getsum(ls[quary[e2[j].y][0]]-1);
if(str[i]=='B')
{
inc(ls[p],-1);
p=ac[p].father;
}
if('a'<=str[i] && str[i]<='z')
{
p=ac[p].next[str[i]-'a'];
inc(ls[p],1);
}
}
for(int i=1;i<=m;i++)
printf("%d\n",quary[i][2]);
return 0;
}
道路修建
(传送门)
题意
n个国家只修建n-1条双向道路。每条路的修建费用等于道路长度乘以道路两端的国家个数之差的绝对值。给定方案,计算费用。
分析
在图上的dfs,这一年最简单的一个题了
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=1000000+5;
long long ans;
int n,size[MAXN],fa[MAXN];
struct Edge
{
int to,w,next;
} edges[MAXN<<1];
int cnt,head[MAXN];
void addEdge(int u,int v,int w)
{
edges[++cnt]=(Edge){v,w,head[u]};
head[u]=cnt;
}
void dfs(int x)
{
size[x]++;
for(int i=head[x];i;i=edges[i].next)
{
int now=edges[i].to;
if(now!=fa[x])
{
fa[now]=x;
dfs(now);
size[x]+=size[now];
ans+=(long long)edges[i].w * abs(n-2*size[now]);
}
}
}
int main()
{
cin>>n;
for(int i=1;i<n;i++)
{
int u,v,w;
scanf("%d%d%d",&u,&v,&w);
addEdge(u,v,w);
addEdge(v,u,w);
}
dfs(1);
cout<<ans<<endl;
return 0;
}
NOI嘉年华
(传送门)
题意
两个会场,n个活动,活动从Si开始,持续Ti,可安排到任意会场,可不安排。不能有两活动在两会场同时进行。若第i个活动必须举办,求活动相对较少的会场的活动数量的最大值。
分析
先离散化,设两个嘉年华分别为集合A、B,不选为集合C,
然后DP计算,定义以及方程直接看代码的注释吧。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=200+5;
pair<int,int> event[MAXN];//活动i的时间为(fir[i],sec[i])
int num[MAXN*2][MAXN*2];//num[i][j]表示i<=fir[i],sec[i]<=j的活动i个数
int pre[2*MAXN][MAXN];//pre[i][j]表示将所有sec[k]<=i的元素k放入集合A、B、C中,当|A|=j时,|B|的最大值
int back[2*MAXN][MAXN];//设suc[i][j]表示将所有fir[k]>=i的元素k放入集合A、B、C中,当|A|=j时,|B|的最大值
int ans[2*MAXN][2*MAXN];//设ans[i][j]表示将区间所有i<=fir[k],sec[k]<=j的所有元素k放入A中的 活动较少的嘉年华的活动数的最大值
int a[MAXN*2];
int n,m;
map<int,int> home;
void discretization()
{
sort(a+1,a+2*n+1);
int j=0;
a[0]=-1;
for(int i=1;i<=2*n;i++)
{
if(a[i]!=a[i-1]) j++;
home[a[i]]=j;
}
for(int i=1;i<=n;i++)
{
event[i].first=home[event[i].first];
event[i].second=home[event[i].second];
}
m=j;
}
void solve1()
{
memset(num,0,sizeof(num));
memset(pre,0xaf,sizeof(pre));
memset(back,0xaf,sizeof(back));
for(int k=1;k<=n;k++)
for(int i=0;i<=event[k].first;i++)
for(int j=event[k].second;j<=m+1;j++)
num[i][j]++;
pre[0][0]=0;
for(int i=1;i<=m;i++)
for(int j=0;j<=n;j++)
for(int k=0;k<i;k++)
{
pre[i][j]=max(pre[i][j],pre[k][j]+num[k][i]);
if(j>=num[k][i])
pre[i][j]=max(pre[i][j],pre[k][j-num[k][i]]);
}
back[m+1][0]=0;
for(int i=m;i>=1;i--)
for(int j=0;j<=n;j++)
for(int k=i+1;k<=m+1;k++)
{
back[i][j]=max(back[i][j],back[k][j]+num[i][k]);
if(j>=num[i][k])
back[i][j]=max(back[i][j],back[k][j-num[i][k]]);
}
int ans=0;
for(int j=0;j<=n;j++)
ans=max(ans,min(j,pre[m][j]));
printf("%d\n",ans);
}
int calc(int i,int j,int x,int y)
{
return min(x+y+num[i][j],pre[i][x]+back[j][y]);
}
void solve2()
{
memset(ans,0,sizeof(ans));
for(int i=1;i<=m;i++)
for(int j=i;j<=m;j++)
{
int s=0,y=n;
for(int x=0;x<=n;x++)
{
for(y;y>0;y--)
{
if(calc(i,j,x,y-1)<calc(i,j,x,y))
break;
}
s=max(s,calc(i,j,x,y));
}
ans[i][j]=s;
}
for(int k=1;k<=n;k++)
{
int s=0;
for(int i=1;i<=event[k].first;i++)
for(int j=event[k].second;j<=m;j++)
s=max(s,ans[i][j]);
printf("%d\n",s);
}
}
int main()
{
scanf("%d",&n);
int i;
for(int i=1;i<=n;i++)
{
scanf("%d%d",&event[i].first,&event[i].second);
event[i].second+=event[i].first;
a[2*i-1]=event[i].first;
a[2*i]=event[i].second;
}
discretization();
solve1();
solve2();
return 0;
}
兔兔与蛋蛋
(传送门)
题意
每人轮流移棋子,同个格子不能走两次,谁不能移就输。
分析
暴力算法是用博弈逐层扩展MIN-MAX来判断,这样做超时。可以将每次选手的操作看作空白位置的移动,可证明空白的移动路径一定不存在自交,自交,说明移动偶数后到了原位,与规则矛盾。标记所有可以达到的点,黑白染色,通过求二分图匹配的方法来判断是否存在必胜策略,若空白点在最大匹配上,那么存在必胜策略,否则不存在必胜策略。对每一局的改变,将一些点标记为不可达即可。
代码
#include <bits/stdc++.h>
using namespace std;
const int MAXN=2000+5;
int n;
int xl[MAXN],Yd[MAXN],xr[MAXN],Yu[MAXN];
int xs,ys,xt,yt;
inline double dis(double x1,double y1,double x2,double y2)
{
return sqrt((x1-x2)*(x1-x2)+(y1-y2)*(y1-y2));
}
inline int cross(int x,int y,int x1,int y1,int x2,int y2)
{
long long tmp=(y2-y)*(x1-x)-(y1-y)*(x2-x);
if(tmp>0) return 1;
if(tmp<0) return -1;
return 0;
}
double d[MAXN][2];
int x[MAXN],yu[MAXN],yd[MAXN];
int tot=0;
int main()
{
scanf("%d",&n);
for(int i=1;i<=n;i++)scanf("%d%d%d%d",&xl[i],&Yd[i],&xr[i],&Yu[i]);
scanf("%d%d%d%d",&xs,&ys,&xt,&yt);
if(xs>xt)
{swap(xs,xt);swap(ys,yt);}
double v;
scanf("%lf",&v);
int st,ed;
for(st=n;st>=1;st--)
if(xs>=xl[st] && xs<=xr[st] && ys>=Yd[st] && ys<=Yu[st]) break;
for(ed=1;ed<=n;ed++)
if(xt>=xl[ed] && xt<=xr[ed] && yt>=Yd[ed] && yt<=Yu[ed]) break;
for(int i=st;i<ed;i++)
{
x[++tot]=xr[i];
yu[tot]=min(Yu[i],Yu[i+1]);
yd[tot]=max(Yd[i],Yd[i+1]);
}
x[++tot]=xt;
yu[tot]=yd[tot]=yt;
x[0]=xs;yu[0]=yd[0]=ys;
d[0][0]=d[0][1]=0.0;
for(int i=1;i<=tot;i++)
d[i][0]=d[i][1]=100000000000.0;
for(int j=0;j<tot;j++)
{
int upx=x[j],upy=yu[j],downx=x[j],downy=yu[j];
for(int i=j+1;i<=tot;i++)
{
if(cross(x[j],yu[j],upx,upy,x[i],yu[i])<=0)
{
if(cross(x[j],yu[j],downx,downy,x[i],yu[i])>=0)
d[i][0]=min(d[i][0],d[j][0]+dis(x[j],yu[j],x[i],yu[i]));
upx=x[i];
upy=yu[i];
}
if(cross(x[j],yu[j],downx,downy,x[i],yd[i])>=0)
{
if(cross(x[j],yu[j],upx,upy,x[i],yd[i])<=0)
d[i][1]=min(d[i][1],d[j][0]+dis(x[j],yu[j],x[i],yd[i]));
downx=x[i];
downy=yd[i];
}
if(cross(x[j],yu[j],upx,upy,downx,downy)>0) break;
}
upx=x[j],upy=yd[j],downx=x[j],downy=yd[j];
for(int i=j+1;i<=tot;i++)
{
if(cross(x[j],yd[j],upx,upy,x[i],yu[i])<=0)
{
if(cross(x[j],yd[j],downx,downy,x[i],yu[i])>=0)
d[i][0]=min(d[i][0],d[j][1]+dis(x[j],yd[j],x[i],yu[i]));
upx=x[i];
upy=yu[i];
}
if(cross(x[j],yd[j],downx,downy,x[i],yd[i])>=0)
{
if(cross(x[j],yd[j],upx,upy,x[i],yd[i])<=0)
d[i][1]=min(d[i][1],d[j][1]+dis(x[j],yd[j],x[i],yd[i]));
downx=x[i];
downy=yd[i];
}
if(cross(x[j],yd[j],upx,upy,downx,downy)>0) break;
}
}
printf("%.10lf\n",d[tot][0]/v);
return 0;
}