NOIP2017模拟(7.11)

4 篇文章 0 订阅
1 篇文章 0 订阅

T1:permut

题目描述

求由 1 到 n 一共 n 个数字组成的所有排列中,逆序对个数为 k 的有多少个。

输入格式

第一行为一个整数 T ,为数据组数。
以下 T 行,每行两个整数 n,k,意义如题目所述。

输出格式

对每组数据输出答案对 10000 取模后的结果。

分析:

   一个靠规律进行的DP,f[i][j]表示1到i组成的序列中逆序对为j的有多少,转移方程为

f[i][j]=f[i][j-1]+f[i-1][j](j<=i)

f[i][j]=f[i][j-1]+f[i-1][j]-f[i-1][j-i](j>i)

   考试的时候强行靠直觉找规律野路子(其实具体的证明可以用组合数学简单证明下正确性),找出来好开心,结果最后取模时出了点问题WA了后面7个点...以后取模一定要注意啊!

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<string>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
long long f[1010][1010];
int read()
{
	int x=0;
	char ch;
	int f=1;
	for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
	if(ch=='-')
	{
		f=-1;
		ch=getchar();
	}
	for(;ch>='0'&&ch<='9';ch=getchar())
	  x=(x<<1)+(x<<3)+ch-'0';
	return x*f;
}
void work()
{
	for(int i=1;i<=1000;++i)
	{
	    f[i][0]=1;
	    f[i][1]=i-1;
	}
	f[2][0]=1;
	f[2][1]=1;
	f[3][0]=1;
	f[3][1]=2;
	f[3][2]=2;
	f[3][3]=1;
	f[4][0]=1;
	f[4][1]=3;
	f[4][2]=5;
	f[4][3]=6;
	f[4][4]=5;
	f[4][5]=3;
	f[4][6]=1;
	for(int i=5;i<=1000;++i)
	  for(int j=1;j<=1000;++j)
	  {
	  	f[i][j]=f[i][j-1]+f[i-1][j];
	  	f[i][j]%=10000;
	  	if(j>=i) f[i][j]+=10000-f[i-1][j-i];
	  	f[i][j]%=10000;
	  }
}
int main()
{
	int t=read();
	work();
	while(t--)
	{
		int n,k;
		n=read();
		k=read();
		cout<<f[n][k]%10000<<endl;
	}
	return 0;
}
T2:beautiful

题目描述

一个长度为 n 的序列,对于每个位置 i 的数 ai 都有一个优美值,其定义是:找到序列中最长的一段 [l,r] ,满足 l≤i≤r,且 [l,r] 中位数为ai(我们比较序列中两个位置的数的大小时,以数值为第一关键字,下标为第二关键字比较。这样的话 [l,r] 的长度只有可能是奇数),r-l+1 就是 i 的优美值。

接下来有 Q 个询问,每个询问 [l,r] 表示查询区间 [l,r] 内优美值的最大值。

输入格式

第一行输入 n 。
接下来 n 个整数,代表 ai
接下来 Q ,代表有 Q 个区间。
接下来 Q 行,每行两个整数 l,r (l≤r),表示区间的左右端点。

输出格式

对于每个区间的询问,输出答案。

分析:

   n*n预处理,每个数往右往左都扫一遍,有大于他的数++,反之--,再取max值,然后就是纯RMQ问题。考试时不信邪非要直接对一整个序列的beautiful值进行维护,后来发现这样不可做,也是醉了。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<string>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
