NOIP模拟赛0815

T1

题目链接:洛谷P5427
题目大意:一个n*n的01矩阵,可以任意翻转一行或者一列,不限次数,最终要得到一个有且仅有一个点与其他点不同的矩阵,问这个点可能是哪个点,输出最靠前的一个。n<=1000。

题目分析:

这种棋盘翻转题,需要考虑一定的顺序,固定一些东西。
因为可以整体翻转,所以我们不妨定下来(1,1)的点不动,这意味着第一行和第一列不能再翻转。
然后看第一行和第一列的点,如果与(1,1)不同,就翻转它所在列/行,使得第一行和第一列相同。
在这里插入图片描述
在不改变红色区域的情况下,蓝色区域不能被改变。
如果答案为(1,1),此时只需翻转第一行和第一列,翻转后不能再操作,所以蓝色区域必定全与(1,1)不同。
如果答案在蓝色区域,由于红色区域不能变,所以只有目标位置与(1,1)不同。
如果答案在第一行或第一列(除(1,1)外),此时翻转对应列/行,翻转后同样不能再操作,所以应当只有对应的一整行/列与(1,1)不同。
如果不满足上述条件,则无解。

Code:

#include<bits/stdc++.h>
#define maxn 1005
#define x first
#define y second
using namespace std;
int n,a[maxn][maxn];
char s[maxn];
typedef pair<int,int> pii;
pii solve(){
	int O = a[1][1];
	for(int k=2;k<=n;k++){
		if(a[1][k]!=O) for(int i=1;i<=n;i++) a[i][k]=!a[i][k];
		if(a[k][1]!=O) for(int j=1;j<=n;j++) a[k][j]=!a[k][j];
	}
	int sum=0,X,Y;
	for(int i=2;i<=n;i++) for(int j=2;j<=n;j++) if(a[i][j]!=O) sum++,X=i,Y=j;
	if(sum==(n-1)*(n-1)) return pii(1,1);
	if(sum==1) return pii(X,Y);
	if(sum!=n-1) return pii(-1,-1);
	for(int j=2;j<=n;j++){
		bool now=1;
		for(int i=2;i<=n;i++) if(a[i][j]==O) {now=0;break;}
		if(now) return pii(1,j);
	}
	for(int i=2;i<=n;i++){
		bool now=1;
		for(int j=2;j<=n;j++) if(a[i][j]==O) {now=0;break;}
		if(now) return pii(i,1);
	}
	return pii(-1,-1);
}
int main()
{
	//freopen("transitioning.in","r",stdin);
	//freopen("transitioning.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++) {scanf("%s",s+1);for(int j=1;j<=n;j++) a[i][j]=(s[j]=='R');}
	pii ans=solve();
	if(ans.x==-1) puts("-1");
	else printf("%d %d\n",ans.x,ans.y);
}

PS:考试的时候想到枚举答案点,然后把对应行列相同的格子翻转,再判断是否符合条件,这样是n4的,有50分。然而CSQ用神奇的骗分技巧 bitset优化,在翻转的时候用&和^判断是否符合,不符合中途退出,就rush过去了(震惊。。。)

T2

题目链接:洛谷P5428
题目大意:有n条线段,保证去掉一条后剩余的线段不相交,找出这条线段。n<=100000。

题目分析:

似乎是个经典题目。扫描线。
关键在于找到相交的两条线段,找到之后O(n)判断是哪条即可。

把线段放到set中,按照x轴做扫描线,set中按当前横坐标对应的线段纵坐标排序,扫到一个左端点就加入这条线段,判断此时在它上面和下面的线段和它是否相交,如果相交就找到了答案,如果不相交,说明直到下一次操作之前,set中线段的纵坐标的顺序不会改变。扫到右端点时从set中删除线段,判断在它上面和下面的线段是否相交。 形象化地理解就是线段隔线段。

Code:

#include<bits/stdc++.h>
#define maxn 100005
#define LL long long
using namespace std;
const double eps = 1e-12;
inline int dcmp(const double &x){return x<-eps?-1:x>eps?1:0;}
int n,X,L1,L2;
struct Point{
	int x,y; Point(){}
	Point(int x,int y):x(x),y(y){}
	Point operator - (const Point &p)const{return Point(x-p.x,y-p.y);}
	Point operator + (const Point &p)const{return Point(x+p.x,y+p.y);}
	LL operator * (const Point &p)const{return 1ll*x*p.y-1ll*y*p.x;}
	bool operator < (const Point &p)const{return x==p.x?y<p.y:x<p.x;}
};
struct Line{
	Point p,v; double k; int id;
	bool operator < (const Line &L)const{
		double Y1 = p.y+k*(X-p.x), Y2 = L.p.y+L.k*(X-L.p.x);
		return dcmp(Y1-Y2)?Y1<Y2:id<L.id;
	}
}a[maxn];
struct Opt{
	int id,op,x;
	bool operator < (const Opt &B)const{return x==B.x?op>B.op:x<B.x;}
}Q[maxn*2];
set<Line>S;
set<Line>::iterator it,it2;
bool Cross(const Line &a,const Line &b){
	if(dcmp(a.v*(b.p-a.p))*dcmp(a.v*(b.p+(b.v-a.p)))>0) return 0;
	if(dcmp(b.v*(a.p-b.p))*dcmp(b.v*(a.p+a.v-b.p))>0) return 0;
	return 1;
}
int check(int L1,int L2){
	if(L1>L2) swap(L1,L2);
	for(int i=1;i<=n;i++) if(i!=L1&&i!=L2){
		if(Cross(a[L1],a[i])) return L1;
		if(Cross(a[L2],a[i])) return L2;
	}
	return L1;
}
int main()
{
	//freopen("jump.in","r",stdin);
	//freopen("jump2.out","w",stdout);
	scanf("%d",&n);
	for(int i=1;i<=n;i++){
		scanf("%d%d%d%d",&a[i].p.x,&a[i].p.y,&a[i].v.x,&a[i].v.y);
		if(a[i].v<a[i].p) swap(a[i].v,a[i].p);
		Q[i*2-1].id=Q[i*2].id=i,Q[i*2-1].x=a[i].p.x,Q[i*2].x=a[i].v.x;
		Q[i*2-1].op=1,Q[i*2].op=-1;
		a[i].v=a[i].v-a[i].p,a[i].id=i;
		if(a[i].v.x) a[i].k=1.0*a[i].v.y/a[i].v.x;
	}
	sort(Q+1,Q+1+2*n);
	for(int i=1;i<=2*n;i++){
		X=Q[i].x;
		if(Q[i].op==1){
			it = S.lower_bound(a[Q[i].id]);
			if(it!=S.end()&&Cross(*it,a[Q[i].id])) {L1=(*it).id,L2=Q[i].id;break;}
			if(it!=S.begin()){
				it--; if(Cross(*it,a[Q[i].id])) {L1=(*it).id,L2=Q[i].id;break;}	
			}
			S.insert(a[Q[i].id]);
		}
		else{
			S.erase(a[Q[i].id]);
			it = S.lower_bound(a[Q[i].id]);
			if(it!=S.end()&&it!=S.begin()){
				it2=it,it--; if(Cross(*it,*it2)) {L1=(*it).id,L2=(*it2).id;break;}
			}
		}
	}
	printf("%d\n",check(L1,L2));
}

