URL: Dashboard - Codeforces Round #749 (Div。 1 + Div。 2, based on Technocup 2022 Elimination Round 1) - Codeforces

A。Windblume Ode

題意:已知一個

(1,2,3,....,200)

的子集

S

|S|\ge 3

,從中找到

S

S

中所有元素之和是合數,且

|S

最大化。

分析:

先判斷集合所有元素是否滿足條件。如可以則輸出,返回。

隨後列舉去掉其中一個元素是否滿足條件,如可以則輸出,返回。

不需要再列舉其他的情況,理由如下:

因為

n\ge 3

,設總和是偶數,則一定是合數。

設總和是奇數,則其中一定含有某奇數,去掉該數後,則和為偶數,(且因為元素個數為

n-1\ge 2

),一定是合數。

程式碼:

bool

prime

int

x

{

for

int

i

=

2

i

<=

sqrt

x

);

i

++

{

if

x

%

i

==

0

return

false

}

return

true

}

void

solve

()

{

int

n

cin

>>

n

int

tot

=

0

int

a

n

+

1

];

for

int

i

=

1

i

<=

n

i

++

cin

>>

a

i

],

tot

+=

a

i

];

if

prime

tot

))

{

cout

<<

n

<<

endl

for

int

i

=

1

i

<=

n

i

++

cout

<<

i

<<

“ ”

cout

<<

endl

return

}

else

{

for

int

i

=

1

i

<=

n

i

++

{

if

prime

tot

-

a

i

]))

{

cout

<<

n

-

1

<<

endl

for

int

j

=

1

j

<=

n

j

++

{

if

i

==

j

continue

cout

<<

j

<<

“ ”

}

cout

<<

endl

return

}

}

}

}

B。Omkar and Heavenly Tree

題意:請構造一棵

n

個節點的樹,滿足

m

個限制條件,且

m<n

每個限制條件如下:

b_i

不能在

a_i,c_i

的路徑上。

分析:取某沒有作為

b_i

出現的節點作為根節點

root

,根節點連線其他所有節點構成深度為

2

的樹。

則任何的

a_i,c_i

一定是經過三個節點:

a_i,c_i,root

,不會經過其他的節點,所以條件一定滿足。

程式碼:

void

solve

()

{

int

n

m

cin

>>

n

>>

m

set

<

int

>

s

for

int

i

=

1

i

<=

n

i

++

s

insert

i

);

for

int

i

=

1

i

<=

m

i

++

{

int

a

b

c

cin

>>

a

>>

b

>>

c

s

erase

b

);

}

int

num

=

*

s

begin

();

for

int

i

=

1

i

<=

n

i

++

{

if

i

==

num

continue

cout

<<

num

<<

“ ”

<<

i

<<

endl

}

}

C。 Omkar and Determination

題意:已知一個由‘。’和‘X’構成的迷宮,其中‘。’表示空白格子,‘X’表示有障礙物。從某個格子出發,可以走到

左方或上方的空白格子

。如果從某個格子出發,可以到達最左側的格子或最上方的格子,則該格子是可脫離迷宮的。用E表示。反之,則用N表示。

已知一個迷宮,它有對應一個的EN矩陣,而如果EN矩陣所對應的迷宮是唯一的,則稱該迷宮是“確定”的。現求迷宮的

[x_1,x_2]

列所對應的子迷宮,是否是確定的。

例如,下方所示的迷宮是不可確定的:

Codeforces Round #749 (Div. 1 + Div. 2, based on Technocup 2022 Elimination Round 1) A~E

分析:

在整個迷宮中,如果一個點的左方和上方都是‘X’,則其是不可確定的。該點一定是不可脫離的(‘N’),但不論它是‘。’還是‘X’都對應‘N’,所以該點的狀態是不可確定的。我們記這一點是整個迷宮的“

關鍵點

”。如果子迷宮(第一列除外)中有該點,則子迷宮不可確定。

