[NOI2012]

时间:2021-05-29 19:21:07

来自FallDream的博客,未经允许,请勿转载,谢谢。


一天一套noi 简直了....

昨天勉强做完了noi2011 今天教练又丢出来一套noi2012  去掉提答还有5题 勉强做了3题  先占个坑 剩下的明天补补吧

Upd : 终于补完了  2012什么丧题啊

D1T1 随机数生成器

栋栋最近迷上了随机算法,而随机数是生成随机算法的基础。栋栋准备使用线性同余法(Linear Congruential Method)来生成一个随机数列,这种方法需要设置四个非负整数参数m,a,c,X[0],按照下面的公式生成出一系列随机数{X[n]}:X[n+1]=(aX[n]+c)mod m其中mod m表示前面的数除以m的余数。从这个式子可以看出,这个序列的下一个数总是由上一个数生成的。用这种方法生成的序列具有随机序列的性质,因此这种方法被广泛地使用,包括常用的C++和Pascal的产生随机数的库函数使用的也是这种方法。栋栋知道这样产生的序列具有良好的随机性,不过心急的他仍然想尽快知道X[n]是多少。由于栋栋需要的随机数是0,1,...,g-1之间的,他需要将X[n]除以g取余得到他想要的数,即X[n] mod g,你只需要告诉栋栋他想要的数X[n] mod g是多少就可以了。

NOI的签到题? 题解戳这里

D1T2 骑行川藏

蛋蛋非常热衷于挑战自我,今年暑假他准备沿川藏线骑着自行车从成都前往拉萨。川藏线的沿途有着非常美丽的风景,但在这一路上也有着很多的艰难险阻,路况变化多端,而蛋蛋的体力十分有限,因此在每天的骑行前设定好目的地、同时合理分配好自己的体力是一件非常重要的事情。
由于蛋蛋装备了一辆非常好的自行车,因此在骑行过程中可以认为他仅在克服风阻做功(不受自行车本身摩擦力以及自行车与地面的摩擦力影响)。某一天他打算骑N段路,每一段内的路况可视为相同:对于第i段路,我们给出有关这段路况的3个参数 si , ki , vi' ,其中 si 表示这段路的长度, ki 表示这段路的风阻系数, vi' 表示这段路上的风速(表示在这段路上他遇到了顺风,反之则意味着他将受逆风影响)。若某一时刻在这段路上骑车速度为v,则他受到的风阻大小为 F = ki ( v - vi' )2(这样若在长度为s的路程内保持骑行速度v不变,则他消耗能量(做功)E = ki ( v - vi' )2 s)。
设蛋蛋在这天开始时的体能值是 Eu ,请帮助他设计一种行车方案,使他在有限的体力内用最短的时间到达目的地。请告诉他最短的时间T是多少。

N <= 10000,0 <= Eu <= 108,0 < si <= 100000,0 < ki <= 1,-100 < vi' < 100。数据保证最终的答案不会超过105。

