noip2013题解

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/captain_Ben/article/details/49419827

Day1:

1.转圈游戏

(circle.cpp/c/pas)

【问题描述】

n 个小伙伴(编号从 0 到 n-1)围坐一圈玩游戏。按照顺时针方向给n 个位置编号,从 0 到 n-1。最初,第 0 号小伙伴在第 0 号位置,第 1 号小伙伴在第 1 号位置,……,依此类 推。

游戏规则如下:每一轮第 0 号位置上的小伙伴顺时针走到第 m 号位置,第 1 号位置小伙伴走到第 m+1 号位置,……,依此类推,第n − m号位置上的小伙伴走到第0 号位置,第n-m+1 号位置上的小伙伴走到第 1 号位置,……,第 n-1 号位置上的小伙伴顺时针走到第m-1 号位置。

现在,一共进行了 10k 轮,请问 x 号小伙伴最后走到了第几号位置。

 

【输入】

输入文件名为 circle.in。

输入共 1 行,包含 4 个整数 n、m、k、x,每两个整数之间用一个空格隔开。

 

【输出】

输出文件名为 circle.out。

输出共 1 行,包含 1 个整数,表示 10k 轮后 x 号小伙伴所在的位置编号。

 

【输入输出样例】

circle.in

10 3 4 5

circle.out

 5

【数据说明】

对于 30%的数据,0 < k < 7;

对于 80%的数据,0 < k < 107

对于 100%的数据,1 < n< 1,000,000,0 <m <n ,0 ≤ x ≤ n,0 < k< 109

快速幂(关于快速幂的详细解说将在即将发出的博客:数论总结。中有说明):
#include<cstdio>//快速幂 
#include<algorithm>
#include<cstring>
using namespace std;
long long n,m,k,x;
int main()
{
	freopen("circle.in","r",stdin);
	freopen("circle.out","w",stdout);
	scanf("%d %d %d %d",&n,&m,&k,&x);
	long long q=1,p=10;
	while(k>0)
	{
		if(k%2!=0) q=(q*p)%n;
		k=k>>1;
		p=(p*p)%n;
	}
	x=(x+q*m)%n;
	printf("%d\n",x);
	return 0;
}

2.火柴排队

(match.cpp/c/pas)

【问题描述】

涵涵有两盒火柴,每盒装有 n 根火柴,每根火柴都有一个高度。现在将每盒中的火柴各自排成一列,同一列火柴的高度互不相同,两列火柴之间的距离定义为:,其中 ai 表示第一列火柴中第 i 个火柴的高度,bi 表示第二列火柴中第 i 个火柴的高度。

每列火柴中相邻两根火柴的位置都可以交换,请你通过交换使得两列火柴之间的距离最小。请问得到这个最小的距离,最少需要交换多少次?如果这个数字太大,请输出这个最小交换次数对 99,999,997 取模的结果。

 

【输入】

输入文件为 match.in。

共三行,第一行包含一个整数 n,表示每盒中火柴的数目。

第二行有 n 个整数,每两个整数之间用一个空格隔开,表示第一列火柴的高度。

第三行有 n 个整数,每两个整数之间用一个空格隔开,表示第二列火柴的高度。

 

【输出】

输出文件为 match.out。

输出共一行,包含一个整数,表示最少交换次数对 99,999,997取模的结果

 

【输入输出样例 1】

match.in

match.out

4

2 3 1 4

3 2 1 4

1

【输入输出样例说明】

最小距离是 0,最少需要交换 1 次,比如:交换第 1 列的前 2 根火柴或者交换第 2 列的前 2 根火柴。

 

【输入输出样例 2】

match.in

match.out

4

1 3 4 2

1 7 2 4

2

【输入输出样例说明】

最小距离是 10,最少需要交换 2 次,比如:交换第 1 列的中间 2 根火柴的位置,再交换第 2 列中后 2 根火柴的位置。

 

【数据范围】

对于 10%的数据, 1 ≤ n ≤ 10;

对于 30%的数据,1 ≤ n ≤ 100;

对于 60%的数据,1 ≤ n ≤ 1,000;

对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤火柴高度≤ 231− 1。

当了解了题目,可将问题转化为求交换相邻数字的长度。
典型的归并排序,从题目给出的公式我们可以证明到该题贪心的思路:用一个结构体记录每组的数据分别的编号和数值,分别按从小到大排序(可观察处按升序排列后对应两组数据的差值最小),排序后查找错位的点(即排序后两组对应数值编号不同的点),进行归并排序。
归并排序的大概思路:分别排序后合并排序。
#include<cstdio>//归并排序 
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=100005;
const long long mod=99999997;
struct node
{
	int u,v;//记录位置和高度 
}a[maxn],b[maxn],f[maxn]; //f记录第i个数移动后的位置 

