NOIP2015提高组解析

来源:互联网 发布:板式家具软件 编辑:程序博客网 时间:2024/06/06 02:08

题目见此



day1

神奇的幻方:

裸裸的模拟(其实也可以发现规律:i+1在i的右上方,如果已经有数了,就填在i的下方)


参考程序:

#include<cstdio>#include<algorithm>using namespace std;int a[50][50];int nx,ny,n;int main(){freopen("magic.in","r",stdin);freopen("magic.out","w",stdout);scanf("%d",&n);nx=1;ny=n/2+1;a[nx][ny]=1;for (int i=2;i<=n*n;i++){int tx,ty;if (nx==1){if (ny!=n){tx=n;ty=ny+1;}else{tx=nx+1;ty=ny;}}else{if (ny==n){tx=nx-1;ty=1;}else{tx=nx-1;ty=ny+1;if (a[tx][ty]){tx=nx+1;ty=ny;}}}a[tx][ty]=i;nx=tx;ny=ty;}//紧跟题目走,比较low的程序for (int i=1;i<=n;i++){for (int j=1;j<=n;j++)printf("%d ",a[i][j]);printf("\n");}return 0;}


信息传递:

当时比赛时用Pascal打了个tarjan求强连通分量,不知为什么爆栈40分,心痛心痛

所以这次打了个简单的深搜,其实就是沿着走,如果当前的点已经访问过了就是一个环。


参考程序:

#include<cstdio>#include<algorithm>#include<cstring>#include<ctime>#define maxn 210000using namespace std;int a[maxn];int ans[maxn];int res=0x7f7f7f7f;bool vis[maxn];int n;void dfs(int x,int k){ans[x]=k;vis[x]=true;if (!vis[a[x]])dfs(a[x],k+1);if (ans[a[x]] && ans[x]-ans[a[x]]+1)res=min(res,ans[x]-ans[a[x]]+1);ans[x]=0;}int main(){freopen("message.in","r",stdin);freopen("message.out","w",stdout);scanf("%d",&n);memset(vis,0,sizeof(vis));memset(ans,0,sizeof(ans));for (int i=1;i<=n;i++)scanf("%d",&a[i]);for (int i=1;i<=n;i++)if (!vis[i])dfs(i,1);printf("%d",res);//printf("%d\n%.2f",res,(double)clock()/CLOCKS_PER_SEC);return 0;}


斗地主:

又是深搜(好像几年的NOIP的压轴题都是倍增和深搜。。)

其实,每次深搜时只需记录当前的步数和剩下的牌,对于每一个状态首先模拟不打顺子的情况最少步数(无需深搜,直接计算),再深搜需要打顺子的情况,加个最优化剪枝就行了,不是非常复杂。


参考程序:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;int n;int hand[25];int sum[25];int ans;int unline(){memset(sum,0,sizeof(sum));for (int i=0;i<=13;i++)sum[hand[i]]++;    int tot=0;                                      while(sum[4]&&sum[2]>1)  sum[4]--,sum[2]-=2,tot++;          while(sum[4]&&sum[1]>1) sum[4]--,sum[1]-=2,tot++;    while(sum[4]&&sum[2]) sum[4]--,sum[2]--,tot++;    while(sum[3]&&sum[2]) sum[3]--,sum[2]--,tot++;    while(sum[3]&&sum[1]) sum[3]--,sum[1]--,tot++;    return tot+sum[1]+sum[2]+sum[3]+sum[4]; }void dfs(int dep){if (dep>=ans)return;int tmp=unline();if (tmp+dep<ans)ans=tmp+dep;for (int i=2;i<=13;i++) {                                    int j=i;while(hand[j]>=3) j++;        if(j-i>=2) {            for (int j2=i+1;j2<=j-1;j2++) {                for (int k=i;k<=j2;k++) hand[k]-=3;                dfs(dep+1);                for (int k=i;k<=j2;k++) hand[k]+=3;    }    }    }    for (int i=2;i<=13;i++) {                                        int j=i;while(hand[j]>=2) j++;        if(j-i>=3) {            for (int j2=i+2;j2<=j-1;j2++) {                for (int k=i;k<=j2;k++) hand[k]-=2;                dfs(dep+1);                for (int k=i;k<=j2;k++) hand[k]+=2;    }    }    }    for (int i=2;i<=13;i++) {                                     int j=i;while(hand[j]>=1) j++;        if(j-i>=5) {            for (int j2=i+4;j2<=j-1;j2++) {                for (int k=i;k<=j2;k++) hand[k]--;                dfs(dep+1);                for (int k=i;k<=j2;k++) hand[k]++;    }    }    }}int main(){freopen("landlords.in","r",stdin);freopen("landlords.out","w",stdout);int T;scanf("%d%d",&T,&n);while (T--){memset(hand,0,sizeof(hand));for (int i=1;i<=n;i++){int x,y;scanf("%d%d",&x,&y);if(x==1) x=13; else if(x) x--;            hand[x]++;}ans=0x7f7f7f7f;dfs(0);printf("%d\n",ans);}return 0;}



