View on GitHub

Wenchong Huang

旧博客,文章搬迁完后删除

广州二中NOI2018模拟赛(四十一)

返回首页 返回模拟赛列表

本场比赛试题:ProblemSet

乱搞大赛

A.修序列1

看到这题应该要非常高兴,因为题目只输入一个数!

然后就可以愉快地打表了!打出前500的表,可以拿40分

求区间方差相信小学生都会,就不说了

因为打出的表长达47KB,所以要看源码,请点这里

下面是用来打表的程序

#include<bits/stdc++.h>
using namespace std;

double a[2000];
double f[2000000];
int size=0;

void insert(double x)
{
	for(int i=1;i<=size;i++)
		if(fabs(f[i]-x)<=1e-8) return;
	f[++size]=x;
}

int main()
{
	freopen("gao.out","w",stdout);
	srand(time(0));int cnt=58,xx=0;
	for(int fk=1;fk<=500;)
	{
		int n=cnt;size=0;
		for(int i=1;i<=n;i++) a[i]=rand()%n+1;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n-i+1;j++)
			{
				double ave=0;
				for(int k=j;k<=j+i-1;k++) ave+=a[k];
				ave/=(double)i;
				double dafa=0;
				for(int k=j;k<=j+i-1;k++)
					dafa+=(a[k]-ave)*(a[k]-ave);
				dafa/=(double)i;
				insert(dafa);
			}
		if(size==fk)
		{
			printf("if(k==%d){puts(\"%d\");puts(\"",fk,n);
			for(int i=1;i<=n;i++) printf("%.0lf ",a[i]);
			puts("\");}");fk++;cnt=58;xx=0;
			fflush(stdout);
		}
		xx++;
		if(xx%700==0&&cnt<fk) cnt++;
	}
	return 0;
}

然后70分是一个神秘的分数,除我之外没有人拿到了这个分数,要么是40,要么是100,但是我研究出了一个神秘的方法,可以拿到70分这个神秘分数

首先要想到,假如你知道n以及大致的值域,然后去随机一个长度为n的序列,就会很快得到答案,这个自己试一下就知道

但现在的问题是怎么根据k快速地得到n和值域

我们上面打了一个表,表里面包含了500以内k与n的对应关系,于是我们可以用一些点来拟合n关于k函数。经过使用各种不同的函数进行尝试,最终确定了函数模型:lg-lg函数,即lg(y)=a+b*lg(x)

然后拟合,得到一个近似的函数:

这个函数在k>2200时会不准确,我们引入纠偏:

值域对方差的影响应该是:值域范围越大,区间方差越多

值域我们可以钦定为2n,然后随着rand出来的结果进行调整:如果rand出来的方差比k小,就让值域扩大n/8,如果比k大就让值域缩小n/8

为了防止我们拟合出的n还不够准确,我们在运算过程中再次引入纠偏。容易想到,如果值域已经很大了,却仍然得不到我们想要的答案,那肯定是序列长度出了问题。而序列长度越大,区间方差越多。所以如果值域扩的太大了,就让序列长度+1;如果值域缩的太小了,就让序列长度-1。调整了序列长度后应该让值域恢复成2n

方差数量的统计可以使用map,然后手写一个浮点数比较的仿函数

#include<bits/stdc++.h>
using namespace std;

double a[1000];
int size=0;

double lg(double x){return log(x)/log(10);}

struct cmp
{
	bool operator () (const double &x,const double &y) const
	{
		return x<=y-1e-8;
	}
};

map<double,bool,cmp> f;
void insert(double x)
{
	if(f.count(x)) return;
	f[x]=1;size++;
}

