来自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.
$egin{equation}
left[
egin{matrix}
fi\
f_{i-1}\
1
end{matrix}
ight]
end{equation}$*$egin{equation}
left[
egin{matrix}
1&1&0\
1&0&0\
0&0&1&
end{matrix}
ight]
end{equation}$=$egin{equation}
left[
egin{matrix}
f_{i+1}\
fi\
1
end{matrix}
ight]
end{equation}$
$egin{equation}
left[
egin{matrix}
fi\
f_{i-1}\
1
end{matrix}
ight]
end{equation}$*$egin{equation}
left[
egin{matrix}
1&0&-1\
0&1&0\
0&0&1&
end{matrix}
ight]
end{equation}$=$egin{equation}
left[
egin{matrix}
fi-1\
f_{i-1}\
1
end{matrix}
ight]
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 = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } ll n; int K,p,f[MN+5],mp[1000005]; ll L[1000005]; bool flag=0; struct Matrix { int s[4][4]; Matrix(){memset(s,0,sizeof(s));} }A,B,C,D,Mem[1000005]; Matrix Mul(Matrix a,Matrix b,int mod) { Matrix c; for(int i=1;i<=3;i++) for(int j=1;j<=3;j++) for(int k=1;k<=3;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,0,sizeof(C.s)); C.s[1][1]=C.s[1][2]=C.s[2][1]=1; C.s[2][2]=0;C.s[3][3]=1; } Matrix MatPow(Matrix C,Matrix D,ll k,int mod) { for(;k;k>>=1,D=Mul(D,D,mod)) if(k&1) C=Mul(C,D,mod); return C; } void vio(Matrix&D,ll k,int mod) { Init(); for(;k;k>>=1,C=Mul(C,C,mod)) if(k&1) D=Mul(C,D,mod); } inline ll exgcd(ll a,ll b,ll&x,ll&y) { if(!b){x=1;y=0;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(1!=exgcd(x,mod,X,Y)) return 0; return (X%mod+mod)%mod; } inline void Dec(Matrix&D) { memset(A.s,0,sizeof(A.s)); A.s[1][1]=A.s[2][2]=A.s[3][3]=1;A.s[1][3]=p-1; D=Mul(A,D,p); B.s[1][1]=0; } Matrix GetSq(int x) { Matrix E;int y=x; E.s[1][1]=E.s[2][2]=E.s[3][3]=1; do { ll z=getinv(y,K);z=mp[z]; vio(E,z,p);vio(B,z,K);Dec(E); y=B.s[2][1]; }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-1,p); D=MatPow(T,D,1,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[2][1]); } int main() { f[1]=f[2]=1;B.s[1][1]=B.s[2][1]=D.s[1][1]=D.s[2][1]=B.s[3][1]=D.s[3][1]=1; n=read();K=read();p=read(); for(int i=3;i<=MN;i++) f[i]=(f[i-1]+f[i-2])%K; for(int i=3;i<=MN;i++) if(!mp[f[i]]) mp[f[i]]=i; if(!mp[1]||mp[1]>n) return vio(D,max(0LL,n-2),p),0*printf("%d ",D.s[1][1]); vio(D,mp[1]-2,p);vio(B,mp[1]-2,K);n-=mp[1];Dec(D); Solve(B.s[2][1]); if(flag) { memset(L,0,sizeof(L)); Solve(B.s[2][1]); } vio(D,n,p);printf("%d ",D.s[1][1]); return 0; }
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 = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } inline double sqr(double x){return x*x;} struct P { double x,y; P(double _x=0,double _y=0):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+5],D[MN+5],X[MN+5]; 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*100+5]; double v,d[MN+5]; int n,cnt=0,head[MN+5]; 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(0,INF)),down(x,P(0,-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+1+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(0,INF)),down(x,P(0,-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+1+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,127,sizeof(d));d[FROM]=0; q.push(mp(0,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[0]=INF;D[0]=-INF; for(register int i=1;i<=n;i++) { int x1=read(),y1=read(),x2=read(),y2=read(); X[i-1]=x1;X[i]=x2; U[i-1]=min(U[i-1],y2); D[i-1]=max(D[i-1],y1); U[i]=y2;D[i]=y1; } S.x=read();S.y=read();T.x=read();T.y=read(); for(register int i=0;i<=n;i++) { if(i) SolveLeft(i-1,n+1+i,P(X[i],U[i])), SolveLeft(i-1,i,P(X[i],D[i])); if(i<n) SolveRight(i+1,n+1+i,P(X[i],U[i])), SolveRight(i+1,i,P(X[i],D[i])); } register int i=0,j=0;bool flag=1; 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+1,FROM,dis(S,P(X[i],U[i]))); if(S.y>=D[i]&&S.y<=U[i]&&i>0) if(i>0)SolveLeft(i-1,FROM,S); } else if(i>0)SolveLeft(i-1,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+1,TO,dis(T,P(X[j],U[j]))); if(T.y>=D[j]&&T.y<=U[j]&&j>0) if(j>0)SolveLeft(j-1,TO,T); } else if(j>0)SolveLeft(j-1,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 ",d[TO]/v); return 0; }
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 = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int n,head[MN+5],cnt=0,size[MN+5]; struct edge{int to,next,w;}e[MN*2+5]; ll ans=0; 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<0?-x:x;} void dfs(int x,int f) { size[x]=1; 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=1;i<n;i++) { int x=read(),y=read(),w=read(); ins(x,y,w);ins(y,x,w); } dfs(1,0); printf("%lld ",ans); return 0; }
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 = 0 , f = 1; char ch = getchar(); while(ch < '0' || ch > '9'){ if(ch == '-') f = -1; ch = getchar();} while(ch >= '0' && ch <= '9'){x = x * 10 + ch - '0';ch = getchar();} return x * f; } int f[MN+5][MN+5],g[MN+5][MN+5],h[MN+5][MN+5],p[MN+5],Ans[MN+5][MN+5],tot=0,n,m=1; struct eve{int b,e;}e[MN+5]; int main() { n=read(); for(int i=1;i<=n;i++) { e[i].b=read(); e[i].e=e[i].b+read()-1; p[++tot]=e[i].b; p[++tot]=e[i].e; } sort(p+1,p+tot+1); for(int i=2;i<=tot;i++) if(p[i]!=p[i-1]) p[++m]=p[i]; for(int i=1;i<=n;i++) { e[i].b=lower_bound(p+1,p+m+1,e[i].b)-p; e[i].e=lower_bound(p+1,p+m+1,e[i].e)-p; h[e[i].b][e[i].e]++; } for(int len=2;len<=m;len++) for(int i=1;i+len-1<=m;i++) { int j=i+len-1; h[i][j]=h[i][j]+h[i+1][j]+h[i][j-1]-h[i+1][j-1]; } for(int i=0;i<=m+1;i++) for(int j=0;j<=n;j++) f[i][j]=g[i][j]=-INF; f[0][0]=g[m+1][0]=0; for(int i=1;i<=m;i++) for(int k=0;k<=n;k++) { for(int j=0;j<i;j++) if(h[j+1][i]<=k) f[i][k]=max(f[i][k],f[j][k-h[j+1][i]]); f[i][k]=max(f[i][k],f[i-1][k]); if(f[i][k]>=0) f[i][f[i][k]]=max(f[i][f[i][k]],k); } for(int i=m;i;--i) for(int k=0;k<=n;k++) { for(int j=i+1;j<=m+1;j++) if(h[i][j-1]<=k) g[i][k]=max(g[i][k],g[j][k-h[i][j-1]]); g[i][k]=max(g[i][k],g[i+1][k]); if(g[i][k]>=0) g[i][g[i][k]]=max(k,g[i][g[i][k]]); } int ans=0; for(int i=1;i<=n;i++) ans=max(ans,min(i,f[m][i])); printf("%d ",ans); for(int i=0;i<=m;i++) for(int j=i+1;j<=m+1;j++) { int l=1,r=n,ans=0,mid; while(l<=r) { mid=l+r>>1;bool flag=false; for(int k=0;k<=mid;k++) if(f[i][k]+g[j][mid-k]+h[i+1][j-1]>=mid) { flag=true; break; } if(flag) ans=mid,l=mid+1; else r=mid-1; } Ans[i][j]=ans; } for(int i=1;i<=n;i++) { int ans=0; for(int j=0;j<e[i].b;j++) for(int k=e[i].e+1;k<=m+1;k++) ans=max(ans,Ans[j][k]); printf("%d ",ans); } return 0; }
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=0;char ch=getchar(); while(ch<'0'||ch>'9')ch=getchar(); while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar(); return x; } int n,m,head[MN+5],cnt=0,match[MN+5],mark[MN+5],fx,fy,s[MC+5][MC+5],tot=0,cn=0,Ans[MN+5]; struct edge{int to,next;}e[MN*4+5]; char st[MC+5][MC+5]; bool ban[MN+5]; const int dis[4][2]={{1,0},{-1,0},{0,1},{0,-1}}; 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-1)*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]=1; if(!match[x])return 0; int rt=match[x]; match[rt]=match[x]=0; return ++tot,!Dfs(rt); } int main() { n=read();m=read(); for(int i=1;i<=n;i++) scanf("%s",st[i]+1); for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) if(st[i][j]=='.') fx=i,fy=j; else s[i][j]=st[i][j]=='X'?0:1; for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) for(int k=0;k<4;k++) { int x=i+dis[k][0],y=j+dis[k][1]; if(x<1||y<1||x>n||y>m||s[i][j]==s[x][y]) continue; ins(num(i,j),num(x,y)); } for(int i=1;i<=n*m;++i) if(!match[i])++tot,Dfs(i); int q=read(); for(int i=1;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=1;i<=cn;i++)printf("%d ",Ans[i]); return 0; }