如果一個子迷宮不含有上述性質的點,則迷宮一定是確定的。證明如下:

所有點都可以向左方或上方拓展,則所有‘。’點一定是可脫離的。所以若將‘。’改為‘X’,該點的性質將改變。而若將‘X’改為‘。’,則它也將變為可脫離的,該點的性質將改變。

所以,記錄下所有滿足上述性質的點所對應的列號,查詢即可。

(昨天以為每個點可以向四個方向拓展,結果一直wa。。。。。)

int

main

()

{

int

n

m

cin

>>

n

>>

m

string

s

n

];

for

int

i

=

0

i

<

n

i

++

cin

>>

s

i

];

set

<

int

>

t

for

int

i

=

0

i

<

n

i

++

for

int

j

=

0

j

<

m

j

++

{

if

i

==

0

||

j

==

0

||

s

i

-

1

][

j

==

‘。’

||

s

i

][

j

-

1

==

‘。’

))

t

insert

j

+

1

);

}

t

insert

9999999

);

int

q

cin

>>

q

while

q

——

){

int

x

y

cin

>>

x

>>

y

if

*

t

upper_bound

x

<=

y

cout

<<

“NO

\n

else

cout

<<

“YES

\n

}

return

0

}

D。 Omkar and the Meaning of Life

題意:有一個

(1,2,...n)

的排列

p

,使用最多

2n

次詢問機會,猜出這個排列。

詢問格式如下:選擇一組

a_1,a_2,...a_n

,將構造陣列

b=(a_1+p_1,a_2+p_2....a_n+p_n)

,返回下標

i

,使得

b[i]

b

陣列中出現了至少兩次。如果有多個下標滿足條件,返回最小的那個;如果沒有下標滿足條件,返回

0

分析:考慮特殊情況,①如果詢問

2\ 2 \ ...\ 2 \ 1\ 2 \ ...\ 2

,其中第

x

位放

1

,其餘放

2

,則一定只能找出一對下標

(x,y)

滿足

a_x+p_x=a_y+p_y

,其中應當有

p_x-p_y=1

。但是由於函式只會返回一個數據,即

min(x,y)

。如果

y<x

就能得到

p_x-p_y=1

的資訊,反之則無法得到有用的資訊。

對於這種詢問方法,我們可以得知在

x

左側且比

x

1

的元素。

②如果詢問

1\ 1\ ...\ 1\ 2 \ 1\ ...\ 1

,其中第

x

位放

2

,其餘放

1

,同理可以獲知在

x

左側且比

x

1

的元素。

我們對

1..n

分別進行上述兩種詢問,正好用了

2n

次詢問機會。這樣,如果兩個元素相差一,則這種關係一定能在上述兩種詢問之一被探尋出——設

p_x-p_y=1

,若

x>y

,則對於

x

採用第一種詢問時能得出;若

x<y

,則對於

y

採用第二種詢問時能得出。

得知了比各個數大

1

的元素的位置後,我們就能很輕鬆地還原出原排列了。

程式碼:

int

main

()

{

cin

>>

n

int

next

n

+

1

],

ans

n

+

1

];

//next陣列表示比當前位置值大1的位置。

for

int

i

=

1

i

<=

n

i

++

{

cout

<<

“? ”

for

int

j

=

1

j

<=

n

j

++

if

i

==

j

cout

<<

1

<<

“ ”

else

cout

<<

2

<<

“ ”

cout

<<

endl

int

tmp

cin

>>

tmp

if

tmp

!=

i

next

tmp

=

i

//分別對應兩種詢問

cout

<<

“? ”

for

int

j

=

1

j

<=

n

j

++

if

i

==

j

cout

<<

2

<<

“ ”

else

cout

<<

1

<<

“ ”

cout

<<

endl

cin

>>

tmp

if

tmp

!=

i

next

i

=

tmp

}

int

cnt

=

0

now

=

next

0

];

while

now

ans

now

=

++

cnt

now

=

next

now

];

cout

<<

