11.18
联赛前一天,作为强省的弱OIer,第一次参加Noip提高组还是挺紧张的。于是在训练的小黑屋了浪了一整天……
11.19
Day1
因为本土作战,而且家离学校近,所以睡到7:40才起床。
T1 模拟暴力,考试的时候竟然没注意到mogician……
T2 由于做了前几年的联赛,感觉前两道题应该挺水的,结果愣是看了一个多小时想不出正解……只好打80分的暴力。
T3 正解打挂没时间,搜索暴力……
11.20
Day2
T1
O(N2)
预处理后
O(1)
输出
T2 吸取第一天的教训,第二题直接STL堆暴力
T3 打完暴搜后还有一个多小时,开始着手优化搜索……在想记忆化的时候猛然想到可以状压DP……结果被卡精度,又没预处理…
O(T∗2N∗N3)
11.28
听说零点出成绩,于是兴致冲冲地等到了0:00,结果CCF没上班,网页只有一个空壳…..
下午跑到机房,果然出成绩了……
12.8
分数线330……
题解
Day1
T1 toy
直接暴力模拟。 O(N)
#include <cstdio>
#include <string>
#include <cstring>
#include <iostream>
#define N 100010
using namespace std;
int n,f[N],m,noww;
char namE[N][20];
void reaD(int &x){
char Ch=getchar();x=0;
for(;Ch>'9'||Ch<'0';Ch=getchar());
for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar());
}
void reaDs(char a[]){
char Ch=getchar();int len=0;
for(;Ch>'z'||Ch<'a';Ch=getchar());
for(;Ch>='a'&&Ch<='z';a[++len]=Ch,Ch=getchar());
a[++len]=0;
}
void M(int &noww){
if(noww<=0) noww+=n;
if(noww>n) noww-=n;
}
int main(){
reaD(n);reaD(m);
for(int i=1;i<=n;i++)
reaD(f[i]),reaDs(namE[i]);
noww=1;
for(int i=1,LoR,Lo;i<=m;i++){
reaD(LoR);reaD(Lo);
if(f[noww]^LoR) noww+=Lo;
else noww-=Lo;
M(noww);
}
for(int i=1;namE[noww][i]!=0;i++) putchar(namE[noww][i]);
return 0;
}
T2 running
根据提供的数据信息可拿80……
对于一个从u到v的人,令
f
为
可以先考虑所有人都从根节点出发和所有人都跑到根节点的做法:
对于一个
T 时刻从 x 节点跑到根节点的人,如果路径上的观察员i 满足 T+d(x)−d(i)=w(i) ,即 T+d[x]=w[i]+d[i] , 那么观察员 i 可以观察到这个人。对于一个
T 时刻从根节点跑到 x 节点的人,如果路径上的观察员i 满足 T+d(i)=w(i) ,即 T=w(i)−d(i) ,那么观察员 i 就可以观察到这个人
所以我们可以把一个从
- 1个在0时刻从u跑到根节点的人
- 1个在
d(u)−2∗d(f) 时刻从根节点跑到v的人但是这样的话 F(f) -根节点这条路径上的观察员也会观察到这个人,所以可以增加两个玩家:
- -1个在 d(u)−d(F(f)) 时刻从 F(f) 跑到根节点的人
- -1个在 d(u)−2∗d(f) 时刻从根节点跑到 v 的人
然后按照所有人从根节点出发和所有人跑到根节点的方法做。
那么复杂度就集中在求LCA的过程,用树剖或倍增
O(NlogN+N+M) #include <cstdio> #define N 300010 int En,n,m; int w[N],G[N],Ans[N],F[N],S[N],L[N],GG[N],tt; int Top[N],ws[N],Dept[N],B[N],R[N],mi[N],GG1[N]; struct timble{ int t,nx,w; }Tb[N<<3]; struct edgee{ int t,nx; }E[N<<1]; inline void reaD(int &x){ char Ch=getchar();x=0; for(;Ch>'9'||Ch<'0';Ch=getchar()); for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar()); } inline void InserT(int x,int y){ E[++En].t=y; E[En].nx=G[x]; G[x]=En; } inline void clf(){fclose(stdin);fclose(stdout);} void D1(int x,int f,int d){ F[x]=f;S[x]=1; for(int i=G[x];i;i=E[i].nx) if(E[i].t!=f){ D1(E[i].t,x,d+1); if(S[E[i].t]>S[ws[x]]) ws[x]=E[i].t; S[x]+=S[E[i].t]; } } void D2(int x,int dep,int tp){ Dept[x]=dep;Top[x]=tp; if(ws[x]) D2(ws[x],dep+1,tp); for(int i=G[x];i;i=E[i].nx) if(E[i].t!=F[x]&&E[i].t!=ws[x]) D2(E[i].t,dep+1,E[i].t); } inline void reaDedge(){ for(int i=1,x,y;i<n;i++){ reaD(x);reaD(y); InserT(x,y); InserT(y,x); } for(int i=1;i<=n;i++) reaD(w[i]); D1(1,0,1);D2(1,1,1); } inline int Lca(int x,int y){ int tp1,tp2; while(Top[x]!=Top[y]) if(Dept[Top[x]]<Dept[Top[y]]) y=F[Top[y]]; else x=F[Top[x]]; if(Dept[x]<Dept[y]) return x; return y; } inline void InsertT2(int x,int t,int w){ Tb[++tt].nx=GG[x]; GG[x]=tt; Tb[tt].t=t; Tb[tt].w=w; } inline void InsertT1(int x,int t,int w){ Tb[++tt].nx=GG1[x]; GG1[x]=tt; Tb[tt].t=t; Tb[tt].w=w; } void S1(int x,int f){ int r=B[Dept[x]+w[x]]; for(int i=GG1[x];i;i=Tb[i].nx) B[Tb[i].t]+=Tb[i].w; for(int i=G[x];i;i=E[i].nx) if(E[i].t!=f) S1(E[i].t,x); Ans[x]+=B[Dept[x]+w[x]]-r; } int S2(int x,int f){ int r; if(w[x]-Dept[x]>=0) r=R[w[x]-Dept[x]]; else r=mi[Dept[x]-w[x]]; for(int i=GG[x];i;i=Tb[i].nx) if(Tb[i].t>=0)R[Tb[i].t]+=Tb[i].w; else mi[-Tb[i].t]+=Tb[i].w; for(int i=G[x];i;i=E[i].nx) if(E[i].t!=f) S2(E[i].t,x); if(w[x]-Dept[x]>=0) Ans[x]+=R[w[x]-Dept[x]]-r; else Ans[x]+=mi[Dept[x]-w[x]]-r; } int ww[20],wt; inline void Pt(int x){ if(!x){putchar('0');return;} while(x)ww[++wt]=x%10,x/=10; for(;wt;wt--)putchar(ww[wt]+'0'); } int main(){ reaD(n);reaD(m); reaDedge(); for(int i=1,u,v,f;i<=m;i++){ reaD(u);reaD(v); f=Lca(u,v); InsertT1(u,Dept[u],1); InsertT1(F[f],Dept[u],-1); InsertT2(f,Dept[u]-Dept[f]*2,-1); InsertT2(v,Dept[u]-2*Dept[f],1); } S1(1,0);S2(1,0); for(int i=1;i<n;i++) Pt(Ans[i]),putchar(' '); return Pt(Ans[n]),0; }
T3 classroom
概率DP
f[i][j][k] 表示前 i 节课,申请了换j 节课, k=1 表示第i节课申请了换课, k=0 表示没有。g(i,j) 表示 i 教室与
j 教室的最短距离那么对于第 i 节课,如果不申请换课:
- 若第
i−1 节课没有申请换课, f[i][j][0]=f[i−1][j][0]+g(c[i−1],c[i]) - 若第
i−1
节课申请了换课,那么
– 有 k[i−1] 的概率申请成功,要消耗 g(d[i−1],c[i]) 的体力
– 有 (1−k[i−1]) 的概率申请失败,要消耗 g(c[i−1],c[i]) 的体力
所以期望消耗 k[i−1]∗g(d[i−1],c[i])+(1−k[i−1])∗g(c[i−1],c[i]) 的体力
如果申请换课,同意按照上面的方法分类,只不过要同时考虑第 i 节课和第
i−1 节课的申请成功的情况。计算两个教室间的最短路程,因为教室数比较小,可以floyd
O(v3+nm)
#include <cstdio> #include <iostream> #include <cstring> #include <string> #include <algorithm> #include <queue> #define N 2010 #define M 310 using namespace std; typedef long long ll; int n,m,v,e,C[N],D[N],w[N]; double K[N],Ans,f[2][N][2]; ll d[M][M]; inline void reaD(int &x){ char Ch=getchar();x=0; for(;Ch>'9'||Ch<'0';Ch=getchar()); for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar()); } inline int min(const int &a,const int &b){ return a<b?a:b; } inline double min(double a,double b,double c){ return min(min(a,b),c); } int main(){ reaD(n);reaD(m);reaD(v);reaD(e); for(int i=1;i<=v;i++){ for(int j=1;j<=v;j++) d[i][j]=1<<30; d[i][i]=0; } for(int i=1;i<=n;i++) reaD(C[i]); for(int i=1;i<=n;i++) reaD(D[i]); for(int i=1;i<=n;i++){ scanf("%lf",&K[i]); } for(int i=1,x,y,z;i<=e;i++){ reaD(x);reaD(y);reaD(z); d[x][y]=d[y][x]=min(d[x][y],z); } for(int k=1;k<=v;k++) for(int i=1;i<=v;i++) for(int j=1;j<=v;j++) if(d[i][j]>d[i][k]+d[k][j]) d[i][j]=d[i][k]+d[k][j]; memset(f,0x7F,sizeof(f)); f[1][1][1]=f[1][0][0]=0; for(int i=2;i<=n;i++) for(int j=0;j<=i&&j<=m;j++){ f[i&1][j][0]=min(f[i&1^1][j][0]+d[C[i-1]][C[i]],f[i&1^1][j][1]+d[D[i-1]][C[i]]*K[i-1]+d[C[i-1]][C[i]]*(1-K[i-1])); if(j) f[i&1][j][1]=min(f[i&1^1][j-1][0]+d[C[i-1]][D[i]]*K[i]+d[C[i-1]][C[i]]*(1-K[i]), f[i&1^1][j-1][1]+d[D[i-1]][D[i]]*K[i]*K[i-1]+d[D[i-1]][C[i]]*K[i-1]*(1-K[i])+d[C[i-1]][D[i]]*K[i]*(1-K[i-1])+d[C[i-1]][C[i]]*(1-K[i])*(1-K[i-1])); } Ans=1<<30; for(int i=0;i<=m;i++) Ans=min(Ans,f[n&1][i][0],f[n&1][i][1]); printf("%.2lf",Ans); return 0; }
Day2
T1 problem
根据 C(i,j)=C(i−1,j−1)+C(i,j−1) 预处理后输出
但是因为 C(i,j) 会很大,所已预处理的时候就可以对 C(i,j) 取模,之后判断是不是为零就行了
O(n2+T)
#include <cstdio> #define N 2010 int A[N][N],n,m,k,t,Ans[N][N]; void reaD(int &x){ char Ch=getchar();x=0; for(;Ch>'9'||Ch<'0';Ch=getchar()); for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=getchar()); } int w[20],wt; void Pt(int x){ if(!x){putchar('0');putchar('\n');return ;} wt=0;while(x)w[++wt]=x%10,x/=10; for(;wt;wt--)putchar(w[wt]+'0');putchar('\n'); } int main(){ reaD(t);reaD(k); for(int i=0;i<=2000;i++) A[i][0]=1;A[1][1]=1; for(int i=2;i<=2000;i++){ for(int j=1;j<=i;j++){ if((A[i][j]=A[i-1][j-1]+A[i-1][j])>=k) A[i][j]-=k; } } for(int i=1;i<=2000;i++)for(int j=i+1;j<=2000;j++) A[i][j]=-1; for(int i=1;i<=2000;i++) for(int j=1;j<=2000;j++){ Ans[i][j]=Ans[i-1][j]+Ans[i][j-1]-Ans[i-1][j-1]+(A[i][j]==0); } while(t--){ reaD(n);reaD(m); Pt(Ans[n][m]); } return 0; }
T2 earthworm
可以开三个队列,分别记录初始的蚯蚓和切开后的两断蚯蚓的长度,因为存储初始蚯蚓的队列按照升序排列,所以可以证明另外两个队列也是升序的。
那么每次找最长的只要比较三个队列的头部就行了。
至于怎么处理每个单位时间增加的长度,可以开个变量L,L每个单位时间加上q,新增的蚯蚓的长度只要减去q就行了。
O(nlogn+m)
#include <cstdio> #include <iostream> #include <algorithm> #include <queue> #include <vector> #define N 100010 #define M 7000010 using namespace std; int n,t,m,L,u,v,q,A[N],tt,T; int B[M],C[M],lb,lc,tb,tc; inline char NC(){ static char buf[100000],*p1=buf,*p2=buf; if(p2==p1){ p2=(p1=buf)+fread(buf,1,100000,stdin); if(p1==p2) return EOF; } return *p1++; } void reaD(int &x){ char Ch=NC();x=0; for(;Ch>'9'||Ch<'0';Ch=NC()); for(;Ch>='0'&&Ch<='9';x=x*10+Ch-'0',Ch=NC()); } int w[20],wt; void Pt(int x){ if(!x){putchar('0');return ;} wt=0;while(x)w[++wt]=x%10,x/=10; for(;wt;wt--)putchar(w[wt]+'0'); } inline void Cut(int x,int ti){ if(ti==tt){ Pt(x+L),tt+=T; if(tt<=m) putchar(' '); } int a=1ll*(x+L)*u/v,b=x+L-a;L+=q; B[++tb]=a-L;C[++tc]=b-L; } inline void pt(int x,int ti){ if(ti==tt){ Pt(x),tt+=T; if(tt<=m+n) putchar(' '); } } inline bool cmp(const int &a,const int &b){ return a>b; } int main(){ reaD(n);reaD(m);reaD(q);reaD(u);reaD(v);reaD(T);tt=T; for(int i=1;i<=n;i++) reaD(A[i]); sort(A+1,A+1+n,cmp);lb=lc=t=1; for(int i=1;i<=m;i++){ if(t<=n&&(A[t]>=B[lb]||lb>tb)&&(A[t]>=C[lc]||lc>tc)) Cut(A[t++],i); else if(lb<=tb&&(B[lb]>=C[lc]||lc>tc)) Cut(B[lb++],i); else Cut(C[lc++],i); }putchar('\n'); int i=1;tt=T; while(t<=n||lb<=tb||lc<=tc){ if(t<=n&&(A[t]>=B[lb]||lb>tb)&&(A[t]>=C[lc]||lc>tc)) pt(A[t++]+L,i); else if(lb<=tb&&(B[lb]>=C[lc]||lc>tc)) pt(B[lb++]+L,i); else pt(C[lc++]+L,i); i++; } return 0; }
T3 angrybirds
状压DP
因为只有18只小鸟,可以把状态压缩在2^18的整数内。
但是这样DP的复杂度就是 O(2nn3)
所以我们可以枚举两只小鸟,计算以他们的坐标和原点坐标画成的抛物线上有哪些小鸟,也用2^18的整数记录下来。
这样复杂度就是 O(2nn2) 了
总复杂度 0(2nn2∗T)
#include <cstdio> #include <cstring> #include <string> #include <iostream> using namespace std; int n,m,l,r,mid,v[20],qt,tt,d[20][20],Res[1<<20],w; double x[20],y[20]; struct prs{ double a1,b1,a2,b2; }q[30],p[20][20]; double abs(double x){ if(x<0) return -x; return x; } prs Biu(int l,int s){ prs Re; Re.a1=(y[l]*x[s]-y[s]*x[l]),Re.a2=(x[l]*x[l]*x[s]-x[s]*x[s]*x[l]); if(Re.a2==0) return Re; Re.b1=(y[l]-Re.a1*x[l]*x[l]/Re.a2),Re.b2=x[l]; return Re; } void work(){ scanf("%d%d",&n,&m);qt=0; for(int i=1;i<=n;i++) scanf("%lf %lf",&x[i],&y[i]),v[i]=0; for(int i=1;i<=n;i++) for(int j=i+1;j<=n;j++) if(x[i]!=x[j]){ p[i][j]=Biu(i,j); d[i][j]=0; if(p[i][j].a1*p[i][j].a2>-1e-6) continue; for(int k=1;k<=n;k++) if(abs(p[i][j].a1*x[k]*x[k]/p[i][j].a2+p[i][j].b1*x[k]/p[i][j].b2-y[k])<=1e-6) d[i][j]|=1<<k-1; } memset(Res,0x7F,sizeof(Res)); Res[0]=0; for(int i=0,sx;i<=(1<<n);i++){ for(int j=1;j<=n;j++){ Res[i|(1<<(j-1))]=min(Res[i|(1<<(j-1))],Res[i]+1); } for(int j=1;j<=n;j++) if((i&(1<<(j-1)))==0) for(int k=j+1;k<=n;k++) if(x[j]!=x[k]&&(i&(1<<(k-1)))==0&&p[j][k].a1*p[j][k].a2<0){ Res[i|d[j][k]]=min(Res[i|d[j][k]],Res[i]+1); } } printf("%d\n",Res[(1<<n)-1]); } int main(){ int t; scanf("%d",&t); while(t--) work(); return 0; }