NOIP2013

本文提供了NOIP2013两天赛事中的六道题目的详细解答思路及代码实现,涵盖贪心算法、图论、搜索等多种算法类型。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

NOIP2013 DAY1,DAY2     数据,标程,题目,题解合集:
http://download.youkuaiyun.com/download/junjie435/8038671

DAY1

P1:


这道题稍微分析一下就可以得到
ans = (x + m *(10^k )) %n   10^k %n 用快速幂计算即可

#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
int n,m,k,o;
long long solve(int a,int b,int mm)
{
	if(b==1)return a%mm;
	long long x=solve(a,b/2,mm);
	long long ans=((long long)(x%mm*x%mm))%mm;
	if(b%2==1)ans=((long long)(ans%mm*a%mm))%mm;
	return ans;
}
int main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	cin>>n>>m>>k>>o;
	printf("%I64d\n",(o+(long long)(m*solve(10,k,n))%n)%n);
	return 0;
}


P2:


/*排序,求最小模板。再逆序对

(ai-bi)^2 = ai*ai +bi*bi-2ai*bi  最小化结果 显然就是最大化  ai *bi的和,因此我们要找每个ai对应的bi是谁,显然假设ai< aj  那么一定满粗  bi< bj 因为不难证明 如果bi>bj 交换对应关系能使得值更优 那么显然最后问题变成了如何移动b使得移动次数最少,显然根据a我们可以知道每个b最终所在的位置。假设最终所在位置是实际的大小(假设那样是对b的有序化),那么 这个问题就可以转变为求逆序对的问题,就是在那种相对大小的情况下,当前b数组的逆序对

#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
using namespace std;
struct node
{
	int pos;
	int num;
}a[100010],b[100010];
int e[100010],t[100010];
int n,ans=0;
void init()
{
	freopen("match.in ","r",stdin);
	freopen("match.out","w",stdout);
}
bool cmp(node x,node y)
{
	return x.num<y.num;
}
void pr()
{
	cin>>n;
	for(int i=1;i<=n;i++)
	{
		a[i].pos=i;
		scanf("%d",&a[i].num);
	}
	for(int i=1;i<=n;i++)
	{
		b[i].pos=i;
		scanf("%d",&b[i].num);
	}
	sort(a+1,a+n+1,cmp);
	sort(b+1,b+n+1,cmp);
	for(int i=1;i<=n;i++)
		e[a[i].pos]=b[i].pos;
	//交换后的最小结果模板是e[];
}
void merge_sort(int l,int r)
{		
	if(r>l)
	{
		int mid=(l+r)/2;
		int p=l,q=mid+1,i=l;
		merge_sort(l,mid);
		merge_sort(mid+1,r);
		while(p<=mid||q<=r)
		{
			if(q>r||(p<=mid&&e[q]>e[p]))t[i++]=e[p++];
				else 
				{
					t[i++]=e[q++];
					ans=(ans+(mid-p+1)%99999997)%99999997;
				}
		}
		for(int i=l;i<=r;i++)e[i]=t[i];
	}
}
int main()
{
	init();
	pr();
	merge_sort(1,n);//归并 
	cout<<ans;
	return 0;
}


我把WA的代码也写出来:

#include<cstdio>
#include<iostream>
#include<cstring>
#include<algorithm>
#define mm 99999997 
using namespace std;
/*
	先找出对应的序列,
	再归并求逆序对。 
*/
struct node
{
	int num,fa;
}a[100000+10];
int n;
int c[100000+10];
int t[100000+10];
long long cnt;
int ans;
void msort(int l,int r)
{
	if(r-l>1)
	{
		//int mid=l+(r-l)/2;
		int mid=(l+r+1)>>1;
		int p=l,q=mid,i=l;
		msort(l,mid);
		msort(mid,r); 
		while(p<mid||q<r)
		{
			if(q>=r||(p<mid&&c[p]<=c[q]))
			{
				t[i++]=c[p++];
			}
			else
			{
				t[i++]=c[q++];
				cnt=((long long)(cnt+mid-p))%mm;
			}
			//for(int i=l;i<r;i++)	c[i]=t[i];
		}
		for(int i=l;i<r;i++)	c[i]=t[i];
	}
}
bool cmp(node u,node v)
{
	return u.fa<v.fa;
}
int main()
{
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	cin>>n;
	for(int i=0;i<n;i++)	scanf("%d",&a[i].num);
	for(int i=0;i<n;i++)	scanf("%d",&a[i].fa);
	sort(a,a+n,cmp);
	for(int i=0;i<n;i++)
	{
		c[i]=a[i].num;
		//printf("%d ",c[i]);
	}
	//printf("\n");
	msort(0,n);///坑~~ 
	/*for(int i=0;i<n;i++)
	{
		printf("%d ",c[i]);
	}*/
	printf("%I64d\n",cnt);
	return 0;
}
/*
6
6 5 4 3 2 1
1 2 3 4 5 6
*/


