NOIP2015提高组解析

题目见此



day1

神奇的幻方:

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


参考程序:

#include
#include
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
#include
#include
#include
#define maxn 210000
using 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
#include
#include
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=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
#include
#define maxn 110000
using 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]>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

显然,既超时又超空间。

对于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
#include
#include
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
#include
#include
#define maxn 310000
using 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]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;idep[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>1;
          if (check(mid))r=mid;
            else l=mid+1;
    }
	printf("%d",l);
    return 0;
}


你可能感兴趣的:(NOIP2015提高组解析)