NOIP2016提高组题解

\(D1T1\) 玩具谜题\((OK)\)

\(D1T2\) 天天爱跑步

\(D1T3\) 换教室\((OK)\)

\(D2T1\) 组合数问题\((OK)\)

\(D2T2\) 蚯蚓\((OK)\)

\(D2T3\) 愤怒的小鸟\((OK)\)

\(2016\)年的只有一道毒瘤题没做\(!!!\)

\(D1T1\)简单模拟就好,字符串(名字/职业)与数字编号的映射直接用\(map\)就好.然后朝内向左=朝外向右,朝内向右=朝外向左.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
map<int,string>rev;
const int N=1e5+5;
int cx[N];
int main(){
	int n,m;cin>>n>>m;
	for(int i=1;i<=n;++i){
		int x;cin>>x;string s;cin>>s;
		rev[i]=s;cx[i]=x;
	}
	int now=1;
	while(m--){
		int x,y;cin>>x>>y;
		if(!x){
			if(!cx[now]){
				now-=y;if(now<=0)now+=n;
			}
			else{
				now+=y;if(now>n)now-=n;
			}
		}
		else{
			if(!cx[now]){
				now+=y;if(now>n)now-=n;
			}
			else{
				now-=y;if(now<=0)now+=n;
			}
		}
	}
	cout<<rev[now]<<endl;
    return 0;
}

\(D1T2\)咕咕咕

\(D1T3\)不久前谢总讲概率期望的时候写过.博客

\(D2T1\)因为每一次的\(k\)都是不变的,然后看一下数据范围猜测一下是\(n^2\)预处理.然后组合数是可以递推的,因为\(\sum_{i=0}^n\sum_{j=0}^i\)是一个三角矩阵,所以我们递推出\(f[i][j]\)表示\(C_i^j\)之后(处理的时候就取模),可以用二维前缀和来维护\(f[i][j]=0\)的数量(就如果取模之后等于零,就把矩阵中的这个位置赋值为\(1\),其它都是\(0\),然后就可以二维前缀和处理了).感觉讲的不是很清楚,代码还是很好懂的.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
 #define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=2005;