#define rep(i,l,r) for(int i=l;i<=r;++i)
#define drep(i,r,l) for(int i=r;i>=l;--i)
using namespace std;
const int N=2010;
int l[2*N],r[2*N];
int n,a[N],q,w[N];
int read()
{
	int x=0;
	char ch;
	int f=1;
	for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
	if(ch=='-')
	{
		f=-1;
		ch=getchar();
	}
	for(;ch>='0'&&ch<='9';ch=getchar())
	  x=(x<<1)+(x<<3)+ch-'0';
	return x*f;
}
int main()
{
	n=read();
	rep(i,1,n) a[i]=read();
	rep(i,1,n)
	{
		int cnt=0;
		memset(l,-1,sizeof(l));l[n]=0;
		memset(r,-1,sizeof(r));r[n]=0;
		rep(j,i+1,n)
		{
			if(a[j]>=a[i]) cnt++;
			if(a[j]<a[i]) cnt--;
			r[cnt+n]=j-i;
		}
		cnt=0;
		drep(j,i-1,1)
		{
			if(a[j]>a[i]) cnt++;
			if(a[j]<=a[i]) cnt--;
			l[cnt+n]=i-j;
		}
		rep(j,1-i,i-1)
		if(l[n+j]>=0&&r[n-j]>=0)
		{
			w[i]=max(w[i],l[j+n]+r[n-j]+1);
		}
	}
	q=read();
	rep(i,1,q)
	{
		int ans=-1;
		int x=read(),y=read();
		rep(j,x,y)
		ans=max(ans,w[j]);
		cout<<ans<<endl;
	}
	return 0;
}

T3:subset

题目描述

一开始你有一个空集,集合可以出现重复元素,然后有 Q 个操作:

1. add s
在集合中加入数字 s 。

2. del s
在集合中删除数字 s 。保证 s 存在。如果有多个 s,只删除一个即可。

3. cnt s
查询满足 a&s=a 条件的 a 的个数。

输入格式

第一行一个整数 Q 接下来 Q 行,每一行都是 3 个操作中的一个。

输出格式

对于每个 cnt 操作输出答案。

备注

【数据规模】
对于 30% 的数据满足:1≤n≤1000;
对于 100% 的数据满足:1≤n≤200000;0<s<216 。

分析:

   一看题很有可能被题面的描述迷惑,认为这是一道数据结构的水题,但实际上如果要用数据结构来维护,但实际上这样时间是无法承受的(每次询问都要扫一遍),正解应该进行分块计算,用一个数组a[pre][suf]来进行储存,具体操作如下:

每放进一个数,取出其2进制的前8位放入[pre](取出8位是因为0<s<216 ),再强行枚举0到256(2的8次方)能单独与其2进制后8位满足条件的数,每出现一个就将[pre][suf(num)]++。每次进行cnt操作,就将他给出的数后8位取出放在一旁,前8位取出枚举0到256能与他匹配的数ans+=a[枚举的合条件的数][后8位],即可得到答案。

如果不理解可以这样想:放入一个数时只将前8位保留,而后8位用合条件的数来代替,这样做和直接放入实际在后面匹配时是等价的,因为在匹配时你枚举的是前8位,后8位作为标志,这样如果后8位在之前加入过可以匹配的,那么在之前一定是被扫到了,此时前8位又从0到256进行了枚举,那么是一定不会漏掉数量的。

关于分块的思想可以在网上找点相关的资料。

代码:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<ctime>
#include<string>
#include<cstring>
#include<cstdlib>
#include<map>
#include<vector>
#include<algorithm>
using namespace std;
int n,a[256][256];
int read()
{
	int x=0;
	char ch;
	int f=1;
	for(ch=getchar();ch!='-'&&(ch<'0'||ch>'9');ch=getchar());
	if(ch=='-')
	{
		f=-1;
		ch=getchar();
	}
	for(;ch>='0'&&ch<='9';ch=getchar())
	  x=(x<<1)+(x<<3)+ch-'0';
	return x*f;
}
int main()
{
	n=read();
	char s[10];
	int x;
	for(int i=1;i<=n;++i)
	{
		scanf("%s",s);
		x=read();
		if(s[0]=='a')
		{
			for(int i=0,t=x>>8,k=x&255;i<=255;++i)
			  (t & i)==t ? ++a[i][k] : 0;
		}
		if(s[0]=='d')
		{
			for(int i=0,t=x>>8,k=x&255;i<=255;++i)
			   (t & i)==t ? --a[i][k] : 0;
		}
		if(s[0]=='c')
		{
			int ans=0;
			for(int i=0,t=x>>8,k=x&255;i<=255;++i)
			   (k & i)==i ? ans+=a[t][i] : 0;
			cout<<ans<<endl;
		}
	}
	return 0;
}

总结:这次测试有不尽人意的地方,比如第一题取模的问题,第二题有点抽了,非死磕线段树,但总体来说这次测试的意义还是很大,发现了一些不足的地方,还有T3这样以前没见过的思想。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值