P3:



这道题当时没写过,这是一位同学的代码。我觉得较好。

直接求出最大生成树,然后询问两个节点路径上的最小值即可。求路径上的最小值,因为树是静态的,所有可以求LCA的时候顺便把路上最小值求出来。

可以用倍增算法求,比较简便


//#define _TEST _TEST
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <iostream>
#include <cmath>
#include <algorithm>
using namespace std;
/////////////////////////////////////////////////
#define rep(i,l,r) for(int i=l;i<=r;i++)
#define per(i,r,l) for(int i=r;i>=l;i--)
#define MS(arr,x) memset(arr,x,sizeof(arr))
#define LL long long
#define INE(i,u,e) for(int i=head[u];~i;i=e[i].next)
/////////////////////////////////////////////////

//O(nlogn+qlogn) 

const int inf=0x3f3f3f3f;
const int maxn=10010;
const int maxm=50010;
int n,m,q;

namespace Tree//Tree声明 
{         //////////////////////////////////
	struct edge{int v,w,next;}e[maxm];
	int head[maxn],k;
	inline void adde(int u,int v,int w){e[k].v=v;e[k].w=w;e[k].next=head[u];head[u]=k++;}
	bool vis[maxn];
	
	int dis[maxn][16];
	int fa[maxn][16];
	int dep[maxn];
}         //////////////////////////////////

namespace MST//MST声明 
{         //////////////////////////////////
	struct edge{int u,v,w;}e[maxm*2];
	int k=0;
	inline void adde(int u,int v,int w){e[k].u=u;e[k].v=v;e[k].w=w;k++;}
	inline bool cmp(edge a,edge b){return a.w>b.w;}
	int cnt;//联通块 
	int fa[maxn];
	int find(int x){return fa[x]==x?x:fa[x]=find(fa[x]);}
	inline bool unio(int u,int v)
	{
	     int fu=find(u),fv=find(v);
	     if(fv==fu)return 0;
	     fa[fu]=fv;
	     return 1;
	}
}         //////////////////////////////////

namespace MST//MST定义 
{         //////////////////////////////////
	void cal()
	{
	     sort(&e[0],&e[k],cmp);
	     rep(i,1,n) fa[i]=i;
	     cnt=n;
	     rep(i,0,m-1)
	     {
	         if(unio(e[i].u,e[i].v)) cnt--,Tree::adde(e[i].u,e[i].v,e[i].w),Tree::adde(e[i].v,e[i].u,e[i].w);
	         if(cnt<=1) break;
	     }
	}
};        //////////////////////////////////

namespace Tree//Tree定义 
{         //////////////////////////////////
	void dfs(int u,int father,int depth)
	{
	     vis[u]=1;
	     dep[u]=depth;
	     INE(i,u,e)
	     {
	         int v=e[i].v;
	         if(v==father || vis[v]) continue;
	         dis[v][0]=e[i].w;
	         fa[v][0]=u;
	         dfs(v,u,depth+1);
	     }
	}
	void init()
	{    
	     MS(vis,0);
	     //init fa[u][0] dis[u][0]
	     rep(i,1,n)
	         if(!vis[i]) dfs(i,-1,0);
	     //init fa[u][j] dis[u][j]
	     rep(j,1,15)
	     {
	         rep(i,1,n)
	         {
	            fa[i][j]=fa[fa[i][j-1]][j-1];
	         }
	     }
	     rep(j,1,15)
	     {
	         rep(i,1,n)
	         {
	            dis[i][j]=min(dis[i][j-1],dis[fa[i][j-1]][j-1]);
	         }
	     }
	}

	int query(int u,int v)
	{
	    if(MST::find(u)!=MST::find(v)) return -1;
	    int ans=inf;
	    while(u!=v)
	    {
	        if(dep[u]<dep[v])swap(u,v);
	        int tmp;
	        for(tmp=0;tmp<=15;tmp++) if(dep[u]-(1<<(tmp+1))<dep[v]) break;
	        ans=min(ans,dis[u][tmp]);
	        u=fa[u][tmp];
	    }
	    return ans;
	}
}         //////////////////////////////////
/////////////////////////////////////////////////