long long sb[maxn];
long long n;
long long ans=0;
bool compare1(node a,node b)
{
	return a.v<=b.v;
}
bool compare2(node a,node b)
{
	return a.v>=b.v;
}
void work1(int y)//计算错位的个数 
{
	//printf("y=%d\n",y);
	for(int i=y;i<=n;i+=i&-i)//如果i是奇数,变成偶数 
	{
		sb[i]++;
	//	printf("i=%d sb=%d\n",i,sb[i]);
	}
}
int work2(int a)
{
	int ans=0;
	for(int i=a;i>=1;i-=i&-i)
	{
		ans+=sb[i];
	//	printf("%d\n",ans);
	}
	return ans;
}
int main()
{
	freopen("match.in","r",stdin);
	freopen("match.out","w",stdout);
	scanf("%d",&n);
	for(long long i=1;i<=n;i++)
	{
		scanf("%lld",&a[i].v);
		a[i].u=i;
	}
	for(long long i=1;i<=n;i++)
	{
		scanf("%lld",&b[i].v);
		b[i].u=i;
	}
	for(int i=1;i<=n;i++)
	{
		f[i].u=i;
	}
	sort(a+1,a+n+1,compare1);
	sort(b+1,b+n+1,compare1);
	for(int i=1;i<=n;i++)
	{
		f[a[i].u].v=b[i].u;
		//printf("%d",f[a[i].u].v);//
	}
	sort(f+1,f+1+n,compare2);//不排序的话答案是错的= =woc 
	for(int i=1;i <=n;i++)
	{
		work1(f[i].u);
		ans+=work2(f[i].u-1);
	//	printf("%d\n",f[i].u-1);
	//	printf("%d\n",ans);
		ans=ans%mod;
	}
	printf("%d",ans);
	return 0;
}

3.货车运输

(truck.cpp/c/pas)

【问题描述】

A 国有 n 座城市,编号从 1 到 n,城市之间有 m 条双向道路。每一条道路对车辆都有重量限制,简称限重。现在有 q 辆货车在运输货物,司机们想知道每辆车在不超过车辆限重的情况下,最多能运多重的货物。

 

【输入】

输入文件名为 truck.in。

输入文件第一行有两个用一个空格隔开的整数 n,m,表示 A 国有n 座城市和 m 条道路。

接下来 m 行每行 3 个整数 x、y、z,每两个整数之间用一个空格隔开,表示从 x 号城市到 y 号城市有一条限重为 z 的道路。注意:x 不等于 y,两座城市之间可能有多条道路。

接下来一行有一个整数 q,表示有 q 辆货车需要运货。

接下来 q 行,每行两个整数 x、y,之间用一个空格隔开,表示一辆货车需要从 x 城市 运输货物到 y 城市,注意:x 不等于 y。

 

【输出】

输出文件名为 truck.out。

输出共有 q 行,每行一个整数,表示对于每一辆货车,它的最大载重是多少。如果货车不能到达目的地,输出-1。

 

【输入输出样例】

truck.in

truck.out

4

3

 

3

1

2

4

-1

2

3

3

3

3

1

1

3

 

 

1

3

 

1

4

 

1

3

 

 

【数据说明】

 

对于 30%的数据,0 < n < 1,000,0 < m < 10,000,0 < q< 1,000;

对于 60%的数据,0 < n < 1,000,0 < m < 50,000,0 < q< 1,000;

对于 100%的数据,0 < n < 10,000,0 < m < 50,000,0 < q< 30,000,0 ≤ z ≤ 100,000。

最大生成树加最近公共祖先。

