模拟题

模拟赛

前言

说是模拟赛,只不过是借用本校\(ych\)小神仙在某某澡堂泡澡时由\(zhx\)一时兴起出的一些有趣的难度不是很大的题,据\(ych\)\(zhx\)说由于本省为弱省,所以大概\(170-180Pts\)就差不多能拿省一。故与其说是模拟赛,更不如说是乱搞练习分享赛。

话不多说,还是来看题吧。


增量幻境

\(60Pts\)

首先一个显然的想法是暴力,即从大到小枚举每一个数,直到找到第一个具有单调不减性质的数输出。

显然,要是处理不好,可能只能拿\(40Pts\),但是要是迫不得已也没办法是不是。

像是某些大佬就会拿这个\(brute\)手玩数据合理的进行对拍,以此检查算法正确性。

\(80Pts\)

说实话,我就只拿了80\(Pts\),但感觉我和正解的思路差不多,后来才知道是被毒瘤数据点给调戏淦了,所以具体在干数据的情况下去正常拿\(80Pts\)我也是不知道怎么去做的。

\(100Pts\)

首先来分析一下数据

\(0\leq x\leq 10^{10^5}\) ,复杂度肯定是和这个数的直接大小没有什么关系。

先来看样例:

\(2\ \ 3 \ \ 3\ \ 9\ \ 9\ \ 6\)

\(1\ \ 2\ \ 3\ \ 4\ \ 5\ \ 6\)(位数)

可以看出,这个样例在前五位满足单调不减的性质,直到第六位出现\(6\)

再来看看输出:

\(2\ \ 3\ \ 3\ \ 8\ \ 9\ \ 9\)

\(1\ \ 2\ \ 3\ \ 4\ \ 5\ \ 6\)(位数)

它把\(6\)变成了\(9\),第四位的\(9\)变成了\(8\)

不难想到,想要保持这个数在满足单调不减的性质上最大,和\(9\)这个数是有直接关系的。

样例中第四位的\(9\)变成了\(8\),也就是说在这一位之后都填\(9\)一定是最好的方案

那么显然能想到这道题的关键就是看从哪一个位置开始之后全填9

看出来关键就是在于从左到右第一个不满足单调不减性质的数

还是对于样例

\(2\ \ 3\ \ 3\ \ 9\ \ 9\ \ 6\)

当我们发现最后一位的\(6\)不满足性质时,我们考虑把它变成\(9\)。这样的话前面的\(9\)需要减一才能满足大小关系。

\(2\ \ 3\ \ 3\ \ 9\ \ 8\ \ 9\)

这样的话再把第四位减一,后面全填\(9\),即\(2\ \ 3\ \ 3\ \ 8 \ \ 9\ \ 9\) 得到答案

那么就有了一个初步想法:从第一个不满足的位置开始,这个位置之后全填\(9\),这个位置之前逐步减一,如果不满足条件就再进行相同的处理

经过和暴力的对拍,可以发现这种做法是正确的

你以为这样就可以\(100Pts\)

