【NOIP2014】提高组
终于在8.16晚上压哨补完了,再这么拖下去NOIP迟早药丸TAT......
Day1
T1生活大爆炸版 石头剪刀布
日常签到模拟题的话就没什么难度了,注意耐心分好类别出错就行了。
#include<cstdio> int n,na,nb,l1=1,l2=1; int a[202],b[202]; int sa=0,sb=0; int main() { scanf("%d %d %d",&n,&na,&nb); for(int i=1;i<=na;i++) scanf("%d",&a[i]); for(int i=1;i<=nb;i++) scanf("%d",&b[i]); for(int i=1;i<=n;i++,l1++,l2++) { if(l1>na)l1=1; if(l2>nb)l2=1; if(a[l1]==b[l2])continue; if(a[l1]==0) { if(b[l2]==2||b[l2]==3)sa++; else sb++; } else if(a[l1]==1) { if(b[l2]==3||b[l2]==0)sa++; else sb++; } else if(a[l1]==2) { if(b[l2]==1||b[l2]==4)sa++; else sb++; } else if(a[l1]==3) { if(b[l2]==2||b[l2]==4)sa++; else sb++; } else if(a[l1]==4) { if(b[l2]==0||b[l2]==1)sa++; else sb++; } } printf("%d %d",sa,sb); return 0; }
T2联合权值
注意加法原理的应用防TLE,还有就是最后联合权值之和要记得乘以2(正反都算)!
s[x]表示与x相连边的权值之和,注意运用加法原理必须先统计s1,再把权值加入s数组中(因为联合权值不能是自己和自己啊)。
其他的就没什么了吧,细节的话看代码吧:
#include<cstdio> #include<algorithm> #include<cstring> const int mod=1e4+7; const int N=2e5+5; using namespace std; int n,u[N],v[N]; long long s[N],s1[N]; int ma[N],ma1[N]; int w[N],maxx=0; long long sum=0; int read() { int ans=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-48;c=getchar();} return ans*f; } void ji(int x,int y) { if(ma[x]==0)ma[x]=w[y]; else if(ma[x]<w[y]){ma1[x]=ma[x];ma[x]=w[y];} else if(ma1[x]<w[y])ma1[x]=w[y]; s1[x]=(s1[x]+s[x]*w[y])%mod; s[x]+=w[y]; } int main() { n=read(); for(int i=1;i<=n-1;i++){ u[i]=read();v[i]=read(); } for(int i=1;i<=n;i++)w[i]=read(); for(int i=1;i<=n-1;i++){ int x=u[i],y=v[i]; ji(x,y);ji(y,x); } for(int i=1;i<=n;i++){ maxx=max(maxx,ma[i]*ma1[i]); sum=(sum+s1[i])%mod; } printf("%d %lld\n",maxx,(sum*2)%mod); return 0; }
T3飞扬的小鸟
这道题很容易看出是dp,状态转移方程也很好写,但是无脑dp会TLE。
不过我们可以发现,向上飞属于完全背包问题,而不点击让其下降属于01背包问题。
这样的话就很显然了,直接各跑一次不就得了......但是注意01背包要放在完全背包的后面(虽然我不知道为什么但我反过来确实WA了... )因为若01背包在前则有可能出现一个单位时间内既下降又上升的情况,而这种情况是不合法的。
还有就是上下界问题各种细节很麻烦记得处理好啊(WA了好几发QAQ)(弱
#include<cstdio> #include<cstring> #include<algorithm> const int inf=0x3f3f3f3f; using namespace std; int n,m,k,num=0,sum=inf; int p,L,H; int f[10001][1001]; int x[10001],y[10001],h[10001],l[10001]; int read() { int ans=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-48;c=getchar();} return ans*f; } int main() { n=read();m=read();k=read(); for(int i=0;i<=n-1;i++){ x[i]=read();y[i]=read(); h[i]=m+1; } h[n]=m+1; for(int i=1;i<=k;i++){ p=read();L=read();H=read(); l[p]=L;h[p]=H; } for(int i=1;i<=n;i++){ for(int j=0;j<=m;j++)f[i][j]=inf; for(int j=1;j<=m;j++){ if(j==m){ for(int kk=j-x[i-1];kk<=j;kk++) { f[i][j]=min(f[i][j],f[i-1][kk]+1); f[i][j]=min(f[i][j],f[i][kk]+1); } } if(j-x[i-1]>0){ f[i][j]=min(f[i][j],f[i-1][j-x[i-1]]+1); f[i][j]=min(f[i][j],f[i][j-x[i-1]]+1);//完全背包 } } for(int j=min(h[i]-1,m-y[i-1]);j>=l[i]+1;j--){ f[i][j]=min(f[i][j],f[i-1][j+y[i-1]]);//01背包 } if(h[i]!=m+1||l[i]!=0)num++; for(int j=1;j<=m;j++) if(j<=l[i]||j>=h[i])f[i][j]=inf; bool ff=0; for(int j=1;j<=m;j++) if(f[i][j]<inf){ff=1;break;} if(!ff){printf("0\n%d",num-1);return 0;} } for(int i=1;i<=m;i++) sum=min(sum,f[n][i]); printf("1\n%d",sum); return 0; }
Day2
T1无线网络发射器选址
看起来很复杂的样子然而因为数据范围太水无脑O(128^2*n)枚举即可(真良心啊这题...)
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int x[25],y[25],z[25]; int d,n,mx=0,num=1; int main() { scanf("%d %d",&d,&n); for(int i=1;i<=n;i++){ scanf("%d %d %d",&x[i],&y[i],&z[i]); } for(int i=0;i<=128;i++){ for(int j=0;j<=128;j++){ int sum=0; for(int k=1;k<=n;k++){ if(abs(x[k]-i)<=d&&abs(y[k]-j)<=d)sum+=z[k]; } if(mx<sum){num=1;mx=sum;} else if(mx==sum)num++; } } printf("%d %d",num,mx); return 0; }
T2寻找道路
这道题不就是把边全部建成反向的,然后从终点开始bfs,标记走到的点,然后1~n扫一遍没有被标记的点,把与其直接相连的点标记为-1。
若此时起点s的标记为0或-1,则直接输出-1。
不然的话就再从终点跑一次bfs,因为边权都为1就没什么必要跑最短路了,拓展到起点就输出并退出即可。
//很简单的一道搜索题,要是今年也有这么简单的题就好了(tan90°)。
#include<cstdio> #include<cstring> #include<algorithm> const int N=2e5+5; using namespace std; int n,m,x,y,s,t,sum=0; int first[10005],q[N],fa[N]; int ok[10005]; bool pp[10005]; struct point{ int to,next; }e[N]; int read() { int ans=0,f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ans=ans*10+c-48;c=getchar();} return ans*f; } void add(int u,int v) { sum++;e[sum].to=v;e[sum].next=first[u];first[u]=sum; } void bfs() { int head=0,tail=1; q[0]=t;ok[t]=1; while(head!=tail){ int x=q[head];head++;if(head>=200000)head=0; for(int i=first[x];i;i=e[i].next){ int y=e[i].to; if(!ok[y]){ ok[y]=1; q[tail]=y; tail++;if(tail>=200000)tail=0; } } } } void print(int x) { int tot=1; int w=fa[x]; while(w>=0){ tot++; w=fa[w]; } printf("%d\n",tot); } void bfs1() { int head=0,tail=1; q[0]=t;pp[t]=1;fa[t]=-1; while(head!=tail){ int x=q[head];head++;if(head>=200000)head=0; for(int i=first[x];i;i=e[i].next){ int y=e[i].to; if(y==s){print(x);return;} if(ok[y]<0)continue; if(!pp[y]){ fa[y]=x; pp[y]=1; q[tail]=y; tail++;if(tail>=200000)tail=0; } } } } int main() { n=read();m=read(); for(int i=1;i<=m;i++){ x=read();y=read(); add(y,x); } s=read();t=read(); bfs(); for(int i=1;i<=n;i++){ if(!ok[i]){ for(int j=first[i];j;j=e[j].next){ ok[e[j].to]=-1; } } } if(ok[s]<=0){printf("-1");return 0;} bfs1(); return 0; }
T3解方程
算是一道数论题?反正我自己是没想到怎么写的,于是求救了CZL和YYL dalao,最后历经又WA又TLE的才过了的...(弱
读入的ai可能很大,所以我们要对其进行取余操作(我们知道当f(x)=0时,f(x)%p=0,反之不一定成立,但我们可以通过选择比较不容易出错的几个大素数来使得正确的概率大大提高)。
ps:假设P是所有模数组成的集合,如果lcm(P)>该多项式的最大值,正确率可以达到100%。但是这道题显然无法这样做...
读入时用读入优化一并预处理了就好,t[i][j]表示ai取余mod[j]的值。
还有最好用秦九韶公式优化一下,其求多项式的值时间复杂度是O(n)的(也可以用快速幂取模)。
70分做法就是直接枚举1~m,判断这个数是否满足系数取余任意一个p都能使得f(x)=0,这样的复杂度是O(n*m*所选素数个数),最后三个点会超时。
70分代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int mod[6]={0,8191,8117,9973,9511}; int n,m,tot=0; long long t[105][5]; int an[1000005]; void read(int p) { int f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ for(int i=1;i<=4;i++) t[p][i]=(t[p][i]*10%mod[i]+c-48)%mod[i]; c=getchar(); } if(f==-1)for(int i=1;i<=4;i++)t[p][i]*=-1; } int main() { scanf("%d %d",&n,&m); for(int i=0;i<=n;i++)read(i); for(int i=1;i<=m;i++){ bool ff=0; for(int j=1;j<=4;j++){ int v=t[n][j]; for(int k=n-1;k>=0;k--) v=((v*i)%mod[j]+t[k][j])%mod[j]; if(v!=0){ff=1;break;} } if(!ff)an[++tot]=i; } printf("%d\n",tot); for(int i=1;i<=tot;i++) printf("%d\n",an[i]); return 0; }
满分做法就是每次只枚举到mod[p](因为若多项式的结果为0,那么取x和取x%p并不会影响结果),这样可以大幅降低时间复杂度。
100分代码:
#include<cstdio> #include<cstring> #include<algorithm> using namespace std; int mod[6]={0,8191,8117,9973,9511}; int n,m,tot=0; int t[105][5],pr[30005][5]; int an[1000005]; void read(int p) { int f=1;char c=getchar(); while(c<'0'||c>'9'){if(c=='-')f=-1;c=getchar();} while(c>='0'&&c<='9'){ for(int i=1;i<=4;i++) t[p][i]=(t[p][i]*10%mod[i]+c-48)%mod[i]; c=getchar(); } if(f==-1)for(int i=1;i<=4;i++)t[p][i]*=-1; } int main() { scanf("%d %d",&n,&m); for(int i=0;i<=n;i++)read(i); for(int i=1;i<=4;i++){ for(int j=1;j<mod[i];j++){ int v=t[n][i]; for(int k=n-1;k>=0;k--) v=((v*j)%mod[i]+t[k][i])%mod[i]; pr[j][i]=v; } } for(int i=1;i<=m;i++){ bool ff=0; for(int j=1;j<=4;j++) if(pr[i%mod[j]][j]!=0){ff=1;break;} if(!ff)an[++tot]=i; } printf("%d\n",tot); for(int i=1;i<=tot;i++) printf("%d\n",an[i]); return 0; }