NOIP 2015模拟赛 题解&总结

时间:2022-12-17 08:10:52

这场比赛总的来讲不是很难,区分度并不高,第一题属于思维训练类型,第二题是一个比较新的DP题,第三题转化一下就是贪心+背包了

总的来说,要根据题目中的已知条件来判断出题人的意图,比如第一题给出的范围<=500摆明了就是有诈……

【联训赛10.16】丢钉子

Time Limit:20000MS  Memory Limit:165536K
Total Submit:87 Accepted:42
Case Time Limit:1000MS

Description

学校里一年一度的自行车大赛又开始了!!可是ZL同学却非常不高兴,因为他不会骑自行车!所以他决定干扰这次比赛。他已经了解到了这次参加比赛的m名参赛选手的资料。他决定要进行一次惊天动地的干扰。
我们假设比赛场地是一个从起点处向右和向前无限延伸的跑道。编号为1到m的参赛队员从左到右并列排列。从比赛开始后的第1秒末,第2秒末,第3秒末,第4秒末……第m秒末他分别会投一枚钉子到当前排名第一的自行车的前面使其爆胎!(爆胎的自行车自动退出比赛,不再计入排名),当有多个人同时并列第一时,由于ZL童鞋在起点左侧的观众席,他总是丢在最接近左侧边缘的那个第一名参赛选手前(字典序最小的第一名)。
而参赛队员的资料只有两个,一个是他第一秒能前进的距离Vi,一个是他第一秒末后每秒秒能前进的距离Ai。
现在要你来求,每秒钟,ZL童鞋把钉子丢在了编号为几的参赛选手前。

Input

第一行为一个整数m,表示有m名参赛选手。
接下来有m行,分别表示编号为 i(从1到m)的参赛队员的数据,每行两个整数Vi,Ai。

Output

一行,m个整数,第i个数,表示第i秒退赛的选手的编号

Sample Input

样例1
3
100 1
100 2
3 100

样例2
5
1 1
2 2
3 3
4 1
3 4

Sample Output

样例1:
1 3 2

样例2:
4 5 3 2 1

Hint

【样例1说明】
第一组测试数据
第1秒
选手1前进到100米处。
选手2前进到100米处。
选手3前进到 3 米处。
此时选手1、2并列第一,ZL把钉子丢在1号选手前,1号选手退出比赛。

第2秒
选手2前进到102米处。
选手3前进到103米处。
此时选手3第一。ZL把钉子丢在3号选手前,3号选手退出比赛。

第3秒
选手2前进到104米处。
此时选手2第一。ZL把钉子丢在2号选手前,2号选手退出比赛。

1 <= m <= 50000
0 <= Vi <= 500
0 < Ai <= 100

Source

hdu4393

这题开始看起来毫无头绪,反正只要涉及到修改元素就是n^2的复杂度,因此不考虑对元素进行修改

我们发现虽然m的范围很大,但是vi,ai的范围比较小,这就是此题的突破口

显然对于第0秒,两个人的距离最大为500,即一个人(A)在0处,另一个(B)在500处

这个时候如果B的速度大于A的速度,那么A肯定是追不上B的,显然此时排名不变

如果A的速度刚刚大于B的速度,即Va==Vb+1,那么A只需500秒就可以追上B

因此我们发现,不管选手的初始位置,速度如何分布,500秒之后的排名肯定就已经不变了

所以我们只需暴力模拟前500秒的情况,然后对500秒末进行排序处理就可以了