#include<cstdio>
#include<algorithm>
using namespace std;
/*
   更新:
     一个是f数组的更新:f[i][j]=f[f[i][j-1]][j-1];
     一个是g数组的更新:g[i][j]=min(g[i][j-1],g[f[i][j-1][j-1])
    (因为i结点的2^j的祖先节点就是i的2^(j-1)的祖先节点的2^(j-1)的祖先节点) 
   过程:
      将两个点调到同一深度(将深度大的),当x==y是就可返回值ans 
*/
int n,m,q;
int deep[100001];//i节点的深度
int f[100001][32];//i节点的2^j的父亲节点
int g[100001][32];//i节点到2^j祖先的最短小边;
struct node{
	int u,v,w;
}e[100001];
int father[100001];
int s[100001];
int t[100001];
int ans[100001];
int head[100001];
int k=0;
struct node1{
	int to,next,value;
}e1[500001];
bool cmp(node e1,node e2)
{
	return e1.w>e2.w;//求最大按降序排列 
}
int find(int x)//找祖先
{
	if(father[x]==x)
	return x;
	else 
	return father[x]=find(father[x]);
}
void add(int u,int v,int w)
{
	k++;
	e1[k].to=v;
	e1[k].value=w;
	e1[k].next=head[u];
	head[u]=k;
}
int dfs(int x,int sum)//最近公共祖先查找 (起点是终点的父亲)
{
	deep[x]=sum;
	for(int i=1;i<=14;i++)//初始 
	{
		f[x][i]=f[f[x][i-1]][i-1];
		g[x][i]=min(g[f[x][i-1]][i-1],g[x][i-1]);
	}
	int p1=head[x];
	while(p1)
	{
		if(deep[e1[p1].to]==0)
		{
			f[e1[p1].to][0]=x;//该节点已无儿子= =,所以x是他的祖先
			g[e1[p1].to][0]=e1[p1].value;
			dfs(e1[p1].to,sum+1);
		}
		p1=e1[p1].next;
	}
}
int work(int x,int y)//最短路 
{
	if(deep[x]>deep[y])//默认x的深度比y小1,所以进行一个判定 
	{
		int temp=x;
		x=y;
		y=temp;
	}
	int minn=2100000000;
	for(int i=14;i>=0;i--)//把一个较深的点向上提,并记录他到祖先的最小距离
	{
		if(deep[x]<=deep[f[y][i]])
		{
			minn=min(g[y][i],minn);
			y=f[y][i];
		}
	}
	if(x==y)
	return minn;
	for(int i=14;i>=0;i--)
	{
		if(f[x][i]!=f[y][i])
		{
			minn=min(min(g[x][i],g[y][i]),minn);//查找最短路 
			x=f[x][i];
			y=f[y][i];
		}
	}
	minn=min(min(g[x][0],g[y][0]),minn);
	return minn;
}
void read()
{
	scanf("%d%d",&n,&m);
	for(int i=1;i<=m;i++)
	{
		scanf("%d%d%d",&e[i].u,&e[i].v,&e[i].w);
	}
	for(int i=1;i<=n;i++)
	{
		father[i]=i;//父节点初始 
	}
	int cnt=0;
	sort(e+1,e+m+1,cmp);
	for(int i=1;i<=m;i++)
	{
		int fx=find(e[i].u);
		int fy=find(e[i].v);
		if(fx!=fy)
		{
			father[father[e[i].u]]=father[e[i].v];
			cnt++;
			add(e[i].u,e[i].v,e[i].w);
			add(e[i].v,e[i].u,e[i].w);
		}
		if(cnt==n-1)
		break;
	}
	for(int i=1;i<=n;i++)
	{
		if(deep[i]==0)dfs(i,1);
	}
	scanf("%d",&q);
	for(int i=1;i<=q;i++)
	{
		scanf("%d%d",&s[i],&t[i]);
		if(find(s[i])!=find(t[i]))ans[i]=-1;
		else
		ans[i]=work(s[i],t[i]);
		printf("%d\n",ans[i]);
	}
}
int main()
{
	//freopen("truck.in","r",stdin);
//	freopen("truck.out","w",stdout);
	read();
	return 0;
}
/*
4 3
1 2 4
2 3 3
3 1 1
3
1 3
1 4
1 3



3
-1
3
*/


Day2:

1.积木大赛

(block.cpp/c/pas)

【题目描述】

春春幼儿园举办了一年一度的“积木大赛”。今年比赛的内容是搭建一座宽度为n的大厦,大厦可以看成由n块宽度为1的积木组成,第i块积木的最终高度需要是ℎi。

在搭建开始之前,没有任何积木(可以看成块高度为 0 的积木)。接下来每次操作,小朋友们可以选择一段连续区间[L,R],然后将第L块到第R块之间(含第 L 块和第 R 块)所有积木的高度分别增加1。

小M是个聪明的小朋友,她很快想出了建造大厦的最佳策略,使得建造所需的操作次数最少。但她不是一个勤于动手的孩子,所以想请你帮忙实现这个策略,并求出最少的操作次数。

 

【输入】

输入文件为 block.in

输入包含两行,第一行包含一个整数n,表示大厦的宽度。

第二行包含n个整数,第i个整数为ℎi。

 

【输出】

