W zadaniu dany jest okrąg o długości
Naturalne uproszczenie zadania na okręgu, to rozwiązanie najpierw wersji
na prostej.
Tak jest w podzadaniu 4, w którym mamy gwarancję, że żaden odcinek nie
przecina punktu 0 na okręgu.
Zatem można okrąg rozciąć w punkcie 0 i rozważać następujące zadanie:
podzielić odcinki na dwa zbiory
W zadaniach z pokrywaniem zwykle pomaga posortowanie odcinków po początkach lub końcach, a następnie zastanowienie się nad rozwiązaniem zachłannym lub używającym programowania dynamicznego, które przeglądają odcinki w tej kolejności.
Do naszego zadania istnieje rozwiązanie zachłanne.
Sortujemy odcinki po początkach
Rozważmy dołożenie odcinka
Z kolei jeśli
W końcu dla
Dlaczego takie rozwiązanie jest poprawne?
Jeśli
Z uwagi na sortowanie, rozwiązanie działa w czasie
const int N = 110000; int n,m,a[N],b[N],idx[N]; char answer[N]; int endp[2]; void impossible() { printf("impossible\n"); exit(0); } void ret() { printf("%s\n", answer); exit(0); } int main() { scanf("%d%d",&n,&m); REP(i,m) { scanf("%d%d",&a[i],&b[i]); a[i]--; idx[i] = i; } sort(idx, idx+m, [](int i, int j) { return a[i] < a[j]; }); REP(i,m) { int id = endp[0] < endp[1] ? 0 : 1; if (endp[id] < a[idx[i]]) { impossible(); } answer[idx[i]] = '0'+id; endp[id] = max(endp[id], b[idx[i]]); } if (min(endp[0], endp[1]) < n) { impossible(); } ret(); }
Inną standardową techniką, której można użyć w przypadku zadania z odcinkami
na prostej lub okręgu, jest analiza odcinków, które zawierają się w sobie.
Powiemy, że odcinek
Zauważmy, że jeżeli odcinek
Na początek wyznaczymy zbiór
W przypadku okręgu sprawa jest nieco trudniejsza, bo musimy jakoś wyznaczyć
pierwszy odcinek, który wrzucimy do zbioru
Tak wygląda implementacja generowania zbioru
int main() { scanf("%d%d",&n,&m); REP(i,m) { scanf("%d%d",&a[i],&b[i]); if (b[i] < a[i]) { b[i] += n; } a[i]--; idx[i] = i; } sort(idx, idx+m, [](int i, int j) { return a[i] < a[j] || a[i] == a[j] && b[i] > b[j]; }); int longest = 0; REP(i,m) if (b[idx[longest]]-a[idx[longest]] < b[idx[i]]-a[idx[i]]) { longest = i; } REP(i,longest) { a[idx[i]] += n; b[idx[i]] += n; } sort(idx, idx+m, [](int i, int j) { return a[i] < a[j] || a[i] == a[j] && b[i] > b[j]; }); REP(i,m) parent[i] = -1; idxk[k++] = idx[0]; REP(i,m) if (i) { if (b[idx[i]] <= b[idxk[k-1]]) { parent[idx[i]] = idxk[k-1]; } else { idxk[k++] = idx[i]; } }
Zauważmy, że ponieważ żaden odcinek ze zbioru
Okazuje się teraz, że jeśli istnieje poprawny podział odcinków na zbiory
Jeśli
Dlaczego jest to poprawne?
Rozważmy dowolny fragment
Jeśli
W przypadku
Zauważmy jednak, że znalezione przez nas rozwiązanie jest poprawne o ile rozwiązanie istnieje. Musimy zatem jeszcze sprawdzić jego poprawność:
if (k % 2 == 0) { REP(i,k) answer[idxk[i]] = '0'+(i%2); if (check()) ret(); } else { REP(ii,k) { answer[idxk[ii]] = '0'; REP(j,k-1) answer[idxk[(ii+1+j)%k]] = '0'+(j%2); if (check()) ret(); } } impossible();
Procedura sprawdzania poprawności będzie działać w czasie liniowym od
bool check() { REP(i,m) if (parent[i] != -1) { answer[i] = '0'+'1' - answer[parent[i]]; } REP(col,2) { int endp = -1, begp = -1; REP(i,m) if (answer[idx[i]] == '0'+col) { if (endp == -1) { begp = a[idx[i]]; endp = b[idx[i]]; } else if (endp < a[idx[i]]) { begp = a[idx[i]]; } endp = max(endp, b[idx[i]]); } if (endp < begp+n) return 0; } return 1; }
Ostatecznie zatem rozwiązanie działa w czasie