题意即求最小的$T=\sum{\frac{si}{vi}}$,并且满足$\sum{kisi(vi-vi')^{2}}<=E$

后面这个不等式可以直接看作等于E,所以按照拉格朗日乘数法带进去 得到

$f(v1,v2,..,vn)=\sum{\frac{si}{vi}},\varphi(v1,v2,...,vn)=\sum{kisi(vi-v)^{2}}$

$\sum{\frac{si}{vi}}+\sum{\lambda kisi(vi-v)^{2}}-\lambda E=0$

对于每个v分别求偏导,得到n+1个式子

$-\frac{si}{vi^{2}}=2\lambda kisi(v-vi')$

$\varphi(...)=E$

发现$\varphi$的值随着$\lambda$变大而减小 所以考虑二分$\lambda$ 解前n个方程

又因为$\varphi$函数的每一项在大等于vi'的时候单调递增,而vi一定大于vi',所以每个vi都可以二分求得 最后算出g的值和E比较就行了

精度问题还是挺难受的 二分次数太多就T 太少就wa

#include<iostream>
#include<cstdio>
#define MN 10000
#define INF 50000000
using namespace std;
int n;
double k[MN+],s[MN+],v[MN+],E,V[MN+]; double Check(double lam)
{
double sum=;
for(register int i=;i<=n;i++)
{
double l=v[i],r=INF,mid;
for(register int tms=;tms<=;++tms)
{
mid=(l+r)/2.0;
double now=*lam*k[i]*mid*mid*(mid-v[i]);
if(now>) r=mid;
else l=mid;
}
V[i]=(l+r)/2.0;
sum+=k[i]*s[i]*(V[i]-v[i])*(V[i]-v[i]);
}
return sum;
} int main()
{
scanf("%d%lf",&n,&E);
for(register int i=;i<=n;i++)
scanf("%lf%lf%lf",&s[i],&k[i],&v[i]);
double l=-INF,r=INF,mid;
for(register int tms=;tms<=;++tms)
{
mid=(l+r)/2.0;
if(Check(mid)>E) l=mid;
else r=mid;
}
double ans=;
for(register int i=;i<=n;i++) ans+=(double)s[i]/V[i];
printf("%0.8lf",ans);
return ;
}

D1T3魔幻棋盘

 将要读二年级的小Q买了一款新型益智玩具——魔幻棋盘,它是一个N 行 M 列的网格棋盘,每个格子中均有一个正整数。棋盘守护者在棋盘的第 X行第 Y列(行与列均从 1 开始编号)并且始终不会移动。棋盘守护者会进行两种操作:
(a)询问:他会以自己所在位置为基础,向四周随机扩展出一块大小不定的矩形区域,向你询问这一区域内所有数的最大公约数是多少。  
(b)修改:他会随意挑选棋盘上的一块矩形区域,将这一区域内的所有数同时加上一个给定的整数。  
    游戏说明书上附有这样一句话“聪明的小朋友,当你连续答对 19930324 次询问后会得到一个惊喜噢!”。小Q十分想得到这个惊喜,于是每天都在玩这个玩具。但由于他粗心大意,经常算错数,难以达到这个目标。于是他来向你寻求帮助,希望你帮他写一个程序来回答棋盘守护者的询问,并保证 100% 的正确率。  为了简化问题,你的程序只需要完成棋盘守护者的T次操作,并且问题保证任何时刻棋盘上的数字均为不超过 2^62-1 的正整数。
n*m<=500000 T<=100000   5s/512MB
 
考虑用二维线段树维护
gcd不能打标记实现修改 但是gcd(a,b,c,...)=gcd(a,b-a,c-b...)  所以考虑维护差分数组。
由于每一次询问都一定包含那个固定点,所以考虑以它为中心切成4块(切法比较随意),分别维护。
这样区间修改就变成了单点修改,查询直接线段树上查 最后和中间这个点的值求gcd就行了。
算上gcd 复杂度是Tlog^3n 
这还是我第一次写二维线段树  还算写了一个能看的..
#include<iostream>
#include<cstdio>
#include<vector>
#define MN 500000
#define ll long long
#define getchar() (*S++)
#define Rint register int
#define Inc(q,f,t) for(q=f;q<=t;++q)
#define Dec(q,f,t) for(q=f;q>=t;--q)
char B[<<],*S=B;
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 C;
int n,m,sx,sy,Q,Left,Right;ll *s[MN+];
inline ll myabs(ll x){return x<?-x:x;}
inline ll gcd(ll x,ll y){x=myabs(x);y=myabs(y);return !y?x:gcd(y,x%y);}
struct Tree
{
Tree *l,*r;ll*T;int N,Len;
Tree(int L,int R,int Beg)
{
if(L>R||Left>Right) return;
for(N=;N<=Right-Left+;N<<=);
T=new ll[N*+];Len=Right-Left+;
if(L==R){
for(int i=;i<=Right-Left+;i++)T[i+N]=s[L+Beg][Left+i-];
for(int i=N;i;--i) T[i]=gcd(T[i<<],T[i<<|]);
return;
}
int mid=L+R>>;
l=new Tree(L,mid,Beg);r=new Tree(mid+,R,Beg);
for(int i=;i<=N*;i++) T[i]=gcd(l->T[i],r->T[i]);
}
void Modify(int x,int y,int lt,int rt,ll ad)
{
if(x<||y<||x>rt||y>Len) return;
if(lt==rt)
{
for(T[y+=N]+=ad,y>>=;y;y>>=)
T[y]=gcd(T[y<<],T[y<<|]);
return;
}
int mid=lt+rt>>;
if(x<=mid) l->Modify(x,y,lt,mid,ad);
else r->Modify(x,y,mid+,rt,ad);
for(y+=N;y;y>>=)
T[y]=gcd(l->T[y],r->T[y]);
}
ll query(int L,int R,int lt,int rt,int f,int y)
{
if(L>R||!R||lt>rt||!y||f>y) return ;
if(L==lt&&R==rt)
{
ll ans=;y=min(y,Len);
for(L=f+N-,R=y+N+;L^R^;L>>=,R>>=)
{
if(~L&)ans=gcd(ans,T[L+]);
if( R&)ans=gcd(ans,T[R-]);
}
return ans;
}
int mid=lt+rt>>;
if(R<=mid) return l->query(L,R,lt,mid,f,y);
else if(L>mid) return r->query(L,R,mid+,rt,f,y);
else return gcd(l->query(L,mid,lt,mid,f,y),r->query(mid+,R,mid+,rt,f,y));
}
}*LU,*LD,*RU,*RD; void Solve(Tree*Th,int x0,int y0,int X1,int Y1,int lt,int rt,ll val)
{
if(Y1<=||X1<=||lt>rt) return;
x0=max(x0,);y0=max(y0,);
++X1;++Y1;
Th->Modify(X1,Y1,lt,rt,val);
Th->Modify(X1,y0,lt,rt,-val);
Th->Modify(x0,Y1,lt,rt,-val);
Th->Modify(x0,y0,lt,rt,val);
} int main()
{
fread(B,,<<,stdin);Rint i,j,k;
n=read();m=read();sx=read();sy=read();Q=read();
Inc(i,,n)
{
s[i]=new ll[m+];
for(Rint j=;j<=m;j++)
s[i][j]=read();
}
s[n+]=new ll[m+];s[]=new ll[m+];C=s[sx][sy];
Dec(i,sx,) Inc(j,,sy-) s[i][j]=s[i][j]-s[i][j+];
Inc(i,,sx-) Inc(j,,sy-) s[i][j]=s[i][j]-s[i+][j];
Inc(i,,sx-) Dec(j,m,sy+) s[i][j]=s[i][j]-s[i][j-];
Inc(i,,sx-) Inc(j,sy,m) s[i][j]-=s[i+][j];
Dec(i,n,sx+) Inc(j,,sy) s[i][j]-=s[i-][j];
Inc(i,sx+,n) Inc(j,,sy-) s[i][j]-=s[i][j+];
Dec(i,n,sx+) Inc(j,sy+,m) s[i][j]-=s[i-][j];
Inc(i,sx,n) Dec(j,m,sy+) s[i][j]-=s[i][j-];
for(i=,k=sx;i<=k;i++,--k)
for(Rint j=;j<=sy-;j++)
swap(s[i][j],s[k][j]);
for(i=;i<=sx;i++)
for(j=,k=sy-;j<k;++j,--k)
swap(s[i][j],s[i][k]);
for(Rint i=sx+;i<=n;i++)
for(Rint j=,l=sy;j<l;++j,--l)
swap(s[i][j],s[i][l]);
for(Rint i=,k=sx-;i<k;++i,--k)
for(Rint j=sy;j<=m;j++)
swap(s[i][j],s[k][j]);
Left=;Right=sy-;LU=new Tree(,sx,);
Left=;Right=sy; LD=new Tree(,n-sx,sx);
Left=sy,Right=m; RU=new Tree(,sx-,);
Left=sy+;Right=m;RD=new Tree(,n-sx+,sx-);
for(Rint i=;i<=Q;i++)
{
if(read())
{
int X1=read(),Y1=read(),X2=read(),Y2=read();ll c=read();
Solve(LU,sx-X2+,sy-Y2,sx-X1+,sy-Y1,,sx,c);
Solve(LD,X1-sx,sy-Y2+,X2-sx,sy-Y1+,,n-sx,c);
Solve(RU,sx-X2,Y1-sy+,sx-X1,Y2-sy+,,sx-,c);
Solve(RD,X1-sx+,Y1-sy,X2-sx+,Y2-sy,,n-sx+,c);
if(X1<=sx&&sx<=X2&&Y1<=sy&&sy<=Y2) C+=c;
}
else
{
int X1=read(),Y1=read(),X2=read(),Y2=read();ll ans=C;
ans=gcd(ans,LU->query(,X1+,,sx,,Y1));
ans=gcd(ans,LD->query(,X2,,n-sx,,Y1+));
ans=gcd(ans,RU->query(,X1,,sx-,,Y2+));
ans=gcd(ans,RD->query(,X2+,,n-sx+,,Y2));
printf("%lld\n",ans);
}
}
return ;
}

D2T1 迷失游乐园

放假了,小Z觉得呆在家里特别无聊,于是决定一个人去游乐园玩。进入游乐园后,小Z看了看游乐园的地图,发现可以将游乐园抽象成有n个景点、m条道路的无向连通图,且该图中至多有一个环(即m只可能等于n或者n-1)。小Z现在所在的大门也正好是一个景点。小Z不知道什么好玩,于是他决定,从当前位置出发,每次随机去一个和当前景点有道路相连的景点,并且同一个景点不去两次(包括起始景点)。贪玩的小Z会一直游玩,直到当前景点的相邻景点都已经访问过为止。小Z所有经过的景点按顺序构成一条非重复路径,他想知道这条路径的期望长度是多少?小Z把游乐园的抽象地图画下来带回了家,可是忘了标哪个点是大门,他只好假设每个景点都可能是大门(即每个景点作为起始点的概率是一样的)。同时,他每次在选择下一个景点时会等概率地随机选择一个还没去过的相邻景点

n<=100000 环中节点不超过20个

对于50%的数据,是一棵树,考虑树形dp

用g[i]表示往子树走的期望,f[i]表示从点i向子树所有点走的期望之和,in[i]表示点i的度数,那么g[i]=f[i]/(in[i]-1)

那么树形dp的时候,$f[i]=\sum{g[v]+w_{iv}}$ g[i]可以也直接算出来

然后求完g数组之后考虑往上走的部分 令u[i]表示往上走的期望,p[i]表示从点i出发的期望,p[i]=g[i]+u[i],fa是i的父亲

那么$u[i]=\left(p[fa]-g[i]-e_{fa,i}\right)/(in[fa]-1)+e_{fa,i}$

这样两遍dfs之后就能求出期望啦。

对于100%的数据 基环外向树

基环外向树,环上节点不多,考虑拆环之后树形dp。

先找出环之后,用同样的方法求出外向树的g和f,但是不能直接算u,所以考虑枚举环上节点

从每个点出发向两个方向dfs,用与算g相同的方法求出每个点出发在环上走的期望长度,累加到f里面。

最后,还是相同的方法求出p就行了。

#include<iostream>
#include<cstdio>
#define MN 100000
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;
} double f[MN+],g[MN+],h[MN+],ans=,H[MN+];
int head[MN+],n,m,cnt=,size[MN*+],in[MN+],num[MN+],w[MN+],q[MN+],top=,len,C[],cn=;
struct edge{int to,next,w;}e[MN*+];
bool mark[MN+],inq[MN+],flag=;
inline void ins(int f,int t,int w){e[++cnt]=(edge){t,head[f],w};++in[t];head[f]=cnt;} void Solve(int x,int fa)
{
int son=;
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa&&!mark[e[i].to])
{
Solve(e[i].to,x);++son;
f[x]+=e[i].w+g[e[i].to];
}
if(son)g[x]=f[x]/son;son+=(fa>);
} void solve(int x,int fa,int last)
{
if(!fa) h[x]=f[x]; else h[x]=(h[fa]-g[x]-last)/max(,in[fa]-)+last+f[x];
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa&&!mark[e[i].to]) solve(e[i].to,x,e[i].w);
h[x]/=in[x];
} void FindCir(int x,int fa)
{
inq[x]=;q[++top]=x;
for(int i=head[x];i&&!flag;i=e[i].next)
if(e[i].to!=fa&&!mark[e[i].to])
{
if(!inq[e[i].to]) w[e[i].to]=w[x]+e[i].w,FindCir(e[i].to,x);
else if(!flag)
{
len=e[i].w+w[x]-w[e[i].to];
for(;q[top+]!=e[i].to;--top)
mark[C[++cn]=q[top]]=;
}
}
inq[x]=;--top;
} void Cal(int x,int fa,int rt)
{
H[x]=;int son=;
for(int i=head[x];i;i=e[i].next)
if(e[i].to!=fa&&mark[e[i].to]&&e[i].to!=rt)
{
++son;Cal(e[i].to,x,rt);
H[x]+=H[e[i].to]+e[i].w;
}
if(!son) {H[x]=(f[x]/max(,in[x]-));return;}
if(x!=rt) H[x]=(H[x]+f[x])/(in[x]-);
else h[x]+=H[x];
} int main()
{
n=read();m=read();
for(int i=;i<=m;i++)
{
int u=read(),v=read(),w=read();
ins(u,v,w);ins(v,u,w);
}
if(m<n)
{
Solve(,);solve(,,);
for(int i=;i<=n;i++) ans+=h[i];
return *printf("%0.5lf\n",ans/n);
}
FindCir(,);
for(int i=;i<=cn;i++) Solve(C[i],);
for(int i=;i<=cn;i++)
Cal(C[i],,C[i]);
for(int i=;i<=cn;i++) f[C[i]]+=h[C[i]],h[C[i]]=;
for(int i=;i<=cn;i++) solve(C[i],,);
for(int i=;i<=n;i++) ans+=h[i];
printf("%0.5lf\n",ans/n);
return ;
}