输出文件为 block.out

仅一行,即建造所需的最少操作数。

 

【输入输出样例】

block.in

block.out

5

2 3 4 1 2

5

 

 

【样例解释】

其中一种可行的最佳方案,依次选择

[1,5]   [1,3]  [2,3]   [3,3]   [5,5]

 

【数据范围】

对于 30%的数据,有1 ≤ n ≤ 10;

对于 70%的数据,有1 ≤ n ≤ 1000;

对于 100%的数据,有1 ≤ n ≤100000,0 ≤ hi ≤ 10000。
#include<cstdio>//二分思想的暴力= = 
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=100005;
const int inf=0x7f7f7f;

int n;
int h[maxn];
int ans;
int minn;
bool visit[maxn];
void work(int l,int r)
{
	
	if(l>r) return;
	minn=inf;
	for(int i=l;i<=r;i++)
	{
		if(visit[i]==false)
		{
			minn=min(minn,h[i]);
		}
	}	
	if(minn==inf) return;//
	
	ans+=minn;
	for(int i=l;i<=r;i++) h[i]-=minn;//
	for(int i=l;i<=r;i++)
	{
		if(visit[i]==false&&h[i]==0)
		{
			visit[i]=true;
			work(l,i-1);
			work(i+1,r);
		}
	}
	return ;
}
int main()
{
	freopen("block.in","r",stdin);
	freopen("block.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&h[i]);
	}
	memset(visit,false,sizeof(visit));
	work(1,n);
	printf("%d",ans);
	return 0;
}

2.花匠

(flower.cpp/c/pas)

【问题描述】

花匠栋栋种了一排花,每株花都有自己的高度。花儿越长越大,也越来越挤。栋栋决定把这排中的一部分花移走,将剩下的留在原地,使得剩下的花能有空间长大,同时,栋栋希望剩下的花排列得比较别致。

具体而言,栋栋的花的高度可以看成一列整数ℎ1, ℎ2, … , ℎn。设当一部分花被移走后,剩下的花的高度依次为g1,g2,… , gm,则栋栋希望下面两个条件中至少有一个满足:

条件 A:对于所有的1≤i≤,有g2i  >g2i-1,同时对于所有的1≤i≤,有g2i >g2i+1

条件 B:对于所有的1≤i≤,有g2i  < g2i-1,同时对于所有的1≤i≤,有g2i <g2i+1

注意上面两个条件在 m = 1时同时满足,当 m> 1时最多有一个能满足。

请问,栋栋最多能将多少株花留在原地。

 

【输入】

输入文件为 flower.in。

输入的第一行包含一个整数,表示开始时花的株数。

第二行包含个整数,依次为ℎ1, ℎ2, … , ℎn,表示每株花的高度。

 

【输出】

输出文件为 flower.out。

输出一行,包含一个整数,表示最多能留在原地的花的株数。

 

【输入输出样例】

flower.in

flower.out

5

5 3 2 1 2

3

 

【输入输出样例说明】

有多种方法可以正好保留 3 株花,例如,留下第 1、4、5 株,高度分别为 5、1、2,满足条件 B。

 

【数据范围】

对于 20%的数据,n ≤ 10;

对于 30%的数据,n ≤ 25;

对于 70%的数据,n ≤ 1000,0 ≤ ℎn≤ 1000;

对于 100%的数据,1 ≤ n ≤ 100,000,0 ≤ ℎn≤ 1,000,000,所有的ℎn随机生成,所有随机数服从某区间内的均匀分布。

贪心加动归:
开始忘了加h[i]==h[i-1] 的情况,所以WA了2个点。
#include<cstdio>
#include<algorithm>
#include<cstring>
using namespace std;

const int maxn=100005;

int n;
int h[maxn];
int dp[maxn][2];

void work()
{
    for(int i=1;i<=n;i++)
    {
        dp[i][0]=dp[i-1][0]; 
        if(i==1||h[i]<h[i-1])
        {
        	dp[i][0]=max(dp[i-1][0],dp[i-1][1]+1);
            
        }
		dp[i][1]=dp[i-1][1];
        if(i==1||h[i]>h[i-1])
        {
        	dp[i][1]=max(dp[i-1][1],dp[i-1][0]+1);
        }
    }

}
int main()
{
	freopen("flower.in","r",stdin);
	freopen("flower.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++)
	{
		scanf("%d",&h[i]);
	}
	work();
	printf("%d\n",max(dp[n][0],dp[n][1]));
	return 0;
}
展开阅读全文

没有更多推荐了,返回首页