“! ”

for

int

i

=

1

i

<=

n

i

++

cout

<<

ans

i

<<

“ ”

return

0

}

E。 Moment of Bloom

題意:已知無向連通圖,其中指定

q

組點對,為每組

(a_i,b_i)

分配一條路徑,並使路徑上所有的邊權值加一,問是否有方法使得圖中的每條邊的權值均為偶數,如有,給出方法;如沒有,給出最少需要額外新增的點對數量,使得條件成立。

分析:題述條件成立,當且僅當每個點在點對中都出現了偶數次。證明如下:

記某一點

v

在點對中出現的次數為

f_v

①反設某一點

v

出現了奇數次,則考慮以

v

為端點的邊的權值和

w

——

一條以

v

開始的路徑或一條以

v

結尾的路徑將帶來

1

的貢獻

一條穿過

v

(不以它結尾)的路徑將帶來

2

的貢獻;沒有穿過則帶來

0

的貢獻。

所以,

w

一定是奇數。所以其中至少有一條邊的權值是奇數。

②設每一點都出現了偶數次,取包含這些點的某一生成樹。

反設某條邊

e

被經過奇數次——我們設這條邊將樹分成兩個部分,有奇陣列點從該邊的一側到達另一側。考慮其中一側的所有點出現的次數

f_v

之和

s

——

從這一側到達另一側的一條路徑將帶來

1

的貢獻

從這一側某點到另一點的一條路徑將帶來

2

的貢獻

所以,這個和一定是奇數,即一定有某一點

v

滿足

f_v

是奇數,矛盾。

綜上,若每一點的

f_v

都是偶數,則條件成立,取任意生成樹和某一根,輸出兩點在樹上的路徑即可。若某一點的

f_v

是奇數,則條件不成立,輸出NO。需要補充一些詢問使得所有點的出現次數均為偶數。

尋找生成樹的過程,可以用並查集實現。

程式碼:

struct

dsu

{

vector

<

int

>

p

dsu

int

n

){

p

resize

n

+

1

);

for

int

i

=

1

i

<=

n

i

++

p

i

=

i

;}

int

find

int

x

){

if

p

x

!=

x

p

x

=

find

p

x

]);

return

p

x

];}

void

merge

int

x

int

y

){

p

find

x

)]

=

y

;}

};

//並查集模板

bool

dfs

int

now

int

fa

int

goal

int

depth

auto

v

//查詢並列印路徑

{

if

now

==

goal

{

cout

<<

depth

<<

‘\n’

return

true

}

for

auto

it

v

now

])

{

if

it

==

fa

continue

if

dfs

it

now

goal

depth

+

1

v

))

{

cout

<<

it

<<

“ ”

return

true

}

}

return

false

}

int

main

()

{

int

n

m

cin

>>

n

>>

m

dsu

s

n

);

vector

<

int

>

v

n

+

1

];

for

int

i

=

1

x

y

i

<=

m

i

++

{

cin

>>

x

>>

y

if

s

find

x

!=

s

find

y

))

{

v

x

]。

push_back

y

);

v

y

]。

push_back

x

);

s

merge

x

y

);

}

}

int

q

cin

>>

q

int

cnt

n

+

1

=

{

0

};

int

a

q

+

1

],

b

q

+

1

];

for

int

i

=

1

i

<=

q

i

++

{

cin

>>

a

i

>>

b

i

];

cnt

a

i

]]

++

cnt

b

i

]]

++

}

int

num

=

0

//統計cnt為奇數的個數

for

int

i

=

1

i

<=

n

i

++

num

+=

cnt

i

%

2

if

num

==

0

{

cout

<<

“YES

\n

for

int

i

=

1

i

<=

q

i

++

{

dfs

b

i

],

b

i

],

a

i

],

1

v

);

cout

<<

b

i

<<

‘\n’

}

}

else

{

cout

<<

“NO

\n

cout

<<

num

/

2

<<

‘\n’

}

return

0

}