目錄
一.題目
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提高組。