int main()
{
	srand(time(0));
	int n,k;
	scanf("%d",&k);
	if(k==1){puts("1");puts("1 ");}
	if(k==2){puts("2");puts("2 1 ");}
	if(k<=2) return 0;
	n=(int)round(pow(10,0.285919598401854+0.460726827413394*lg((double)k)));
	if(k>=2200) n+=k/1000-1;
	int t=16,cnt1=0,cnt2=0;
	cout<<n<<endl;
	while(true)
	{
		size=0;f.clear();
		for(int i=1;i<=n;i++) a[i]=rand()%(n*t/8)+1;
		for(int i=1;i<=n;i++)
			for(int j=1;j<=n-i+1;j++)
			{
				double ave=0;
				for(int kk=j;kk<=j+i-1;kk++) ave+=a[kk];
				ave/=(double)i;
				double dafa=0;
				for(int kk=j;kk<=j+i-1;kk++)
					dafa+=(a[kk]-ave)*(a[kk]-ave);
				dafa/=(double)i;
				insert(dafa);
				if(size>k){i=n+1;break;}
			}
		if(size==k)
		{
			printf("%d\n",n);
			for(int i=1;i<=n;i++) printf("%.0lf ",a[i]);
			break;
		}
		if(size>k&&t>0) t--;
		if(t==0) n--;
		if(size<k-10) t++;
		if(t>64) n++;
	}
	return 0;
}

100分需要有一些钦定的思想

假如有一个长度为n的随机序列,然后我们往后面插入了一个很大的数,我们就可以钦定这个序列多了n个区间方差,理由是每个包含了这个很大的数的区间,方差八成是不一样的

如果我们要弄出一些重复的区间方差,那肯定要让值域小一些,才能撞到相同方差

于是我们可以先随机一个长度短、值域小的序列,然后算出它的答案。接着我们往后面加入很大的数,并钦定每加入一个数,答案都会加上当前序列长度减1

不断往后面加数,如果什么时候当前答案=k了,就验算一下,如果没问题就直接输出这个序列。如果当前答案大于k了,就重新来一遍上述过程

根据实测结果,很快就能得到答案。

但不知道为什么,我的代码计算那些k很小的序列时会很慢,于是我加入了之前打表的一部分

#include<bits/stdc++.h>
using namespace std;

double a[1010];
int k;

struct cmp{bool operator () (const double &x,const double &y) const{return x<=y-1e-8;}};
map<double,bool,cmp> f;

int check(int n)
{
	f.clear();
	for(int i=1;i<=n;i++)
		for(int j=1;j<=n-i+1;j++)
		{
			double ave=0;
			for(int kk=j;kk<=j+i-1;kk++) ave+=a[kk];
			ave/=(double)i;
			double dafa=0;
			for(int kk=j;kk<=j+i-1;kk++)
				dafa+=(a[kk]-ave)*(a[kk]-ave);
			dafa/=(double)i;
			f[dafa]=1;
		}
	return f.size();
}

void little(int k)
{
	if(k==1){puts("1");puts("1 ");}
	if(k==2){puts("2");puts("2 1 ");}
	if(k==3){puts("3");puts("3 2 2 ");}
	if(k==4){puts("3");puts("1 3 2 ");}
	if(k==5){puts("4");puts("1 2 3 3 ");}
	if(k==6){puts("4");puts("2 4 3 3 ");}
	if(k==7){puts("4");puts("4 1 3 2 ");}
	if(k==8){puts("5");puts("2 3 5 2 2 ");}
	if(k==9){puts("5");puts("2 5 4 2 3 ");}
	if(k==10){puts("5");puts("5 5 1 4 2 ");}
	exit(0);
}

int main()
{
	srand(time(0));
	scanf("%d",&k);
	if(k<=10) little(k);
	while("STO wzt dalao")
	{
		int pre=rand()%50+1;
		for(int i=1;i<=pre;i++) a[i]=rand()%5;
		int now=check(pre);
		while(now<k)
		{
			a[++pre]=rand()%2333333;
			now+=(pre-1)?(pre-1):1;
			if(now==k)
			{
				if(check(pre)!=k) break;
				printf("%d\n",pre);
				for(int i=1;i<=pre;i++) printf("%.0lf ",a[i]);
				return 0;
			}
		}
	}
	return 0;
}

B.修序列2

