1+ import java .io .EOFException ;
2+ import java .io .IOException ;
3+ import java .io .InputStream ;
4+ import java .io .OutputStream ;
5+
6+ /*
7+ Принцип работы алгоритма:
8+ Нужно понять, можно ли выбрать часть партий так, чтобы сумма очков в них была равна половине общей суммы.
9+ Если общая сумма нечётная, две равные части получить нельзя.
10+ Иначе решаем задачу о достижимой сумме: храним битовую динамику reachable, где бит s равен 1,
11+ если некоторым набором уже рассмотренных партий можно набрать сумму s. Изначально достижима сумма 0.
12+ Для очередного значения x обновляем reachable операцией reachable |= reachable << x.
13+ После обработки всех значений проверяем достижимость суммы total / 2.
14+
15+ Доказательство корректности:
16+ После обработки первых i партий инвариант такой: бит s в reachable установлен тогда и только тогда,
17+ когда из этих i партий можно выбрать подмножество с суммой s.
18+ База: до обработки партий достижима только сумма 0, это пустое подмножество.
19+ Переход: для партии с очками x каждая старая достижимая сумма s остаётся достижимой, если не брать эту партию,
20+ а сумма s + x становится достижимой, если взять её. Именно это делает reachable |= reachable << x.
21+ По индукции после всех партий reachable содержит ровно все суммы, которые можно набрать подмножеством партий.
22+ Разбиение на две равные части существует тогда и только тогда, когда общая сумма чётная и достижима сумма total / 2:
23+ выбранное подмножество даёт первую часть, все остальные партии — вторую с такой же суммой.
24+
25+ Сложность:
26+ Пусть S — общая сумма очков, S <= 300 * 300 = 90000.
27+ Временная сложность — O(n * S / 64), потому что суммы хранятся битами в long-блоках.
28+ Пространственная сложность — O(S / 64).
29+ */
30+ public class EqualSums {
31+
32+ static boolean solve (int [] points ) {
33+ int total = 0 ;
34+ for (int point : points ) {
35+ total += point ;
36+ }
37+
38+ if ((total & 1 ) == 1 ) {
39+ return false ;
40+ }
41+
42+ int target = total / 2 ;
43+ long [] reachable = new long [(target >> 6 ) + 1 ];
44+ reachable [0 ] = 1L ;
45+
46+ for (int point : points ) {
47+ if (point <= 0 || point > target ) {
48+ continue ;
49+ }
50+
51+ addPoint (reachable , point );
52+
53+ if (isReachable (reachable , target )) {
54+ return true ;
55+ }
56+ }
57+
58+ return isReachable (reachable , target );
59+ }
60+
61+ private static void addPoint (long [] reachable , int point ) {
62+ int wordShift = point >> 6 ;
63+ int bitShift = point & 63 ;
64+
65+ for (int word = reachable .length - 1 ; word >= wordShift ; word --) {
66+ long shifted = reachable [word - wordShift ] << bitShift ;
67+
68+ if (bitShift != 0 && word - wordShift - 1 >= 0 ) {
69+ shifted |= reachable [word - wordShift - 1 ] >>> (64 - bitShift );
70+ }
71+
72+ reachable [word ] |= shifted ;
73+ }
74+ }
75+
76+ private static boolean isReachable (long [] reachable , int sum ) {
77+ return (reachable [sum >> 6 ] & (1L << (sum & 63 ))) != 0 ;
78+ }
79+
80+ // -------------------- FAST INPUT --------------------
81+ static final class FastIn {
82+ private final InputStream in ;
83+ private final byte [] buf = new byte [1 << 16 ];
84+ private int ptr = 0 ;
85+ private int len = 0 ;
86+
87+ FastIn (InputStream in ) {
88+ this .in = in ;
89+ }
90+
91+ private int read () throws IOException {
92+ if (ptr >= len ) {
93+ len = in .read (buf );
94+ ptr = 0 ;
95+
96+ if (len <= 0 ) {
97+ return -1 ;
98+ }
99+ }
100+
101+ return buf [ptr ++];
102+ }
103+
104+ int nextInt () throws IOException {
105+ int c ;
106+
107+ do {
108+ c = read ();
109+
110+ if (c == -1 ) {
111+ throw new EOFException ("Unexpected EOF" );
112+ }
113+ } while (c <= ' ' );
114+
115+ int sign = 1 ;
116+
117+ if (c == '-' ) {
118+ sign = -1 ;
119+ c = read ();
120+ }
121+
122+ int val = 0 ;
123+
124+ while (c > ' ' ) {
125+ val = val * 10 + (c - '0' );
126+ c = read ();
127+ }
128+
129+ return val * sign ;
130+ }
131+ }
132+
133+ // -------------------- FAST OUTPUT --------------------
134+ static final class FastOut {
135+ private final OutputStream out ;
136+ private final byte [] buf = new byte [1 << 16 ];
137+ private int p = 0 ;
138+
139+ FastOut (OutputStream out ) {
140+ this .out = out ;
141+ }
142+
143+ void writeByte (int b ) throws IOException {
144+ if (p == buf .length ) {
145+ flush ();
146+ }
147+
148+ buf [p ++] = (byte ) b ;
149+ }
150+
151+ void writeAscii (String s ) throws IOException {
152+ for (int i = 0 ; i < s .length (); i ++) {
153+ writeByte (s .charAt (i ));
154+ }
155+ }
156+
157+ void flush () throws IOException {
158+ out .write (buf , 0 , p );
159+ p = 0 ;
160+ }
161+ }
162+
163+ private static void run () throws Exception {
164+ FastIn in = new FastIn (System .in );
165+ FastOut out = new FastOut (System .out );
166+
167+ int n = in .nextInt ();
168+ int [] points = new int [n ];
169+
170+ for (int i = 0 ; i < n ; i ++) {
171+ points [i ] = in .nextInt ();
172+ }
173+
174+ out .writeAscii (solve (points ) ? "True" : "False" );
175+ out .writeByte ('\n' );
176+ out .flush ();
177+ }
178+
179+ private static void test () {
180+ assertEq (true , solve (new int []{1 , 5 , 7 , 1 }));
181+ assertEq (false , solve (new int []{2 , 10 , 9 }));
182+ assertEq (true , solve (new int []{}));
183+ assertEq (true , solve (new int []{0 , 0 , 0 }));
184+ assertEq (false , solve (new int []{1 }));
185+ assertEq (true , solve (new int []{1 , 1 }));
186+ assertEq (true , solve (new int []{100 , 200 , 300 }));
187+ assertEq (false , solve (new int []{100 , 200 , 301 }));
188+
189+ System .out .println ("Test OK" );
190+ }
191+
192+ static void assertEq (boolean exp , boolean act ) {
193+ if (exp != act ) {
194+ throw new AssertionError ("Expected=" + exp + ", actual=" + act );
195+ }
196+ }
197+
198+ public static void main (String [] args ) throws Exception {
199+ if (System .getProperty ("os.name" ).startsWith ("Windows" )) {
200+ test ();
201+ } else {
202+ run ();
203+ }
204+ }
205+ }
0 commit comments