D2T2 美食节

CZ市为了欢迎全国各地的同学,特地举办了一场盛大的美食节。作为一个喜欢尝鲜的美食客,小M自然不愿意错过这场盛宴。他很快就尝遍了美食节所有的美食。然而,尝鲜的欲望是难以满足的。尽管所有的菜品都很可口,厨师做菜的速度也很快,小M仍然觉得自己桌上没有已经摆在别人餐桌上的美食是一件无法忍受的事情。于是小M开始研究起了做菜顺序的问题,即安排一个做菜的顺序使得同学们的等待时间最短。小M发现,美食节共有n种不同的菜品。每次点餐,每个同学可以选择其中的一个菜品。总共有m个厨师来制作这些菜品。当所有的同学点餐结束后,菜品的制作任务就会分配给每个厨师。然后每个厨师就会同时开始做菜。厨师们会按照要求的顺序进行制作,并且每次只能制作一人份。此外,小M还发现了另一件有意思的事情: 虽然这m个厨师都会制作全部的n种菜品,但对于同一菜品,不同厨师的制作时间未必相同。他将菜品用1, 2, ..., n依次编号,厨师用1, 2, ..., m依次编号,将第j个厨师制作第i种菜品的时间记为 ti,j 。小M认为:每个同学的等待时间为所有厨师开始做菜起,到自己那份菜品完成为止的时间总长度。换句话说,如果一个同学点的菜是某个厨师做的第k道菜,则他的等待时间就是这个厨师制作前k道菜的时间之和。而总等待时间为所有同学的等待时间之和。现在,小M找到了所有同学的点菜信息: 有 pi 个同学点了第i种菜品(i=1, 2, ..., n)。他想知道的是最小的总等待时间是多少。