day2:

跳石头:

此题在poj之旅中应做过,二分即可,比赛当时因为r上限取错了,30分没了,心痛心痛。

建立模型(l,r]区间,取mid贪心判断,(贪心:如果当前石头比上一个选取剩下的石头的距离小于mid就把当前石头移走,最后若移走的石头数大于m,就说明mid过大,r=mid,否则l=mid。


参考程序:

#include<cstdio>#include<algorithm>#define maxn 110000using namespace std;int n,m,L;int a[maxn];bool check(int k){int j=0,cnt=0;for (int i=1;i<=n;i++)if (a[i]-a[j]<k)cnt++;else j=i;return cnt<=m;}int main(){freopen("stone.in","r",stdin);freopen("stone.out","w",stdout);scanf("%d%d%d",&L,&n,&m);for (int i=1;i<=n;i++)scanf("%d",&a[i]);a[0]=0;a[n+1]=L;int l=0,r=L+1;while (l+1<r){int mid=(l+r)>>1;if (check(mid))l=mid;else r=mid;}printf("%d",l);return 0;}


子串:

其实就是dp,令f[i][j][k]表示A串选到第i个位置(第i个字母要选),B串选到第j个位置,共用了k个子串的方案数

则若a[i]!=b[j]  , f[i][j][k]=0;

a[i]==b[j],f[i][j][k]=f[i-1][j-1][k](和前一个连成一个子串)+f[p][j-1][k-1](0<p<=i)

显然,既超时又超空间。

对于f[p][j-1][k-1]求和,我们预处理sum[i][j-1][k-1]=f[1][j-1][k-1]+.....f[i][j-1][k-1],于是只需O(1)时间转移,总效率O(nmk)

再发现f[][][k]只于f[][][k-1]和sum[][][k-1]有关,滚动数组即可。


虽然已满分,但是显然很麻烦,

我们重新定义f[i][j][k][0]表示A串选到第i个位置(第i个字母不选),B串选到第j个位置,共用了k个子串的方案数

f[i][j][k][1]表示A串选到第i个位置(第i个字母要选),B串选到第j个位置,共用了k个子串的方案数

原理同上,可推出(t=i&1,p=1-t---->滚动数组):

f[k][t][j][1]=((f[k-1][p][j-1][0]+f[k-1][p][j-1][1])+f[k][p][j-1][1]);

f[k][t][j][0]=(f[k][p][j][0]+f[k][p][j][1]);


参考程序:

#include<cstdio>#include<algorithm>#include<cstring>using namespace std;const int maxn=1100;const int Mod=1000000007;char a[maxn],b[maxn];int f[210][2][210][2];int sum=0,n,m,K;int main(){freopen("substring.in","r",stdin);freopen("substring.out","w",stdout);scanf("%d%d%d",&n,&m,&K);scanf("%s%s",a+1,b+1);for (int i=1;i<=n;i++){int t=i&1;int p=1-t;f[1][t][1][0]=sum;if (a[i]==b[1])sum++,f[1][t][1][1]=1;for (int j=2;j<=m;j++)for (int k=1;k<=K;k++)if (a[i]!=b[j])f[k][t][j][1]=0,f[k][t][j][0]=(f[k][p][j][0]+f[k][p][j][1])%Mod;else{f[k][t][j][1]=((f[k-1][p][j-1][0]+f[k-1][p][j-1][1])%Mod+f[k][p][j-1][1])%Mod;f[k][t][j][0]=(f[k][p][j][0]+f[k][p][j][1])%Mod;}for (int j=1;j<=m;j++)for (int k=1;k<=K;k++)f[k][p][j][0]=f[k][p][j][1]=0;}printf("%d",(f[K][n&1][m][0]+f[K][n&1][m][1])%Mod);return 0;}



运输计划

此题最好在BZOJ上测,其他oj都会爆栈。

显然直接求最大值不容易,所以我们可以二分结果,取mid

对于一些线路所需时间小于mid的,我们可以不用管,对于大于mid的,应取这些线路的最大公共线段,如果将这一段改为虫洞仍不能满足要求,则mid过小,调整区间,反之同理。

但我们还是需要预处理,以1为根建立剖分树(剖分法求LCA),然后求问题中线路起点终点对应的LCA和无虫洞时所需的时间,然后就是二分了。


参考程序:

#include<cstdio>#include<algorithm>#include<cstring>#define maxn 310000using namespace std;int cf[maxn],X[maxn],Y[maxn];int dis[maxn],d[maxn],D[maxn],fa[maxn];int dep[maxn],u[maxn],v[maxn],w[maxn];int top[maxn],size[maxn],son[maxn];int a[3*maxn];int n,m,NodeCnt=0;struct Node{int j,v,next;}e[3*maxn];void addedge(int u,int v,int w){     int p=++NodeCnt;     e[p].j=v;e[p].v=w;e[p].next=a[u];     a[u]=p;}void dfs1(int u){     dep[u]=dep[fa[u]]+1; size[u]=1; int Max=0;     for (int p=a[u];p;p=e[p].next)       if (e[p].j!=fa[u]){         int j=e[p].j;         dis[j]=dis[u]+e[p].v; fa[j]=u;         dfs1(j);         size[u]+=size[j];         if (size[u]>Max){            Max=size[u];            son[u]=j;         }       }}void build(int u,int tp){     top[u]=tp;     if (son[u])build(son[u],tp);     for (int p=a[u];p;p=e[p].next)       if (e[p].j!=fa[u] && e[p].j!=son[u])build(e[p].j,e[p].j);}int LCA(int u,int v){     int fu=top[u],fv=top[v];     while (fu != fv){           if (dep[fu]<dep[fv])swap(fu,fv),swap(u,v);           u=fa[fu];fu=top[u];     }     return dep[u]<dep[v]?u:v;}int redu=0,tot=0;int cnt[maxn];int dfs2(int u){for (int p=a[u];p;p=e[p].next)if (e[p].j != fa[u])cnt[u]+=dfs2(e[p].j);if (cnt[u]==tot)redu=max(redu,d[u]);return cnt[u];}bool check(int mid){memset(cnt,0,sizeof(cnt));int Max=0;tot=redu=0;for (int i=1;i<=n;i++)if (D[i]>mid){Max=max(Max,D[i]);cnt[X[i]]++;cnt[Y[i]]++;cnt[cf[i]]-=2;tot++;}dfs2(1);return Max-redu<=mid;}int main(){    freopen("transport.in","r",stdin);    freopen("transport.out","w",stdout);    scanf("%d%d",&n,&m);    for (int i=1;i<n;i++){        scanf("%d%d%d",&u[i],&v[i],&w[i]);        addedge(u[i],v[i],w[i]);        addedge(v[i],u[i],w[i]);    }    dfs1(1);    build(1,1);    for (int i=1;i<n;i++){        if (dep[u[i]]>dep[v[i]])d[u[i]]=w[i];           else d[v[i]]=w[i];    }    int l=0,r=0;    for (int i=1;i<=m;i++){        scanf("%d%d",&X[i],&Y[i]);        r=max(r,D[i]=dis[X[i]]+dis[Y[i]]-2*dis[cf[i]=LCA(X[i],Y[i])]);}    while (l<r){          int mid=(l+r)>>1;          if (check(mid))r=mid;            else l=mid+1;    }printf("%d",l);    return 0;}


0 0