目录
一.题目
1.队列变换
1.1.题目描述
1.2.题解
这道题挺灵活的,可以当做一个模板来记住可蒟蒻我当时就是没想到
根据题目描述可以知道,每操作一次相当于操作一行或一列,所以我们把每一行和每一列都请个代表出来,为了方便,代表们就是第一行和第一列。我们通过变换将代表们都变成同一个方向,如图:
(代表)R | (代表)R | (代表)R |
(代表)R | X | X |
(代表)R | X | X |
那么那一个讨厌的站反的人就存在于三个地方:
1. (1,1):必须满足非代表的地方朝向是L,降第一行和第一列翻转即可
2. 除了(1,1)的代表:必须满足代表所在的那一行或那一列的人朝向都是L,其他人的的朝向都是R
3. 非代表的人:必须满足除了他自己站反以外,其他人都是R。
如果都不满足,输出-1即可。
模板:对于每次操作都要改变一个区间的题目,把每个改变的区间都请个代表出来。
1.3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
#define M 1005
int n, a[M][M], ansx, ansy, sum;
char c;
int main (){
scanf ("%d", &n);
for (int i = 1; i <= n; i ++){
scanf ("\n");
for (int j = 1; j <= n; j ++){
scanf ("%c", &c);
a[i][j] = c == 'R' ? 1 : 0;
}
}
for (int i = 1; i <= n; i ++){
if (a[1][i]){
for (int j = 1; j <= n; j ++)
a[j][i] = (a[j][i] + 1) % 2;
}
}
for (int i = 2; i <= n; i ++){
if (a[i][1]){
for (int j = 1; j <= n; j ++)
a[i][j] = (a[i][j] + 1) % 2;
}
}
bool flag = 0, f = 0;
for (int i = 2; i <= n; i ++){
for (int j = 2; j <= n; j ++){
if (! a[i][j])
flag = 1;
if (a[i][j]){
sum ++;
if (! f)
ansx = i, ansy = j, f = 1;
else
ansx = -1, ansy = -1;
}
}
}
if (! flag){
printf ("1 1\n");
return 0;
}
if (ansx > 0){
printf ("%d %d\n", ansx, ansy);
return 0;
}
if (sum >= n){
printf ("-1\n");
return 0;
}
for (int i = 2; i <= n; i ++){
int s = 0;
for (int j = 2; j <= n; j ++)
s += a[j][i];
if (s == n - 1){
printf ("1 %d\n", i);
return 0;
}
}
for (int i = 2; i <= n; i ++){
int s = 0;
for (int j = 2; j <= n; j ++)
s += a[i][j];
if (s == n - 1){
printf ("%d 1\n", i);
return 0;
}
}
printf ("-1\n");
return 0;
}
2.跨栏
2.1.题目描述
2.2.题解
这道题是全场最难的题目,我可能有点口胡。
算法:set加扫描线,时间复杂度:
通过题目中说的只要删除一条线就一定能使其它线不相交,那么只要找到两条相交的线,其中一根就一定是要删除的线。关键是怎么找呢?
先来讨论一下怎么判断线的相交,这里要用到叉乘:
不会的先自己了解一下。
我们现在要判断两条线段是否交叉,我们可以取一条线段的一个端点和其他一条线段的两个段点分别连接,如图:
如果P1Q2和P1Q1两条线段的叉乘值的符号和P1Q1和P1P2两条线段叉乘值的符号相同,就可以判断出来P2和Q2分别在线段P1Q1的两面。
再同理,连另一条线段:
都判断一下就行了。如果两种情况都满足符号相同,就说明两条线段相交。
再来讨论如何找出两条相交的线段:
我们用set存储每一条线段。
我们要先把每一条线按横坐标从小到大排序,如果横坐标相同就按纵坐标从小到大排序。
然后用一条扫描线从左到右扫描,每扫描到一条线段的左端点,那么在将这条线段加入set之前,先判断一下这条线段是否和它左右的两条线段相交(左右两条线段都存在在set中,先用lower_bound找到这条线段加入后的位置,就是这个位置和前面那个位置就是这条线段的左右两条线段);每扫描到一条线段的右端点,就要删除线段,那么在删除线段的时候就判断这个线段的左右两条线段是否相交就行了。
最后找到相交的两条线段了,通过找其中一条线段与其他线段的相交次数就能确定是这两条线段中那条线段了。一定是相交次数大于1的那一条线段。
确实有点绕,然看代码还挺好理解的。
2.3.Code
#include <cstdio>
#include <cstring>
#include <iostream>
#include <vector>
#include <set>
#include <cstdlib>
#include <algorithm>
using namespace std;
#define M 100005
#define LL long long
struct Point{
LL x, y;
int Index;
};
struct node {
Point a, b;
int nodeindex;
};
int n, ans1, ans2;
double X;
set <node> Se;
vector <node> S;
vector <Point> P;
bool operator < (Point A, Point B){
if (A.x == B.x)
return A.y < B.y;
return A.x < B.x;
}
int pd (LL A){
if (! A)
return 0;
return A > 0 ? 1 : -1;
}
int operator * (Point A, Point B){
return pd ((A.x * B.y) - (A.y * B.x));
}
Point operator - (Point A, Point B){
Point p;
p.x = A.x - B.x, p.y = A.y - B.y;
return p;
}
bool itcc (node A, node B){
Point p1 = A.a, q1 = A.b, p2 = B.a, q2 = B.b;
return ((q2 - p1) * (q1 - p1)) * ((q1 - p1) * (p2 - p1)) >= 0 && ((q1 - q2) * (p2 - q2)) * ((p2 - q2) * (p1 - q2)) >= 0;
}
double eval (node A){
if (A.a.x == A.b.x)
return A.a.y;
return A.a.y + (A.b.y - A.a.y) * (X - A.a.x) / (A.b.x - A.a.x);
}
bool operator < (node A, node B){
return A.nodeindex != B.nodeindex && eval (A) < eval (B);
}
bool operator == (node A, node B){
return A.nodeindex == B.nodeindex;
}
int main (){
scanf ("%d", &n);
for (int i = 0; i < n; i ++){
node ss;
scanf ("%lld %lld %lld %lld", &ss.a.x, &ss.a.y, &ss.b.x, &ss.b.y);
ss.nodeindex = ss.a.Index = ss.b.Index = i;
S.push_back (ss);
P.push_back (ss.a), P.push_back (ss.b);
}
sort (P.begin (), P.end ());
for (int i = 0; i < P.size (); i ++){
ans1 = P[i].Index; X = P[i].x;
set <node>::iterator it = Se.find (S[ans1]);
if (it != Se.end ()){
set <node>::iterator hou = it;
hou ++;
set <node>::iterator qian = it;
if (hou != Se.end () && qian != Se.begin ()){
qian --;
if (itcc (S[qian -> nodeindex], S[hou -> nodeindex])){
ans1 = qian -> nodeindex, ans2 = hou -> nodeindex;
break;
}
}
Se.erase (it);
}
else{
set <node>::iterator it = Se.lower_bound (S[ans1]);
if (it != Se.end () && itcc (S[it -> nodeindex], S[ans1])){
ans2 = it -> nodeindex;
break;
}
if (it != Se.begin ()){
it --;
if (itcc (S[it -> nodeindex], S[ans1])){
ans2 = it -> nodeindex;
break;
}
}
Se.insert (S[ans1]);
}
}
if (ans1 > ans2)
swap (ans1, ans2);
int ans2_count = 0;
for (int i = 0; i < n; i ++){
if (S[i].nodeindex != ans2 && itcc (S[i], S[ans2]))
ans2_count ++;
}
if (ans2_count > 1)
printf ("%d\n", ans2 + 1);
else
printf ("%d\n", ans1 + 1);
return 0;
}
3.三角形
3.1.题目描述
题目描述
平面上有n行m列,一共n*m个方格,从上到下依次标记为第1,2,...,n行,从左到右依次标记为第1,2,...,m列,方便起见,我们称第i行第j列的方格为(i,j)。小Q在方格中填满了数字,每个格子中都恰好有一个整数a_{i,j}。小Q不 喜欢手算,因此每当他不想计算时,他就会让你帮忙计算。小Q一共会给出q个询问,每次给定一个方格(x,y)和一个整数k(1<=k<=min(x,y)),你需要回答由(x,y),(x-k+1,y),(x,y-k+1)三个格子构成的三角形边上以及内部的所有格子的a的和。
输入格式
第一行包含6个正整数n,m,q,A,B,C(1<=n,m<=3000,1<=q<=3000000,1<=A,B,C<=1000000) 其中n,m表示方格纸的尺寸,q表示询问个数。 为了防止输入数据过大,a和询问将由以下代码生成:
unsigned int A,B,C;
inline unsigned int rng61(){
A ^= A << 16;
A ^= A >> 5;
A ^= A << 1;
unsigned int t = A;
A = B;
B = C;
C ^= t ^ A;
return C;
}
int main(){
scanf("%d%d%d%u%u%u", &n, &m, &q, &A, &B, &C);
for(i = 1; i <= n; i++)
for(j = 1; j <= m; j++)
a[i][j] = rng61();
for(i = 1; i <= q; i++){
x = rng61() % n + 1;
y = rng61() % m + 1;
k = rng61() % min(x, y) + 1;
}
}
输出格式
为了防止输出数据过大,设f_i表示第i个询问的答案,则你需要输出一行一个整数,即:
输入样例
3 4 5 2 3 7
输出样例
3350931807
3.2.题解
殊不知这道题可能是最简单的一道题,可惜当时我没认真读题!
可以从题目描述中看出,每个要求的三角形都是等腰直角三角形,就是这样:
图中红色部分就是要求的三角形,我们可以通过图中的大矩形减去那个灰色的梯形多边形得到三角形。
怎么求大矩形和梯形多边形中所有数的和呢?很明显,二维的前缀和呀!
怎么样,一目了然吧。
3.3.Code
#include <iostream>
#include <cstdio>
#include <cstring>
#include <algorithm>
#include <cmath>
#include <queue>
#include <cstdlib>
using namespace std;
const int MAXN =3003;
#define ll unsigned int
unsigned int A,B,C,x,y,k;
unsigned int a[3003][3003];
ll d[3000003];
int n , m , q;
inline unsigned int rng61(){
A ^= A << 16;
A ^= A >> 5;
A ^= A << 1;
unsigned int t = A;
A = B;
B = C;
C ^= t ^ A;
return C;
}
ll sum[3003][3003] , sum2[MAXN][MAXN] , sumh[MAXN][MAXN];
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();//求前缀和
sum[i][j] = sum[i][j-1] + sum[i-1][j] - sum[i-1][j-1] + a[i][j];
sumh[i][j] = sumh[i][j-1] + a[i][j];sum2[i][j] = sum2[i-1][j+1] + sumh[i][j];
}
sum2[i][0] = sum2[i-1][1];
}
d[0] = 1;
for( int i = 1 ; i <= q ; i ++ )
d[i] = d[i-1] * 233;
ll ans = 0;
for( int i = 1; i<= q; i++){
x = rng61() % n + 1;
y = rng61() % m + 1;
k = rng61() % min(x, y) + 1;
ll tot = d[q-i] * ( sum[x][y] - sum[x-k][y] - (sum2[x][y-k] - sum2[x-k][y] ) ) ;
ans = ans + tot;
}
printf( "%u" , ans );
return 0;
}
二.总结
这次考试可谓暴露了我许多问题,我认为主要是自己没有拼尽全力,读题没有整体把握,有以下几点改善:
1. 每次考试要一直集中精力,就算骗分也要做,实在坚持不住可以去厕所洗脸;
2. 一开始要用半个小时读题并理解每一道题,整体把握;
3. 想思路时不要太局限,图论和数论要发散思维,而且完全有可能题目本身根本没有什么算法,就像第一题。
检查的时候要做到以下几点:
1.最后十分钟检查,别补程序
2.列检查清单:1.肉眼:长整型、变量名;2.动手:造小样例;3.生成极限数据测试是否超时;4.写暴力对拍,生成比较特殊的情况
每次考试结束要深层分析错误,要把每次考试都当做真正的NOIP比赛一般,别掉以轻心。
每日的练靶,我瞄准的就是NOIP提高组。