int f[N][N],g[N][N],sum[N][N];
int main(){
	int T=read(),k=read();
	f[0][0]=1;
	for(int i=1;i<=2000;++i){
		f[i][0]=1;
		for(int j=1;j<=i;++j){
			f[i][j]=(f[i-1][j-1]+f[i-1][j])%k;
			if(!f[i][j])g[i][j]=1;
		}
	}
	for(int i=1;i<=2000;++i)
		for(int j=1;j<=2000;++j)
			sum[i][j]=g[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
	while(T--){
		int n=read(),m=read();
		printf("%d\n",sum[n][m]);
	}
    return 0;
}

\(D2T2\)写了整整一个晚上.

讲个笑话:

刚开始好不容易才写出个\(mlog_m\)的算法,以为就是正解,结果交上去\(T\)\(3\)个点,心想:肯定是\(pq\)的常数大了,先不开\(O_2\),先自己卡一下常.

然后就去卡了半个小时的常,发现还是\(T\)\(3\)个点.

最后自信满满地开了一波\(O_2\),心想肯定能过,结果还是\(T\)\(3\)个点.

然后再去仔细看一下数据范围,才发现\(mlog_m\)根本过不了.正解必须是\(O(n)\)的.

\(lyh\):\(85\)分都是送分的啊,直接开一个堆或者\(multiset\)来模拟整个过程就好了.

(我写个堆的做法,还写了一个多小时,太菜了~~~)

讲正解.发现题目隐藏的单调性,就是我们每次选当前最大的拎出来,拆成两个部分,一个部分大,一个部分小,然后两个部分分开来看,在每个部分中,先拆出的一定总是比后拆出的大.

所以我们可以把堆换成三个数组(一个是初始的蚯蚓数组,一个是拆成的较小的那一部分,一个是拆成的较大的那一部分),三个数组中的元素一定都是单调递减的,然后每次要选最大的元素的时候就比较三个数组的队头即可.

先把\(mlog_m\)的做法放出来.

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
priority_queue<ll>Q;
int main(){
	int n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
	for(int i=1,st;i<=n;++i)st=read(),Q.push(st);
	for(int i=1;i<=m;++i){
		ll x=Q.top();Q.pop();x+=1ll*(i-1)*q;
		if(!(i%t))printf("%lld ",x);
		ll y=(ll)x*u/v;
		Q.push(y-q*i);Q.push(x-y-q*i);
	}printf("\n");
	for(int i=1;i<=n+m;++i){
		if(!(i%t))printf("%lld ",Q.top()+1ll*m*q);
		Q.pop();
	}printf("\n");
    return 0;
}

正解做法(获取性质之后就是一顿模拟了)

#include<iostream>
#include<cstdio>
#include<algorithm>
#include<cstring>
#include<cmath>
#include<queue>
#include<map>
#include<set>
#define ll long long
using namespace std;
inline int read(){
    int x=0,o=1;char ch=getchar();
    while(ch!='-'&&(ch<'0'||ch>'9'))ch=getchar();
    if(ch=='-')o=-1,ch=getchar();
    while(ch>='0'&&ch<='9')x=x*10+ch-'0',ch=getchar();
    return x*o;
}
const int N=1e5+5;
const int M=7e6+5;
int n,m,q,u,v,t;
int a[N],q1[M],q2[M];
inline int check1(int tim,int i,int j,int k){
	int x=a[i]+(tim-1)*q,y=q1[j]+(tim-1-j)*q,z=q2[k]+(tim-1-k)*q;
	if(x>=y&&x>=z)return 1;
	if(y>=x&&y>=z)return 2;
	return 3;
}
inline int check2(int tim,int j,int k){
	int y=q1[j]+(tim-1-j)*q,z=q2[k]+(tim-1-k)*q;
	if(y>=z)return 2;
	return 3;
}
int main(){
	n=read(),m=read(),q=read(),u=read(),v=read(),t=read();
	for(int i=1;i<=n;++i)a[i]=read();sort(a+1,a+n+1,greater<int>());
	int a1=1,l1=1,l2=1,r1=0;
	for(int i=1;i<=m;++i){
		int en;
		if(a1<=n)en=check1(i,a1,l1,l2);//注意初始数组的数可能都用完了
		else en=check2(i,l1,l2);
		if(en==1){
			int x=a[a1]+(i-1)*q;
			if(!(i%t))printf("%d ",x);
			int y=(ll)x*u/v;x-=y;//x*u会爆int
			q1[i]=min(x,y);q2[i]=max(x,y);++a1;
			continue;
		}
		if(en==2){
			int x=q1[l1]+(i-1-l1)*q;
			if(!(i%t))printf("%d ",x);
			int y=(ll)x*u/v;x-=y;
			q1[i]=min(x,y);q2[i]=max(x,y);++l1;
			continue;
		}
		if(en==3){
			int x=q2[l2]+(i-1-l2)*q;
			if(!(i%t))printf("%d ",x);
			int y=(ll)x*u/v;x-=y;
			q1[i]=min(x,y);q2[i]=max(x,y);++l2;
			continue;
		}
	}printf("\n");
	for(int i=1;i<=n+m;++i){
		int en;
		if(a1<=n)en=check1(i,a1,l1,l2);
		else en=check2(i,l1,l2);
		if(en==1){
			if(!(i%t))printf("%d ",a[a1]+m*q);
			++a1;
		}
		if(en==2){
			if(!(i%t))printf("%d ",q1[l1]+(m-l1)*q);
			++l1;
		}
		if(en==3){
			if(!(i%t))printf("%d ",q2[l2]+(m-l2)*q);
			++l2;
		}
	}printf("\n");
    return 0;
}

\(D2T3\)不久前考过,今天借此发现了以前博客的一个锅.

posted on 2019-10-31 17:05  PPXppx  阅读(224)  评论(0编辑  收藏  举报