/////////////////////////////////////////////////
void input()
{
     MS(Tree::head,-1);
     MS(Tree::fa,-1);
     
     using namespace MST;
     scanf("%d%d",&n,&m);
     int u,v,w;
     rep(i,1,m)
     {
         scanf("%d%d%d",&u,&v,&w);
         adde(u,v,w);
     }
}
void solve()
{
     ///////////////////init///////////////////
     int u,v;
     ////////////////calculate////////////////
     MST::cal();//初始化最大生成树 
     Tree::init();//初始化倍增 
     scanf("%d",&q);
     while(q--)
     {
         scanf("%d%d",&u,&v);
         printf("%d\n",Tree::query(u,v));
     }
     /////////////////output/////////////////
     
}
/////////////////////////////////////////////////
int main()
{
     #ifndef _TEST
     freopen("truck.in","r",stdin); 
	 freopen("truck.out","w",stdout);
     #endif
     input();
     solve();
     #ifdef _TEST
     for(;;);
     #endif
     return 0;
}


DAY2

P1:


这是一个简单的贪心。
显然 如果h[i]>h[i-1] 那么 h[i]-h[i-1] 是不得不花新的代价来建造的。否则h[i] 不超过h[i-1]部分可以在h[i-1]部分完成的时候也顺便解决

#include<cstdio>
#include<cstring>
#include<iostream>
using namespace std;
/*
	贪心... 
*/
int n,a,ans,h;
int main()
{
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&a);
		if(a>h)ans+=a-h;
		h=a;
	}
	printf("%d\n",ans);
	return 0;
}

P2:


一个模拟。想一下就可以发现。只要扫一边计算 拐角点的个数就可以了。
#include<cstdio>
#include<iostream>
#include<cstring>
#include<cmath>
using namespace std;
/*
	模拟!!(贪心) 
	当他升高后一定要降低。
	当他降低后一定会升高。 
	①升高—>降低—>升高—>降低
	②降低—>升高—>降低—>升高
*/
int n,ans=1,flag;
int a[110000]; 
int main()
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	cin>>n;
	for(int i=1;i<=n;i++)
		scanf("%d",&a[i]);
	int t=a[1];
	for(int i=2;i<=n;i++) 
	{ 
		if(a[i]>t&&flag!=1)
		{
			ans++;
			flag=1; 	
		}
		if(a[i]<t&&flag!=-1)
		{
			ans++;
			flag=-1; 	
		}
		t=a[i];
	} 
	cout<<ans<<endl;
	return 0;
}


P3:


考试的时候画了个图。
        


        