题目中好像说什么“本题测试点较多,建议不会的选手在subtask#2特判一下退出”?傻逼才这么干

如果不限定询问长度,那就是一道小学生都会的大水题。我就搞一个01序列,每次枚举所有位置,然后往那里插入0或者1,如果在某个位置插入某个数成功了,就继续向更长扩展。

怎样算成功了呢?把搞出的这个序列拿去询问一下不就好了!

代入计分公式算一下,发现这个傻逼方法可以拿到31分

#include<bits/stdc++.h>
#include"hidden.hpp"
#define pb push_back
using namespace std;

std::vector<int> guess(int n)
{
	vector<int> dafa;
	for(int k=1;k<=n;k++)
	{
		vector<int> tmp=dafa;
		for(int i=0;i<=tmp.size();i++)
		{
			tmp.insert(tmp.begin()+i,1,0);
			if(is_subsequence(tmp)){dafa=tmp;break;}
			tmp[i]=1;
			if(is_subsequence(tmp)){dafa=tmp;break;}
			tmp.erase(tmp.begin()+i);
		}
	}
	return dafa;
}

正解还是要有一些乱搞思想的

我们将原序列中出现较少的数作为“分隔符”,然后去算每个“分隔符”的左边有多少个“异教徒”(“异教徒”就是不同于“分隔符”的那个数字)

计算“异教徒”时要注意控制询问串长度,如果左边算过去会超过限制长度,就从右边算过去试试

然后根据每个“分隔符”左边的“异教徒”数量,就可以确定整个01序列了

感觉也没什么好说的。这题只要想到了“分隔符”就是一道水题了

#include<bits/stdc++.h>
#include"hidden.hpp"
#define sub is_subsequence
using namespace std;

int len,limit,falun1,falun2,k;
int dafa[110];

int chk1(int x,int p)
{
	vector<int> a(p,falun1);
	a.insert(a.end(),k-x+1,falun2);
	if(a.size()>limit) return -1;
	else return sub(a);
}

int chk2(int x,int p)
{
	vector<int> a(x,falun2);
	a.insert(a.end(),p,falun1);
	if(a.size()>limit) return -1;
	else return sub(a);
}

int gao(int x)
{
	int p1=0,p2=0;
	while("STO wzt dalao")
	{
		int tmp=chk1(x,p1+1);
		if(tmp==-1){p1=-1;break;}
		if(tmp) p1++;
		else break;
	}
	while("STO wzt dalao")
	{
		int tmp=chk2(x,p2+1);
		if(tmp==-1){p2=-1;break;}
		if(tmp) p2++;
		else break;
	}
	if(~p1) return p1;
	else if(~p2) return len-k-p2;
	else return limit-(k-x+1);
}

vector<int> guess(int n)
{
	len=n;limit=n/2+1;
	if(sub(vector<int>(limit,1))) falun1=1,falun2=0;
	else falun1=0,falun2=1;
	k=n/2;
	while(k&&(!sub(vector<int>(k,falun2)))) k--;
	if(k==0){return vector<int>(n,falun1);}
	for(int i=1;i<=k;i++) dafa[i]=gao(i);
	dafa[k+1]=n-k;
	for(int i=0;i<=k;i++) dafa[i]=dafa[i+1]-dafa[i];
	vector<int> res(dafa[0],falun1);
	for(int i=1;i<=k;i++)
		res.push_back(falun2),res.insert(res.end(),dafa[i],falun1);
	return res;
}

C.修墙

20分可以乱搞

模拟题目给出的迭代方法,构造出300*300的图,然后每次询问遍历子图就好了

遍历顺序从上到下,从左到右,遍历过程中进行染色,如果某个白格子上面或左边有白格子,那么颜色就与那个格子相同,否则就新建一种颜色。容易想到,按这样的顺序更新,绝对不会使得同一个连通块内格子的颜色不同,更不可能漏掉某个连通块

#include<bits/stdc++.h>
using namespace std;

bool mp[520][520];
int color[310][310];