#include<cstdio> 
#include<iostream>  
#include<algorithm>
using namespace std; 
const int maxn=5e4+5,inf=0x3f3f3f3f,Mark=517; 
inline void _read(int &x){ 
    char t=getchar();bool sign=true; 
    while(t<'0'||t>'9') 
    {if(t=='-')sign=false;t=getchar();} 
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0'; 
    if(!sign)x=-x; 
}
int n,s[maxn],d[maxn],tot;
bool vis[maxn];
struct wk{
	int id,dist;
	bool operator<(const wk& h)const{
		return dist==h.dist?id<h.id:dist>h.dist;
	}
}qust[maxn];
void solve1(){
	int i,j;
	for(i=0;i<n;i++){
		int maxx=-inf,id;
		for(j=1;j<=n;j++){
			if(vis[j])continue;
			s[j]+=i?d[j]:0;
			if(maxx<s[j])maxx=s[j],id=j;
		}
		vis[id]=1;
		printf("%d ",id);
	}
}
void solve2(){
	int i,j;
	for(i=1;i<=Mark;i++){
		int maxx=-inf,id;
		for(j=1;j<=n;j++){
			if(vis[j])continue;
			if(maxx<s[j]+(i-1)*d[j])maxx=s[j]+(i-1)*d[j],id=j;
		}
		vis[id]=1;
		printf("%d ",id);
		if(i==n)break;
	}
	for(i=1;i<=n;i++){
		if(vis[i])continue;
		qust[++tot].dist=s[i]+(Mark+1)*d[i];
		qust[tot].id=i;
	}
	sort(qust+1,qust+1+tot);
	for(i=1;i<=tot;i++)printf("%d ",qust[i].id);
}
int main(){
	_read(n);
	for(int i=1;i<=n;i++){_read(s[i]);_read(d[i]);}
	if(n<=2000)solve1();
	else solve2();
}

【联训赛10.16】数字分配

Time Limit:20000MS  Memory Limit:165536K
Total Submit:19 Accepted:14
Case Time Limit:1000MS

Description

有n个整数排成一行,编号1到n。现在给你A,B两个空盒子,需要你从这n个数中取出一些数,放入A,B两个盒子里。但要求A盒子里的数字的编号必须小于B盒子中数字的编号(也就是在原始序列中,A中所有数字均在B中数字的左侧),并且每个盒子里至少要有一个数字。

若将A盒子的所有数字进行XOR(异或)运算后的结果等于将B盒子的所有数字进行AND(与)运算的结果,我们认为这是一个“合理分配”。问,总共有多少种不同的“合理分配”方案?结果可能很大,mod 10^9+7再输出。

Input

第一行,一个整数n
第二行,n个空格间隔的整数,表示编号1到n的数字。

Output

一行,一个整数,表示所求的答案。

Sample Input

样例1:
3
1 2 3

样例2:
4
1 2 3 3

Sample Output

样例1:
1

样例2:
4

Hint

【样例1解释】
A:1 2 (1^2=3)
B:3
【样例2解释】
方案1:
A:1 2 (1^2=3)
B:3
方案2:
A:1 2
B:3
方案3:
A:1
B:2 3 (2&3=1)
方案4:
A: 1
B: 2 3
【数据范围】
n<=1000
0 <= 给出的数字 <1024

Source

hdu4901

将数列分为左右两部分,显然分界点是十分特殊的,因此我们用f[i][j]表示前i个数异或得到j的所有方案数(s[i]必选)

g[i][j]表示第i~n个数与得到j的所有方案数(s[i]可能选)

这里f数组容易递推求得,但是g数组无法由其它状态转到现状态(不信可以试一试),因此只能由现状态推到其他状态

最后计算ans就比较简单了(这里是没有重复的情况的,想一想为什么(提示:和所表示的状态有关)),详情见代码

#include<cstdio>
#include<iostream>
#define LL long long
using namespace std;
const LL maxn=1026,mod=1e9+7;
inline void _read(LL &x){
    char t=getchar();bool sign=true;
    while(t<'0'||t>'9')
    {if(t=='-')sign=false;t=getchar();}
    for(x=0;t>='0'&&t<='9';t=getchar())x=x*10+t-'0';
    if(!sign)x=-x;
}
LL n,f[maxn][maxn],s[maxn],sum[maxn],ans,g[maxn][maxn];
int main(){
	_read(n);
	LL i,j;
	for(i=1;i<=n;i++)_read(s[i]);
	f[1][s[1]]=sum[s[1]]=g[n][s[n]]=1;
	for(i=2;i<=n;i++){
		for(j=0;j<=maxn;j++)f[i][j]=(sum[j^s[i]]+(j==s[i]))%mod;
		for(j=0;j<maxn;j++)(sum[j]+=f[i][j])%=mod;
	}
	for(i=n-1;i;i--){
		for(j=0;j<maxn;j++)(g[i][j&s[i]]+=g[i+1][j])%=mod,(g[i][j]+=g[i+1][j])%=mod;
		g[i][s[i]]++;
	}	
	for(j=0;j<maxn;j++)
		for(i=1;i<n;i++)(ans+=f[i][j]*g[i+1][j]%mod)%=mod;
	cout<<ans;
}

【联训赛10.16】盒子涂色