分析了一下: 初看这道题,很容易让人想到广搜,但仔细观察数据范围,就会发现广搜是过不了的。为什么呢?因为广搜搜索了很多无用状态和重复状态。这里的重复状态不是平常所说的可通过判重剪枝的重复状态,而是每次询问都重复搜索的重复状态。为什么这么说呢,让我们分析一下。  显而易见的是,要让指定棋子移动朝一个方向移动,必须先将空格移动到指定棋子的那 个方向,然后棋子才能迈出步伐。而普通的广搜中,每次都要搜索一遍将空格移动到指定位置。并且,只有空格在指定棋子的隔壁的状态才是有用的,所以可以先预处理出棋子在[i,j]位置时,空格在k方向,要将棋子往h方向移动一格的最小步数,记为next[i][j][k][h],然后用dis[i][j][k]表示一个状态,那么next[i][j][k][h]即dis[i][j][k]状态到dis[i'][j'][h']的步数,其中i',j'为朝h方向移动后指定棋子的坐标,h'为h的反方向(想一想,为什么?),那么我们就相当于从dis[i][j][k]到dis[i'][j'][h']连了一条边权为next[i][j][k][h]的边,每次询问时,先将空格移动到指定棋子的隔壁,然后再通过各种状态之间的转移(用SPFA实现),求出到目标位置的最小移动次数.

#include<iostream>
#include<cstdio>
#include<queue>
#include<cstring>
#define inf 0x3f3f3f3f
using namespace std;
/*
	算法混杂 --!!
	BFS+SPFA....
	反正就是实现一个搜索+最短路 
	需要预处理各种数据 
*/
struct node
{
	int x,y,k;
};
int n,m,q;
int ex,ey,sx,sy,tx,ty;
const int dx[]={0,-1,1,0,0};
const int dy[]={0,0,0,-1,1}; 
int map[50][50];
int dis[50][50][5];
int d[50][50];
int next[50][50][5][5];
int vis[50][50][5];

bool check(node v)
{
	if(v.x<1||v.x>n)return false;
	if(v.y<1||v.y>m)return false;
	return true;
}

int spfa()//find ans 
{
	queue<node>q;
	node u,v;
	for(int k=1;k<=4;k++)
		if(dis[sx][sy][k]!=inf)
			{
				u.x=sx;	u.y=sy;
				u.k=k;	q.push(u);
				vis[u.x][u.y][u.k]=1;
			}
	while(!q.empty())
	{
		u=q.front();
		q.pop();
		vis[u.x][u.y][u.k]=0;
		for (int i=1;i<=4;i++)
		{
			v.x=u.x+dx[i];
			v.y=u.y+dy[i];
			v.k=((i-1)^1)+1;
			if(check(v)&&map[v.x][v.y]!=0&&next[u.x][u.y][u.k][i]!=inf)//判断v是否满足题意
			{
				if(dis[v.x][v.y][v.k]>dis[u.x][u.y][u.k]+next[u.x][u.y][u.k][i]+1) //更新dis 
				{
					dis[v.x][v.y][v.k]=dis[u.x][u.y][u.k]+next[u.x][u.y][u.k][i]+1;
					if(vis[v.x][v.y][v.k]==0)
					{
						q.push(v);
						vis[v.x][v.y][v.k]=1;
					}
				}
			}
		}
	}
	
	int ans=inf;
	for(int i=1;i<=4;i++)
		if(dis[tx][ty][i]<ans)
			ans=dis[tx][ty][i];
			
	if(ans==inf)return -1;
	else return ans;
}

int bfs(int sx,int sy,int tx,int ty)//处理dis 
{
	if(!map[sx][sy]) return inf;
	if(!map[tx][ty]) return inf;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++) 
			d[i][j]=inf;
	//printf("case:\n");
	//printf("d[%d][%d]=%d\n",sx,sy,d[sx][sy]);
	d[sx][sy]=0;
	queue<node>q;
	while(!q.empty()) q.pop();
	node u,v;	u.x=sx;	u.y=sy;	q.push(u);
	while(!q.empty())
	{
		if(d[tx][ty]!=inf)	
		{
			//printf("d[%d][%d]=%d\n",tx,ty,d[tx][ty]);
			return d[tx][ty];//若更新,直接返回 
		}
		node u=q.front();	q.pop();
		for(int i=1;i<=4;i++)
		{
			v.x=u.x+dx[i]; 
			v.y=u.y+dy[i];
			if(check(v)&&map[v.x][v.y]!=0&&d[v.x][v.y]==inf)//判断v是否满足题意 
			{
					d[v.x][v.y]=d[u.x][u.y]+1;//更新到达的最小步数 
					q.push(v);
					//printf("%d-%d\n",v.x,v.y);
			}
		}
	}
	return inf;
}

void readdate()//读入并初始化数据 
{
	memset(vis,0,sizeof(vis));
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=1;k<=4;k++)
					dis[i][j][k]=inf;
	scanf("%d %d %d %d %d %d",&ex,&ey,&sx,&sy,&tx,&ty);
}

int	solve()
{
	if(map[ex][ey]==0)	return -1;//点位于固定格子上 
	if(map[sx][sy]==0)	return -1;
	if(map[tx][ty]==0)	return -1;
	if(sx==tx&&sy==ty) 	return 0;//起点和目标点重合 
	
	map[sx][sy]=0;
	for(int k=1;k<=4;k++)
		 dis[sx][sy][k]=bfs(ex,ey,sx+dx[k],sy+dy[k]);
	map[sx][sy]=1;
	
	return spfa();
}

int main()
{
	freopen("puzzle.in","r",stdin);
	freopen("puzzle.out","w",stdout);
	cin>>n>>m>>q;
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			scanf("%d",&map[i][j]);
	/*for(int i=1;i<=n;i++)
	{
		for(int j=1;j<=m;j++)
			printf("%d",map[i][j]);
		printf("\n");
	}*/
	//预判每个点的下一步的下一步的连通性 
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
		{
			int v=map[i][j];
			map[i][j]=0;
			for(int k=1;k<=4;k++)
				for(int l=1;l<=4;l++)
					next[i][j][k][l]=bfs(i+dx[k],j+dy[k],i+dx[l],j+dy[l]); 
			map[i][j]=v;
		}
		/*
	for(int i=1;i<=n;i++)
		for(int j=1;j<=m;j++)
			for(int k=1;k<=4;k++)
				for(int l=1;l<=4;l++)
					printf("next[%d][%d][%d][%d]=%d\n",i,j,k,l,next[i][j][k][l]);
				*/
	while(q--)
	{
		readdate();//read:EXi、EYi、SXi、SYi、TXi、TYi;
		printf("%d\n",solve());
	}
	return 0;
} 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值