void build(int p)
{
	for(int i=1;i<=p;i++)
		for(int j=p+1;j<=(p<<1);j++)
			mp[i][j]=mp[i][j-p];
	for(int i=p+1;i<=(p<<1);i++)
		for(int j=1;j<=p;j++)
			mp[i][j]=mp[i-p][j];
	for(int i=p+1;i<=(p<<1);i++)
		for(int j=p+1;j<=(p<<1);j++)
			mp[i][j]=0;
}

int main()
{
	mp[1][1]=1;
	for(int i=0;i<=8;i++) build(1<<i);
	int q,x1,x2,y1,y2;
	scanf("%d",&q);
	while(q--)
	{
		memset(color,0,sizeof(color));
		scanf("%d%d%d%d",&x1,&y1,&x2,&y2);
		int dafa=0;
		for(int i=x1;i<=x2;i++)
			for(int j=y1;j<=y2;j++)
				if(mp[i][j])
				{
					if(i>x1&&mp[i-1][j]) color[i][j]=color[i-1][j];
					else if(j>y1&&mp[i][j-1]) color[i][j]=color[i][j-1];
					else color[i][j]=++dafa;
				}
		printf("%d\n",dafa);
	}
	return 0;
}

也许我离100分的做法差个脑洞……

观察这个图,发现白格子从左上角开始,形成了一个分形结构!而且这个分形结构是我们所熟悉的二叉树!

如果我们将左上角那个格子的坐标定为(0,0),不难推出,一个格子(x,y)是白格子,当且仅当x&y=0,黑格子反之

这是一个非常优秀的性质。至少不弄出图也能拿20分啦

继续利用一下这个性质,然后差分处理询问,就很容易想到本题的正解了(其实是我懒得写了)

#include<bits/stdc++.h>
using namespace std;

namespace IO
{
	const int S=1<<16;
	//Input Correlation
	char buf[S],*H,*T;
	inline char Get()
	{
		if(H==T) T=(H=buf)+fread(buf,1,S,stdin);
		if(H==T) return -1;return *H++;
	}
	void read(){}
	template<typename I,typename... Is>
	inline void read(I& x,Is&... xs)
	{
		x=0;char c=Get();
		while(!isdigit(c)) c=Get();
		while(isdigit(c)) x=x*10+c-'0',c=Get();
		read(xs...);
	}
	//Output Correlation
	char obuf[S],*oS=obuf,*oT=oS+S-1,c,qu[55];int qr;
	inline void flush()
	{
		fwrite(obuf,1,oS-obuf,stdout);
		oS=obuf;
	}
	inline void putc(char x)
	{
		*oS++ =x;
		if(oS==oT) flush();
	}
	inline void print(int x)
	{
		if(!x) putc('0');
		while(x) qu[++qr]=x%10+'0',x/=10;
		while(qr) putc(qu[qr--]);
		putc('\n');
	}
}
using namespace IO;

inline bool white(const int &x,const int &y){return !(x&y);}
inline bool black(const int &x,const int &y){return x&y;}

int query(int x,int y)
{
	int dafa=1,falun=1;
	for(int i=0;(1<<i)<=y;i++)
	{
		if(white(x,1<<i)&&black(y,1<<i)) dafa+=falun-white(x,(1<<i)-1);
		if(black(x,1<<i)&&black(y,1<<i)) dafa=falun;
		if(white(x,1<<i)) falun=(falun<<1)-white(x,(1<<i)-1);
	}
	return dafa;
}

inline int gao(int x,int l,int r)
{
	if(l==0) return query(x,r);
	return query(x,r)-query(x,l-1)+(white(l-1,x)&&white(l,x));
}

int main()
{
	int q,x1,x2,y1,y2;
	for(read(q);q;q--)
	{
		read(x1,y1,x2,y2);x1--;x2--;y1--;y2--;
		print(gao(x1,y1,y2)+gao(y1,x1,x2)-white(x1,y1));
	}
	flush();
	return 0;
}