Time Limit:10000MS  Memory Limit:165536K
Total Submit:6 Accepted:3
Case Time Limit:1000MS

Description

有n个盒子排成一行,编号1到n。一开始,所有的盒子都是黑色的。
有如下两种操作:
“1 ai xi”:你可以从编号1到ai间( [1,ai] )的黑盒子中任选xi个,将它们涂成白色;
“2 ai xi”:你可以从编号ai到n间( [ai,n] )的黑盒子中任选xi个,将它们涂成白色;
给出若干操作的序列,问最多能得到多少个白色的盒子?
得到最多的白色盒子最少需要使用多少个操作?
注意:
1.对于给出的操作,不一定要按给出的顺序执行。
2.每个操作,你都可以选择使用,也可以不使用它。
3.若一个操作对应的区间中没有足够的黑盒子,该操作将无法进行。

Input

第一行,两个整数n和m,表示有n个盒子,m个操作。
接下来m行,每行三个整数 si(1<=si<=2) , ai , xi (0 <= xi <= n,1<=ai<=n),
si代表操作的类型, ai和xi如题目描述。

Output

一行,两个空格间隔的整数,第一个表示最多能得到的白色盒子数,第二个表示最少所需的操作数。

Sample Input

样例1:
5 2
2 3 3
1 3 3

样例2:
10 6
2 10 2
2 9 3
1 5 4
2 5 1
1 6 2
2 8 0

Sample Output

样例1:
3 1

样例2:
7 3

Hint

【样例2解释】
采用了下列三个操作
1 5 4
2 5 1
1 6 2
操作1 5 4和1 6 2 将1到6全部涂成了白色
操作2 5 1 在7到10间任选一个涂成了白色
总共7个白色
【数据范围】
1 <= N <= 1000 , 1<=M<=1000

Source

hdu4381

这道题的难点就在于盒子涂色是无序且混乱的,第一眼看根本无从下手

我们可以尝试着让这个盒子变得有序,即贪心让盒子先涂满两边,再涂中间

这个贪心原则是显然正确的,运用贪心原则最后就是一个简单的背包,然后就搞定了

#include<cstdio>
#include<iostream>
#include<algorithm>
#include<cstring>
using namespace std;
const int maxn=1005,inf=0x3f3f3f3f;
int n,m,f1[maxn],f2[maxn],ans1=-inf,ans2=inf,cnt1,cnt2;
struct wk{int a,x;}Move1[maxn],Move2[maxn];
bool cmp1(wk a,wk b){return a.a==b.a?a.x>b.x:a.a<b.a;}
bool cmp2(wk a,wk b){return a.a==b.a?a.x>b.x:a.a>b.a;}
int main(){
	int i,j,type;
	scanf("%d%d",&n,&m);
	for(i=1;i<=m;i++){
		scanf("%d",&type);
		if(type==1){
			cnt1++;
			scanf("%d%d",&Move1[cnt1].a,&Move1[cnt1].x);
		}
		else {
			cnt2++;
			scanf("%d%d",&Move2[cnt2].a,&Move2[cnt2].x);
		}
	}
	sort(Move1+1,Move1+1+cnt1,cmp1);
	sort(Move2+1,Move2+1+cnt2,cmp2);
	memset(f1,inf,sizeof(f1));
	memset(f2,inf,sizeof(f2));
	f1[0]=f2[0]=f1[n+1]=f2[n+1]=0;
	for(i=1;i<=cnt1;i++)
		for(j=Move1[i].a;j>=Move1[i].x;j--)
			f1[j]=min(f1[j],f1[j-Move1[i].x]+1);
	for(i=1;i<=cnt2;i++)
		for(j=Move2[i].a;j+Move2[i].x<=n+1;j++)
			f2[j]=min(f2[j],f2[j+Move2[i].x]+1);
	for(i=0;i<=n;i++)
		for(j=i+1;j<=n+1;j++)
			if(f1[i]<inf&&f2[j]<inf)ans1=max(ans1,i+n+1-j);
	for(i=0;i<=ans1;i++)ans2=min(ans2,f1[i]+f2[n-ans1+i+1]);
	printf("%d %d",ans1,ans2);
}
最后再说两句:

打开思维,一定要打开思维,平常的思维训练题一定要多听多看多想,一道题尝试用不同的方法解决!

(NOIP一周倒计时)