要是你输出的东西有前导\(0\),你同样\(GG\),但是在去前导\(0\)的时候别忘记加上\(0\)这种情况的特判(要不然你输出什么

据说是\(Day 2\ T1\ +\)难度

代码如下
#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;

const int maxn=100010;

char s[maxn],minv[maxn];

int main()
{
	freopen("increase.in","r",stdin);
	freopen("increase.out","w",stdout);
	scanf("%s",s+1);
	int l=strlen(s+1);
	bool able=true;
	for (int a=2;a<=l && able;a++)
		if (s[a]<s[a-1])
		{
			int p=a-1;
			while (p>=1 && s[p]==s[a-1])
				p--;
			p++;
			s[p]--;
			for (int b=p+1;b<=l;b++)
				s[b]='9';
			break;
		}
	int p=1;
	while (p<l && s[p]=='0')
		p++;
	printf("%s\n",s+p);

	return 0;
}

乘积求和

T2

再来康康\(T2\)

\(60Pts\)

说实话,看到这个题的时候我首先想到的就是写一个\(brute\)

说干就干

#include<iostream>
using namespace std;
inline long long read()
{
	long long a=0,b=1;
	char c=getchar();
	while(!isdigit(c))
	{
		if(c=='-')
			b=-1;
		c=getchar();
	}
	while(isdigit(c))
	{
		a=(a<<3)+(a<<1)+(c^48);
		c=getchar();
	}
	return a*b;
}
long long n,s[40005],ans,mod=1e12+7;
int main()
{
	freopen("multiplication.in","r",stdin);
	freopen("multiplication.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
		s[i]=read();
	for(int l=1;l<=n;++l)
		for(int r=l;r<=n;++r)
			for(int i=l;i<=r;++i)
				for(int j=i+1;j<=r;++j)
					if(s[j]<s[i])
						ans=(ans+s[j]*s[i])%mod;
	printf("%lld",ans);
	return 0;
}

后来突然发现可以搞搞逆序对得到\(80Pts\)

于是就有了

\(80Pts\)

翻译一下题目:

求每个子区间里面逆序对的乘积和

分为两个步骤

1.看有多少个子区间包含\(i,j\)

子区间的个数就是\(i*(n-j+1)\)

枚举所有的逆序对,算一下就可以了

2.发现最后两层循环可以预处理。\(f[i][j]\)表示\(i\)\(j\)的答案,最后统计一下

for(int i=1;i<=n;i++)
	for(int j=i;j<=n;j++)
	{
		f[i][j]=f[i][j-1];
		if(a[j]<a[i])
            f[i][j]=(f[i][j]+a[i]*a[j]%mod)%mod;
	}
for(int i=1;i<=n;i++)
	for(int j=i+1;j<=n;j++)
		ans=(ans+i*f[i][j])%mod;

具体一下:

#include<iostream>
#include<cstdio>
#include<cmath>
#include<cstring>
#include<algorithm>
#include<vector>
#include<queue>
#include<map>
#define pa pair<int,int>
using namespace std;
typedef long long ll;
typedef unsigned long long ull;
inline ll read()
{
	char ch=getchar();
	ll x=0; 
	bool f=0;
	while(ch<'0'||ch>'9')
	{
		if(ch=='-') f=1;
		ch=getchar();
	}
	while(ch>='0'&&ch<='9')
	{
		x=(x<<3)+(x<<1)+(ch^48);
		ch=getchar();
	}
	return f?-x:x;
}
const ll mod=1000000000007;
ll n,a[40009],sum[2509][2509],ans[3009][3009];
int main()
{
	freopen("multiplication.in","r",stdin);
	freopen("multiplication.out","w",stdout);
	n=read();
	for(int i=1;i<=n;i++)
	 a[i]=read();
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
			sum[i][j]=sum[i][j-1]+((a[i]>a[j])?(ull)(a[i]*a[j]+mod)%mod:0);
			sum[i][j]=(sum[i][j]+mod)%mod;
		}
	}
	for(int j=n;j>=1;j--)
	{
		for(int i=j-1;i>=1;i--)
		{
			ans[i][j]=(ans[i+1][j]+sum[i][j]+mod)%mod;
		}
	}
	ll aan=0;
	for(int i=1;i<=n;i++)
		for(int j=i+1;j<=n;j++)
			aan=(aan+ans[i][j]+mod)%mod;
    printf("%lld",aan);
}

\(100Pts\)

直接干\(AC\)做法吧,我觉得其实\(90Pts\)\(100Pts\)得分差不多。。。

模数为\(10^{12}+7\)

众所周知,两个\(10^{12}\)的数乘起来会爆\(long\ \ long\)

快速龟速乘了解一下

这个玩意可以将乘法做到\(log(n)\)所以有时候(比如说正常乘法运算上可能会不如直接\(\times\))但是这里显然能多得分【

主要是按照乘法的定义

\(a\times b=\displaystyle \sum_1^b a\)

\(a^b=(a^{\frac{b}{2}})\times a^{b\ mod\ 2}\)

比如\(a^{33}=a^{16}*a^{16}*a\)

每一次乘的范围保证在\(long\ \ long\)里就可以模了

#include<cstdio>
#include<cstdlib>
#include<cstring>

using namespace std;

#define maxv 1000000000000ll
#define wmt 1,maxv,1
#define lson l,m,z[rt].l
#define rson m+1,r,z[rt].r
#define inc(a,b) {a+=b;if (a>=mo) a-=mo;}
#define newnode ++cnt

