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飞。。前缀和优化是个很重要的技巧。