n<=40 m<=100 p<=800  1s/512MB

第一眼 这不是修车嘛?

但是数据范围有点大,直接跑的话显然没法过

其实做法也很简单,就是每次spfa增广,只会有一道菜被做出来,那么我们在一个厨师做出了第k道菜之后,再把它的第k+1道菜的边建出来就行了。

都是玄学。

#include<iostream>
#include<cstdio>
#include<cstring>
#define S 0
#define MN 100000
#define INF 0x7fffffff
#define getchar() (*SS++)
char B[<<],*SS=B;
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 tot=,d[MN+],T,p[MN+],head[MN+],ans=,cnt=,n,m,s[][],num[MN+],from[MN+],q[],top,tail;
bool mark[MN+],inq[MN+];
struct edge{int to,next,w,c;}e[]; inline void ins(int f,int t,int w,int c)
{
e[++cnt]=(edge){t,head[f],w,c}; head[f]=cnt;
e[++cnt]=(edge){f,head[t],,-c};head[t]=cnt;
} bool spfa()
{
for(int i=;i<=T;++i) d[i]=INF;
for(d[q[top=tail=]=S]=,inq[S]=;tail<=top;)
{
int x=q[tail++];
for(register int j=head[x];j;j=e[j].next)
if(e[j].w&&d[x]+e[j].c<d[e[j].to])
{
d[e[j].to]=d[x]+e[j].c;from[e[j].to]=j;
if(!inq[e[j].to])
{
inq[e[j].to]=;
if(top<=tail&&d[e[j].to]<d[tail]) q[--tail]=e[j].to;
else
q[++top]=e[j].to;
}
}
inq[x]=;
}
return d[T]<INF;
} inline void Ins(int j,int k)
{
int from=n+(j-)*tot+k;
for(register int i=;i<=n;i++)
ins(i,from,INF,k*s[i][j]);
ins(from,j+tot*m+n,,);
} int main()
{
fread(B,,<<,stdin);
n=read();m=read();
for(register int i=;i<=n;i++)tot+=(p[i]=read());
for(register int i=;i<=n;i++)
for(register int j=;j<=m;j++)
s[i][j]=read();
T=n+m*tot+m+;int TOT=tot*m;
for(register int i=;i<=n;i++) ins(S,i,p[i],);
for(register int j=;j<=m;j++) Ins(j,num[j]=),ins(TOT+n+j,T,INF,);
while(spfa())
{
int minn=INF;
for(register int i=from[T];i;i=from[e[i^].to])
minn=min(minn,e[i].w);
for(register int i=from[T];i;i=from[e[i^].to])
e[i].w-=minn,e[i^].w+=minn;
int a=e[from[T]^].to;a=a-n-TOT;
if(num[a]<tot)Ins(a,++num[a]);
ans+=d[T]*minn;
}
printf("%d\n",ans);
return ;
}