const int maxn=100010;
const long long mo=1000000000007ll;

int n,cnt=1;

struct node
{
	int l,r;
	long long sum=0;
	node(){l=r=0,sum=0;}
}z[maxn<<3|1];

void update(int rt)
{
	z[rt].sum=z[z[rt].l].sum;
	inc(z[rt].sum,z[z[rt].r].sum);
}

long long query(long long l,long long r,int rt,long long nowl,long long nowr)
{
	if (!rt) return 0;
	if (nowl<=l && r<=nowr) return z[rt].sum;
	long long m=(l+r)>>1;
	long long ans=0;
	if (nowl<=m) ans=query(lson,nowl,nowr);
	if (m<nowr) inc(ans,query(rson,nowl,nowr));
	return ans;
}

void modify(long long l,long long r,int rt,long long p,long long v)
{
	if (l==r)
	{
		inc(z[rt].sum,v);
		return;
	}
	long long m=(l+r)>>1;
	if (p<=m) 
	{
		if (!z[rt].l) z[rt].l=newnode;
		modify(lson,p,v);
	}
	else 
	{
		if (!z[rt].r) z[rt].r=newnode;
		modify(rson,p,v);
	}
	update(rt);
}

long long mul(long long a,long long b)
{
	long long ans=0;
	while (b)
	{
		if (b&1)
		{
			ans+=a;
			if (ans>=mo) ans-=mo;
		}
		a<<=1;
		if (a>=mo) a-=mo;
		b>>=1;
	}
	return ans;
}

int main()
{
	freopen("multiplication.in","r",stdin);
	freopen("multiplication.out","w",stdout);
	scanf("%d",&n);
	long long ans=0;
	for (int a=1;a<=n;a++)
	{
		long long v,vx;
		scanf("%I64d",&v);
		vx=v;
		v = mul(v,query(wmt,v+1,maxv));
		v = v*(n-a+1)%mo;
		ans += v;
		if (ans >= mo) ans-=mo;
		modify(wmt,vx,1ll*a*vx%mo);
	}
	printf("%I64d\n",ans);
	
	return 0;
}

叠加矩阵

