来自FallDream的博客,未经允许,请勿转载,谢谢。
省选完挂。但是善良的教练今天丢了一套NOI2011给我们训练 6道题233(虽然一道题做过了,一道题普及组的都会,就算是4道吧)
熬了一天,总算啃掉了四题中的三题 D2T3那个牛逼的博弈论实在不知道怎么做,只好去看看题解233
简单讲一下题解..
D1T1兔农
农夫栋栋近年收入不景气,正在他发愁如何能多赚点钱时,他听到隔壁的小朋友在讨论兔子繁殖的问题。
问题是这样的:第一个月初有一对刚出生的小兔子,经过两个月长大后,这对兔子从第三个月开始,每个月初生一对小兔子。新出生的小兔子生长两个月后又能每个月生出一对小兔子。问第n个月有多少只兔子?
聪明的你可能已经发现,第n个月的兔子数正好是第n个Fibonacci(斐波那契)数。栋栋不懂什么是Fibonacci数,但他也发现了规律:第i+2个月的兔子数等于第i个月的兔子数加上第i+1个月的兔子数。前几个月的兔子数依次为:
1 1 2 3 5 8 13 21 34 …
栋栋发现越到后面兔子数增长的越快,期待养兔子一定能赚大钱,于是栋栋在第一个月初买了一对小兔子开始饲养。
每天,栋栋都要给兔子们喂食,兔子们吃食时非常特别,总是每k对兔子围成一圈,最后剩下的不足k对的围成一圈,由于兔子特别害怕孤独,从第三个月开始,如果吃食时围成某一个圈的只有一对兔子,这对兔子就会很快死掉。
我们假设死去的总是刚出生的兔子,那么每个月的兔子数仍然是可以计算的。例如,当k=7时,前几个月的兔子数依次为:
1 1 2 3 5 7 12 19 31 49 80 …
给定n,你能帮助栋栋计算第n个月他有多少对兔子么?由于答案可能非常大,你只需要告诉栋栋第n个月的兔子对数除p的余数即可。
1s/256MB n<=10^18 k<=10^6 p<=10^9
上来就刚这道题 但是不会.. 仔细想想没有那么复杂
在一个点从1变成0之后,假设这个1之前的数字是x,之后的序列就变成了x,x,2x,3x,恰好是一个斐波那契数列!
这种斐波那契数列不同的显然不超过k个,之后就会出现循环节。推到这里,解法就比较显然了。
先暴力求出斐波那契数列膜k意义下的每个数值最小的出现位置,然后找到第一个1。
之后我们不断拿出1之前的数字,用exgcd求它下一次变成1的时候,也就是它关于k的逆元,那么它下次出现1的位置就是这个逆元在斐波那契数列的第一次的出现位置。
我们对一个数求解的时候给它打一个标记,一旦我们找到了已经求解过的数,说明找到了循环节。
但是我们还要支持-1操作,所以考虑给斐波那契矩阵加一维1.
$\begin{equation}
\left[
\begin{matrix}
fi\\
f_{i-1}\\
1
\end{matrix}
\right]
\end{equation}$*$\begin{equation}
\left[
\begin{matrix}
1&1&0\\
1&0&0\\
0&0&1&
\end{matrix}
\right]
\end{equation}$=$\begin{equation}
\left[
\begin{matrix}
f_{i+1}\\
fi\\
1
\end{matrix}
\right]
\end{equation}$
$\begin{equation}
\left[
\begin{matrix}
fi\\
f_{i-1}\\
1
\end{matrix}
\right]
\end{equation}$*$\begin{equation}
\left[
\begin{matrix}
1&0&-1\\
0&1&0\\
0&0&1&
\end{matrix}
\right]
\end{equation}$=$\begin{equation}
\left[
\begin{matrix}
fi-1\\
f_{i-1}\\
1
\end{matrix}
\right]
\end{equation}$
用这两个矩阵就可以求出循环节的转移矩阵,然后直接快速幂就行啦。
一开始没想到要加一维 后来乱改 应该是我写的最丑的代码之一..
#include<iostream>
#include<cstdio>
#include<cstring>
#include<map>
#define ll long long
#define MN 2000000
using namespace std;
inline ll read()
{
ll x = , f = ; char ch = getchar();
while(ch < '' || ch > ''){ if(ch == '-') f = -; ch = getchar();}
while(ch >= '' && ch <= ''){x = x * + ch - '';ch = getchar();}
return x * f;
}
ll n;
int K,p,f[MN+],mp[];
ll L[];
bool flag=; struct Matrix
{
int s[][];
Matrix(){memset(s,,sizeof(s));}
}A,B,C,D,Mem[]; Matrix Mul(Matrix a,Matrix b,int mod)
{
Matrix c;
for(int i=;i<=;i++)
for(int j=;j<=;j++)
for(int k=;k<=;k++)
c.s[i][j]=(c.s[i][j]+1LL*a.s[i][k]*b.s[k][j])%mod;
return c;
} void Init()
{
memset(C.s,,sizeof(C.s));
C.s[][]=C.s[][]=C.s[][]=;
C.s[][]=;C.s[][]=;
} Matrix MatPow(Matrix C,Matrix D,ll k,int mod)
{
for(;k;k>>=,D=Mul(D,D,mod))
if(k&) C=Mul(C,D,mod);
return C;
} void vio(Matrix&D,ll k,int mod)
{
Init();
for(;k;k>>=,C=Mul(C,C,mod))
if(k&) D=Mul(C,D,mod);
} inline ll exgcd(ll a,ll b,ll&x,ll&y)
{
if(!b){x=;y=;return a;}
ll c=exgcd(b,a%b,x,y);
ll t=x;x=y;y=t-a/b*x;
return c;
} ll getinv(int x,int mod)
{
ll X,Y;
if(!=exgcd(x,mod,X,Y)) return ;
return (X%mod+mod)%mod;
} inline void Dec(Matrix&D)
{
memset(A.s,,sizeof(A.s));
A.s[][]=A.s[][]=A.s[][]=;A.s[][]=p-;
D=Mul(A,D,p);
B.s[][]=;
} Matrix GetSq(int x)
{
Matrix E;int y=x;
E.s[][]=E.s[][]=E.s[][]=;
do
{
ll z=getinv(y,K);z=mp[z];
vio(E,z,p);vio(B,z,K);Dec(E);
y=B.s[][];
}while(y!=x);
return E;
} void Solve(int x)
{
if(!n) return;
if(L[x]) {ll tms=n/(L[x]-n);
if(tms)
{
Matrix T=GetSq(x);
T=MatPow(T,T,tms-,p);
D=MatPow(T,D,,p);
}n%=(L[x]-n);
flag=true;return;}
L[x]=n;Mem[x]=D;ll y;
if(!(y=getinv(x,K))) return;
y=mp[y];if(y>n) return;
n-=y;
vio(D,y,p);vio(B,y,K);
// B.print();
Dec(D);Solve(B.s[][]);
} int main()
{
f[]=f[]=;B.s[][]=B.s[][]=D.s[][]=D.s[][]=B.s[][]=D.s[][]=;
n=read();K=read();p=read();
for(int i=;i<=MN;i++) f[i]=(f[i-]+f[i-])%K;
for(int i=;i<=MN;i++) if(!mp[f[i]]) mp[f[i]]=i;
if(!mp[]||mp[]>n) return vio(D,max(0LL,n-),p),*printf("%d\n",D.s[][]);
vio(D,mp[]-,p);vio(B,mp[]-,K);n-=mp[];Dec(D);
Solve(B.s[][]);
if(flag)
{
memset(L,,sizeof(L));
Solve(B.s[][]);
}
vio(D,n,p);printf("%d\n",D.s[][]);
return ;
}
D1T2 智能车比赛
新一届智能车大赛在JL大学开始啦!比赛赛道可以看作是由n个矩形区域拼接而成(如下图所示),每个矩形的边都平行于坐标轴,第i个矩形区域的左下角和右上角坐标分别为(xi,1,yi,1)和(xi,2,yi,2)。
题目保证:xi,1<xi,2=xi+1,1,且yi,1< yi,2,相邻两个矩形一定有重叠在一起的边(如图中虚线所示),智能车可以通过这部分穿梭于矩形区域之间。
给定速度(意义不明)求起点到终点的最短时间。
1s/256MB n<=2000
发现到达的点一定是矩形的点,然后直接n^2dp就行了 计算几何比较烦
但是我菜,写了建图最短路,差点T了,大家还是学学dp做法吧,比我的优很多...
代码蛮贴一贴
#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<queue>
#define pa pair<double,int>
#define mp(x,y) make_pair(x,y)
#define MN 8005
#define FROM 8002
#define TO 8001
#define eps 1e-7
#define INF 2000000000
using namespace std;
inline int read()
{
int x = , f = ; char ch = getchar();
while(ch < '' || ch > ''){ if(ch == '-') f = -; ch = getchar();}
while(ch >= '' && ch <= ''){x = x * + ch - '';ch = getchar();}
return x * f;
}
inline double sqr(double x){return x*x;}
struct P
{
double x,y;
P(double _x=,double _y=):x(_x),y(_y){}
double operator^(const P&b){return x*b.y-y*b.x;}
P operator-(const P&b){return P(x-b.x,y-b.y);}
friend double dis(P a,P b){return sqrt(sqr(a.x-b.x)+sqr(a.y-b.y));}
}S,T;
int U[MN+],D[MN+],X[MN+];
struct L
{
P p,v;
L(){}
L(P a,P b):p(a),v(b){}
bool Left(P x){return (v^(x-p))>-eps;}
bool Right(P x){return (v^(x-p))<eps;}
};
struct edge{int to,next;double f;}e[MN*+];
double v,d[MN+];
int n,cnt=,head[MN+];
priority_queue<pa,vector<pa>,greater<pa> >q;
inline void ins(int f,int t,double w)
{
// cout<<"ins"<<f<<" "<<t<<" "<<w<<endl;
e[++cnt]=(edge){t,head[f],w};head[f]=cnt;
e[++cnt]=(edge){f,head[t],w};head[t]=cnt;
} void SolveLeft(int R,int from,P x)
{
L up(x,P(,INF)),down(x,P(,-INF));
for(register int i=R;~i;--i)
{
P u=P(X[i],U[i]),d=P(X[i],D[i]);
if(up.Left(u)&&down.Right(u))
ins(from,n++i,dis(u,x));
if(up.Left(d)&&down.Right(d))
ins(from,i,dis(d,x));
if(up.Left(u)) up=L(x,u-x);
if(down.Right(d)) down=L(x,d-x);
}
} void SolveRight(int Le,int from,P x)
{
L up(x,P(,INF)),down(x,P(,-INF));
for(register int i=Le;i<=n;++i)
{
P u=P(X[i],U[i]),d=P(X[i],D[i]);
if(up.Right(u)&&down.Left(u))
ins(from,n++i,dis(u,x));
if(up.Right(d)&&down.Left(d))
ins(from,i,dis(d,x));
if(up.Right(u)) up=L(x,u-x);
if(down.Left(d)) down=L(x,d-x);
}
} void dij()
{
memset(d,,sizeof(d));d[FROM]=;
q.push(mp(,FROM));
while(!q.empty())
{
while(!q.empty()&&q.top().first!=d[q.top().second]) q.pop();
if(q.empty()) break;
int x=q.top().second;q.pop();
for(int i=head[x];i;i=e[i].next)
if(e[i].f+d[x]<d[e[i].to])
{
d[e[i].to]=d[x]+e[i].f;
q.push(mp(d[e[i].to],e[i].to));
}
}
} int main()
{
n=read();
U[]=INF;D[]=-INF;
for(register int i=;i<=n;i++)
{
int x1=read(),y1=read(),x2=read(),y2=read();
X[i-]=x1;X[i]=x2;
U[i-]=min(U[i-],y2);
D[i-]=max(D[i-],y1);
U[i]=y2;D[i]=y1;
}
S.x=read();S.y=read();T.x=read();T.y=read();
for(register int i=;i<=n;i++)
{
if(i) SolveLeft(i-,n++i,P(X[i],U[i])),
SolveLeft(i-,i,P(X[i],D[i]));
if(i<n) SolveRight(i+,n++i,P(X[i],U[i])),
SolveRight(i+,i,P(X[i],D[i]));
}
register int i=,j=;bool flag=;
for(;X[i]<S.x;++i);
for(;X[j]<T.x;++j);
flag&=(i==j);
if(X[i]==S.x)
{
ins(i,FROM,dis(P(X[i],D[i]),S)),ins(i+n+,FROM,dis(S,P(X[i],U[i])));
if(S.y>=D[i]&&S.y<=U[i]&&i>)
if(i>)SolveLeft(i-,FROM,S);
}
else if(i>)SolveLeft(i-,FROM,S);
for(;X[i]==S.x;++i);if(i<n)SolveRight(i,FROM,S);
if(X[j]==T.x)
{
ins(j,TO,dis(T,P(X[j],D[j]))),ins(j+n+,TO,dis(T,P(X[j],U[j])));
if(T.y>=D[j]&&T.y<=U[j]&&j>)
if(j>)SolveLeft(j-,TO,T);
}
else if(j>)SolveLeft(j-,TO,T);
for(;X[j]==T.x;++j);if(j<n)SolveRight(j,TO,T);
dij();double v;scanf("%lf",&v);
if(i==j&&flag) d[TO]=min(d[TO],dis(S,T));
printf("%.10lf\n",d[TO]/v);
return ;
}
D1T3阿狸的打字机
以前做的题目 戳这里
D2T1道路修建
在 W 星球上有 n 个国家。为了各自国家的经济发展,他们决定在各个国家 之间建设双向道路使得国家之间连通。但是每个国家的国王都很吝啬,他们只愿 意修建恰好 n – 1 条双向道路。 每条道路的修建都要付出一定的费用,这个费用等于道路长度乘以道路两端 的国家个数之差的绝对值。例如,在下图中,虚线所示道路两端分别有 2 个、4 个国家,如果该道路长度为 1,则费用为 1×|2 – 4|=2。图中圆圈里的数字表示国 家的编号。
由于国家的数量十分庞大,道路的建造方案有很多种,同时每种方案的修建 费用难以用人工计算,国王们决定找人设计一个软件,对于给定的建造方案,计 算出所需要的费用。请你帮助国王们设计一个这样的软件。
n<=1000000 1s/256MB
普及组水平的题目...
#include<iostream>
#include<cstdio>
#define MN 1000000
#define ll long long
using namespace std;
inline int read()
{
int x = , f = ; char ch = getchar();
while(ch < '' || ch > ''){ if(ch == '-') f = -; ch = getchar();}
while(ch >= '' && ch <= ''){x = x * + ch - '';ch = getchar();}
return x * f;
} int n,head[MN+],cnt=,size[MN+];
struct edge{int to,next,w;}e[MN*+];
ll ans=;
inline void ins(int f,int t,int w){e[++cnt]=(edge){t,head[f],w};head[f]=cnt;}
inline int abs(int x){return x<?-x:x;}
void dfs(int x,int f)
{
size[x]=;
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=f)
{
dfs(e[i].to,x);
size[x]+=size[e[i].to];
ans+=1LL*e[i].w*abs(size[e[i].to]-(n-size[e[i].to]));
}
} int main()
{
n=read();
for(int i=;i<n;i++)
{
int x=read(),y=read(),w=read();
ins(x,y,w);ins(y,x,w);
}
dfs(,);
printf("%lld\n",ans);
return ;
}
D2T2 Noi嘉年华
NOI2011 在吉林大学开始啦!为了迎接来自全国各地最优秀的信息学选手,吉林大学决定举办两场盛大的 NOI 嘉年华活动,分在两个不同的地点举办。每个嘉年华可能包含很多个活动,而每个活动只能在一个嘉年华中举办。
现在嘉年华活动的组织者小安一共收到了 n个活动的举办申请,其中第 i 个活动的起始时间为 Si,活动的持续时间为Ti。这些活动都可以安排到任意一个嘉年华的会场,也可以不安排。
小安通过广泛的调查发现,如果某个时刻,两个嘉年华会场同时有活动在进行(不包括活动的开始瞬间和结束瞬间),那么有的选手就会纠结于到底去哪个会场,从而变得不开心。所以,为了避免这样不开心的事情发生,小安要求不能有两个活动在两个会场同时进行(同一会场内的活动可以任意进行)。
另外,可以想象,如果某一个嘉年华会场的活动太少,那么这个嘉年华的吸引力就会不足,容易导致场面冷清。所以小安希望通过合理的安排,使得活动相对较少的嘉年华的活动数量最大。
此外,有一些活动非常有意义,小安希望能举办,他希望知道,如果第i 个活动必须举办(可以安排在两场嘉年华中的任何一个),活动相对较少的嘉年华的活动数量的最大值。
n<=200 1s/256MB
这道题看起来很可做 但是啃了很久做不出来 还好yy出了一个比较靠谱的dp方程,总算过了 差点忍不住去看题解了233
离散时间点,然后f[i][j]表示前i个时间点其中一个嘉年华举办j场活动,另一场最多举办几场,转移比较显然。 g[i][j]表示后.....
令h[i][j]表示时间点i到j的区间最多能办几场活动,那么对于每一个时间段x-y,答案是Ans[x][y]=max(min(f[x-1][i]+h[x][y]+g[y+1][k-i],k)) 后面那一串显然可以二分。
所以处理完Ans数组之后,每个询问只要去查一个最大值就行了。复杂度n^3logn
貌似可以借助单调性做到n^3 但是莫名没有我跑的快...
#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#define MN 400
#define INF 1000000000
using namespace std;
inline int read()
{
int x = , f = ; char ch = getchar();
while(ch < '' || ch > ''){ if(ch == '-') f = -; ch = getchar();}
while(ch >= '' && ch <= ''){x = x * + ch - '';ch = getchar();}
return x * f;
} int f[MN+][MN+],g[MN+][MN+],h[MN+][MN+],p[MN+],Ans[MN+][MN+],tot=,n,m=;
struct eve{int b,e;}e[MN+]; int main()
{
n=read();
for(int i=;i<=n;i++)
{
e[i].b=read();
e[i].e=e[i].b+read()-;
p[++tot]=e[i].b;
p[++tot]=e[i].e;
}
sort(p+,p+tot+);
for(int i=;i<=tot;i++)
if(p[i]!=p[i-]) p[++m]=p[i];
for(int i=;i<=n;i++)
{
e[i].b=lower_bound(p+,p+m+,e[i].b)-p;
e[i].e=lower_bound(p+,p+m+,e[i].e)-p;
h[e[i].b][e[i].e]++;
}
for(int len=;len<=m;len++)
for(int i=;i+len-<=m;i++)
{
int j=i+len-;
h[i][j]=h[i][j]+h[i+][j]+h[i][j-]-h[i+][j-];
}
for(int i=;i<=m+;i++)
for(int j=;j<=n;j++) f[i][j]=g[i][j]=-INF;
f[][]=g[m+][]=;
for(int i=;i<=m;i++)
for(int k=;k<=n;k++)
{
for(int j=;j<i;j++)
if(h[j+][i]<=k)
f[i][k]=max(f[i][k],f[j][k-h[j+][i]]);
f[i][k]=max(f[i][k],f[i-][k]);
if(f[i][k]>=) f[i][f[i][k]]=max(f[i][f[i][k]],k);
}
for(int i=m;i;--i)
for(int k=;k<=n;k++)
{
for(int j=i+;j<=m+;j++)
if(h[i][j-]<=k)
g[i][k]=max(g[i][k],g[j][k-h[i][j-]]);
g[i][k]=max(g[i][k],g[i+][k]);
if(g[i][k]>=) g[i][g[i][k]]=max(k,g[i][g[i][k]]);
}
int ans=;
for(int i=;i<=n;i++)
ans=max(ans,min(i,f[m][i]));
printf("%d\n",ans);
for(int i=;i<=m;i++)
for(int j=i+;j<=m+;j++)
{
int l=,r=n,ans=,mid;
while(l<=r)
{
mid=l+r>>;bool flag=false;
for(int k=;k<=mid;k++)
if(f[i][k]+g[j][mid-k]+h[i+][j-]>=mid)
{
flag=true;
break;
}
if(flag) ans=mid,l=mid+;
else r=mid-;
}
Ans[i][j]=ans;
}
for(int i=;i<=n;i++)
{
int ans=;
for(int j=;j<e[i].b;j++)
for(int k=e[i].e+;k<=m+;k++)
ans=max(ans,Ans[j][k]);
printf("%d\n",ans);
}
return ;
}
D2T3
这些天,兔兔和蛋蛋喜欢上了一种新的棋类游戏。 这个游戏是在一个n行m列的棋盘上进行的。游戏开始之前,棋盘上有一个格子是空的,其它的格子中都放置了一枚棋子,棋子或者是黑色,或者是白色。 每一局游戏总是兔兔先操作,之后双方轮流操作,具体操作为:
兔兔每次操作时,选择一枚与空格相邻的白色棋子,将它移进空格。
蛋蛋每次操作时,选择一枚与空格相邻的黑色棋子,将它移进空格。
第一个不能按照规则操作的人输掉游戏。为了描述方便,下面将操作“将第x行第y列中的棋子移进空格中”记为M(x,y)。 例如下面是三个游戏的例子。
最近兔兔总是输掉游戏,而且蛋蛋格外嚣张,于是兔兔想请她的好朋友——你——来帮助她。她带来了一局输给蛋蛋的游戏的实录,请你指出这一局游戏中所有她“犯错误”的地方。 注意:
两个格子相邻当且仅当它们有一条公共边。
兔兔的操作是“犯错误”的,当且仅当,在这次操作前兔兔有必胜策略,而这次操作后蛋蛋有必胜策略。
丢个链接:这里有很详细的题解
#include<cstdio>
#include<iostream>
#define MN 1600
#define MC 40
using namespace std;
inline int read()
{
int x=;char ch=getchar();
while(ch<''||ch>'')ch=getchar();
while(ch>=''&&ch<='')x=x*+ch-'',ch=getchar();
return x;
}
int n,m,head[MN+],cnt=,match[MN+],mark[MN+],fx,fy,s[MC+][MC+],tot=,cn=,Ans[MN+];
struct edge{int to,next;}e[MN*+];
char st[MC+][MC+];
bool ban[MN+];
const int dis[][]={{,},{-,},{,},{,-}};
inline void ins(int f,int t){e[++cnt]=(edge){t,head[f]};head[f]=cnt;}
inline int num(int x,int y){return (x-)*m+y;} bool Dfs(int x)
{
for(int i=head[x];i;i=e[i].next)
if(mark[e[i].to]!=tot&&!ban[e[i].to])
{
mark[e[i].to]=tot;
if(!match[e[i].to]||Dfs(match[e[i].to]))
{
match[x]=e[i].to;
match[e[i].to]=x;
return true;
}
}
return false;
} int calc(int x)
{
ban[x]=;
if(!match[x])return ;
int rt=match[x];
match[rt]=match[x]=;
return ++tot,!Dfs(rt);
} int main()
{
n=read();m=read();
for(int i=;i<=n;i++) scanf("%s",st[i]+);
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
if(st[i][j]=='.') fx=i,fy=j;
else s[i][j]=st[i][j]=='X'?:;
for(int i=;i<=n;i++)
for(int j=;j<=m;j++)
for(int k=;k<;k++)
{
int x=i+dis[k][],y=j+dis[k][];
if(x<||y<||x>n||y>m||s[i][j]==s[x][y]) continue;
ins(num(i,j),num(x,y));
}
for(int i=;i<=n*m;++i) if(!match[i])++tot,Dfs(i);
int q=read();
for(int i=;i<=q;++i)
{
int res1=calc(num(fx,fy));
fx=read(),fy=read();
int res2=calc(num(fx,fy));
if(res1&&res2) Ans[++cn]=i;
fx=read();fy=read();
}
cout<<cn<<endl;
for(int i=;i<=cn;i++)printf("%d\n",Ans[i]);
return ;
}