PS:考试的时候做的是n2暴力只有可怜的10分,然而CSQ又用神奇的骗分技巧 把线段排序后加上剪枝优化(按x排序,顺序循环,当前线段与下一条在x轴上不重合,那么与以后的也不会相交),就rush到了90分。。。这启示我们要骗分就要多加小优化。。

T3

题目大意:n*m的网格,每个格子上有数,Q次询问,每次问一个直角边水平竖直、直角顶点为(x,y)、直角边边长为k的三角形的和。在这里插入图片描述

题目分析:

在这里插入图片描述
如图,所求红色三角形=大三角形-蓝色三角形-绿色矩形。只需维护矩形前缀和和三角形前缀和即可。

Code:

#include<bits/stdc++.h>
#define maxn 3005
using namespace std;
typedef unsigned int ui;
int n,m,Q;
ui A,B,C,a[maxn][maxn],ans,pw[3000005],t[maxn][maxn],r[maxn][maxn];
inline ui rng61(){
    A ^= A << 16;
    A ^= A >> 5;
    A ^= A << 1;
    ui t = A;
    A = B;
    B = C;
    C ^= t ^ A;
    return C;
}
int main()
{
	//freopen("triangle.in","r",stdin);
	//freopen("triangle.out","w",stdout);
	scanf("%d%d%d%u%u%u",&n,&m,&Q,&A,&B,&C);
	for(int i = 1; i<= n; i++)
        for(int j = 1; j <= m; j++)
            a[i][j] = rng61();
    for(int i=1;i<=n;i++) for(int j=1;j<=m;j++) r[i][j]=r[i-1][j]+r[i][j-1]-r[i-1][j-1]+a[i][j];
    for(int i=n;i>=1;i--) for(int j=1;j<=m;j++) t[i][j]=t[i+1][j-1]+r[n][j]-r[n][j-1]-r[i-1][j]+r[i-1][j-1];
    pw[0]=1;
    for(int i=1;i<=Q;i++) pw[i]=pw[i-1]*233;
    for(int i=1;i<=Q;i++){
        int x = rng61() % n + 1;
        int y = rng61() % m + 1;
        int k = rng61() % min(x, y) + 1;
		ans+=pw[Q-i]*(t[x-k+1][y]-t[x+1][y-k]-(r[n][y]-r[n][y-k]-r[x][y]+r[x][y-k]));
    }
    printf("%u",ans);
}

PS:考试的时候压根没想到还有前缀和这种东西。。。求矩形下意识就想二维树状数组,凭空多出两个log然后T飞。。前缀和优化是个很重要的技巧。

NOI(全国青少年信息学奥林匹克竞模拟的测试数据是指用于评测参选手的程序的输入和对应的输出。测试数据是非常重要的,因为它决定了参选手的程序能否正确地解决问题。 在NOI模拟中,测试数据具有以下特点: 1.充分覆盖:测试数据应涵盖各种可能的输入情况,包括边界条件和极端情况。通过提供不同的测试数据,可以考察选手对问题的全面理解和解决能力。 2.随机性和均衡性:为了公平起见,测试数据应该是随机生成的,而不是针对某个特定算法或解法设计的。同时,测试数据应该是均衡的,即各种情况的概率应该大致相等,以避免偏向某些解法。 3.合理性和可行性:测试数据应该是合理和可行的,即符合题目要求的输入数据,并且是选手能够通过编写程序来处理的。测试数据应该考虑到程序的限制和时间复杂度,以充分测试选手的编程能力。 NOI模拟的测试数据通常由经验丰富的考题组负责生成。他们会根据题目的要求和限制,设计出一组合理、充分、随机和均衡的测试数据,以确保参选手的程序在各种情况下都能正确运行,并且能通过性能测试。 总之,测试数据在NOI模拟中起到了至关重要的作用,它既考察了选手对问题的理解和解决能力,又提高了选手编程的技巧和效率。同时,合理和恰当的测试数据也是公平竞的保证,确保每个参选手有相同的机会和条件进行竞争。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值