没办法,为了保证题面的优美,我截了三张图(

显然题目很好理解

所以我们就可以\(rand\)

算了正经一下

先看面积是否符合要求

暴力:两两判断矩形是否相交

\(30Pts\)

暴力:两两判断矩形是否相交

\(60Pts\)

另外\(30 Pts\)所有的矩形都在\(400\times400\)以内

每有一个矩形就把对应的区域染色,如果有已经染过色的区域,就不合法

\(O(n+400\times400)\) 因为每个格子只会被染一次色

这里还有神仙直接用二维的差分数组硬生生的\(brute\),还是康康代码吧

#include<cstdio>
#include<algorithm>
#include<cstring>
#include<iostream>

using namespace std;

inline int read() {
	int ans=0;
	char last=' ',ch=getchar();
	while(ch>'9'||ch<'0') last=ch,ch=getchar();
	while(ch>='0'&&ch<='9') ans=(ans<<1)+(ans<<3)+ch-'0',ch=getchar();
	if(last=='-') ans=-ans;
	return ans;
}

const int inf=2147483647;
int T,n;
int D[5050][5050];
int s[5050][5050];
bool bj;

void clear() {
	bj=0;
	memset(D,0,sizeof(D));
	memset(s,0,sizeof(s));
}

int main() {
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	T=read();
	while(T--) {
		n=read();
		clear();
		int minx=inf,miny=inf,maxx=-1,maxy=-1;
		for(int i=1,a,b,c,d; i<=n; i++) {
			a=read();
			a+=2500;
			minx=min(minx,a);
			maxx=max(maxx,a);
			b=read();
			b+=2500;
			miny=min(miny,b);
			maxy=max(maxy,b);
			c=read();
			c+=2499;
			minx=min(minx,c);
			maxx=max(maxx,c);
			d=read();
			d+=2499;
			miny=min(miny,d);
			maxy=max(maxy,d);
			D[a][d+1]-=1;
			D[c+1][b]-=1;
			D[c+1][d+1]+=1;
			D[a][b]+=1;
		}
		for(int i=minx; i<=maxx; i++) {
			for(int j=miny; j<=maxy; j++) {
				s[i][j]=s[i][j-1]+s[i-1][j]-s[i-1][j-1]+D[i][j];
				if(s[i][j]!=1) {
					bj=1;
					printf("Guguwansui\n");
					break;
				}
			}
			if(bj==1) break;
		}
		if(!bj)
			printf("Perfect\n");
	}
	return 0;
}
//差分数组D:D[x1][y2+1]-=1  D[x2+1][y1]-=1  D[x2+1][y2+1]+=1  D[x1][y1]+=1;
//s[x][y]=s[x][y-1]+s[x-1][y]-s[x-1][y-1]+D[x][y]

不行,码风太丑看不了看不了

\(100Pts\)

有两种做法,一种是叫做扫描线的东西(从\(luogu\)上找到了一个形象的动图

很形象吧(雾

\(y\)轴建一棵线段树

扫描到一号矩形的左边界,就占据线段树上\(2-5\)的区间,就在这个区间打一个标记

二号矩形同理

相当于插入一个矩形左边界的操作

这个时候有四个格子被占据,此时在边界上就是满足的

然后继续把扫描线向右平移。只有当扫描到另外的边界时才会改变

当扫描到\(3\)\(4\)时,先把\(1\)\(2\)对应的区间标记成没有标记过,然后把\(3\)\(4\)对应的区间标记成覆盖过

在扫描线的过程中看看有没有地方没有覆盖或者覆盖了两次

一种相对更简单的做法

考虑一组合法的解里面的每一个点有什么性质:点分三种:在角上,边上,中间

角上的点最多出现一次并且是某一个矩形的角

边上的点合法的条件是这个点是一个矩形的一个角和另一个矩形的另一个角。比如:上边界的点是一个矩形的右上角,另一个矩形的左上角

以一个点为原点画坐标轴,则每个象限都有且只有一个矩形(把边界之外的补齐)

中间的点有两种情况:一是四个象限都有矩形;二是有一个矩形覆盖了两个象限

如果一个点是一个矩形的某个角的话,就把相应的象限标为\(true\),最后检查是不是每一个点的每一个象限都恰好被标记了一次

如果是就合法

排序加二分

前提:先检查过面积

还可以简化:检查完四个角,四条边和面积后,检查中间的点是出现了四次还是两次

如果所有点都出现了四次或者两次就合法

至于为啥,咱也不知道,咱也不敢问(\(bushi\)

康康代码:

#include<cstdio>
#include<cstdlib>
#include<cstring>
#include<algorithm>

using namespace std;

#define INF 2147483647
#define maxn 100010

int n,rectangles[maxn][4];

struct point
{
	int x,y,d;
	point(){}
	point(int a,int b,int c)
	{
		x=a;y=b;d=c;
	}
}p[maxn<<2],q[maxn<<2];

bool operator<(const point &a,const point &b)
{
	if (a.x!=b.x) return a.x<b.x;
	return a.y<b.y;
}

bool cmp(const point &a,const point &b)
{
	if (a.y!=b.y) return a.y<b.y;
	return a.x<b.x;
}

struct line
{
	int x,y,l,r,d;
	line(){}
	line(int a,int b,int c,int e)
	{
		x=a;y=a;l=b;r=c;d=e;
	}
}vert[maxn<<2],hori[maxn<<2];

bool operator<(const line &a,const line &b)
{
	if (a.x!=b.x) return a.x<b.x;
	if (a.l!=b.l) return a.l<b.l;
	return a.r<b.r;
}

bool isRectangleCover() {
	int minx=INF,maxx=-INF,miny=INF,maxy=-INF;
	for (int a=0;a<n;a++)
	{
		minx = min(minx,rectangles[a][0]);
		maxx = max(maxx,rectangles[a][2]);
		miny = min(miny,rectangles[a][1]);
		maxy = max(maxy,rectangles[a][3]);
	}
	long long s=(0ll+maxx-minx)*(0ll+maxy-miny);
	for (int a=0;a<n;a++)
		s -= (0ll+rectangles[a][2]-rectangles[a][0])*(0ll+rectangles[a][3]-rectangles[a][1]);
	if (s) return false;
	int m=0,n1=0,n2=0;
	for (int a=0;a<n;a++)
	{
		p[++m]=point(rectangles[a][0],rectangles[a][1],1);
		p[++m]=point(rectangles[a][2],rectangles[a][1],2);
		p[++m]=point(rectangles[a][2],rectangles[a][3],4);
		p[++m]=point(rectangles[a][0],rectangles[a][3],8);
		vert[++n1]=line(rectangles[a][1],rectangles[a][0],rectangles[a][2],3);
		vert[++n1]=line(rectangles[a][3],rectangles[a][0],rectangles[a][2],12);
		hori[++n2]=line(rectangles[a][0],rectangles[a][1],rectangles[a][3],9);
		hori[++n2]=line(rectangles[a][2],rectangles[a][1],rectangles[a][3],6);
	}
	sort(p+1,p+m+1);
	sort(vert+1,vert+n1+1);
	sort(hori+1,hori+n2+1);
	int k=0;
	for (int a=1;a<=m;)
	{
		int b=a+1,s=p[a].d,x=p[a].x,y=p[a].y;
		while (b<=m && p[b].x==x && p[b].y==y)
		{
			if (s&p[b].d) return false;
			s|=p[b].d;
			b++;
		}

		int news=0;
		if (x==minx) news|=6;
		if (x==maxx) news|=9;
		if (y==miny) news|=12;
		if (y==maxy) news|=3;
		if (news&s) return false;
		s|=news;
		q[++k] = point(x,y,s);

		a=b;
	}
	sort(q+1,q+k+1);
	for (int a=1;a<=n2;a++)
	{
		int x=hori[a].x,y1=hori[a].l,y2=hori[a].r,s=hori[a].d;
		int l=0,r=k;
		while (l+1!=r)
		{
			int m=(l+r)>>1;
			if (q[m].x>x || (q[m].x==x && q[m].y>y1)) r=m;
			else l=m;
		}
		while (r<=k && q[r].x==x && q[r].y<y2)
		{
			if (q[r].d&s) return false;
			q[r].d|=s;
			r++;
		}
	}

	sort(q+1,q+k+1,cmp);
	for (int a=1;a<=n1;a++)
	{
		int y=vert[a].y,x1=vert[a].l,x2=vert[a].r,s=vert[a].d;
		int l=0,r=k;
		while (l+1!=r)
		{
			int m=(l+r)>>1;
			if (q[m].y>y || (q[m].y==y && q[m].x>x1)) r=m;
			else l=m;
		}
		while (r<=k && q[r].y==y && q[r].x<x2)
		{
			if (q[r].d&s) return false;
			q[r].d|=s;
			r++;
		}
	}
	return true;

	/*int l=0,r=n1;
	  while (l+1!=r)
	  {
	  int m=(l+r)>>1;
	  if (vert[m].y >= y) r=m;
	  else l=m;
	  }
	  while (r<=n1 && vert[r].y==y)
	  {
	  if (x>vert[r].l && x<vert[r].r)
	  {
	  if (s&vert[r].d) return false;
	  s|=vert[r].d;
	  }
	  r++;
	  }
	  l=0,r=n2;
	  while (l+1!=r)
	  {
	  int m=(l+r)>>1;
	  if (hori[m].x >= x) r=m;
	  else l=m;
	  }
	  while (r<=n1 && hori[r].x==x)
	  {
	  if (y>hori[r].l && y<hori[r].r)
	  {
	  if (s&hori[r].d) return false;
	  s|=hori[r].d;
	  }
	  r++;
	  }
	  if (s!=15) return false;*/
	return true;
}

int main()
{
	freopen("matrix.in","r",stdin);
	freopen("matrix.out","w",stdout);
	int t;
	scanf("%d",&t);
	for (;t--;)
	{
		scanf("%d",&n);
		for (int a=0;a<n;a++)
			for (int b=0;b<4;b++)
				scanf("%d",&rectangles[a][b]);
		if (isRectangleCover()) printf("Perfect\n");
		else printf("Guguwansui\n");
	}

	return 0;
}

还是比较玄学的是吧(

posted @ 2019-11-12 08:36  卍GC卐  阅读(155)  评论(0编辑  收藏  举报