PROBLEM LINK:
Setter- Alei Reyes
Tester- Pranjal Jain
Editorialist- Abhishek Pandey
DIFFICULTY:
Medium-Hard
PRE-REQUISITES:
PROBLEM:
Given a chessboard with kings placed on it, you need to find minimum number of rooks to place such that kings cannot reach each other. Rooks cannot be attacked by the kings for simplicity.
QUICK-EXPLANATION:
Key to AC- Realize that since N*M \leq 1024, at least one of the dimensions must be less than 32. Now if we remove rows where kings are, and merge groups of empty rows into a single row, there can be at most 16 rows where we can put rooks. Bitmasking + Clever Brute Force is the way to go.
Without loss of generality, assume N \leq M, otherwise we can simply transpose the matrix and obtain the same result. This caps N to N \leq 32. Now, we need to see how many rows we can put rooks on. Out of these 32 rows, remove rows where kings are placed as rook cannot be placed there without attacking the king. Once its done, see that you might get some consecutive rows which don’t have king on them. Placing a rook on any of the row gives same effect, as long as we place it in the same column. Hence, merge them all into single row. We see that this limits maximum number of rows we can place rooks on to N \leq 16.
Try all possible subsets of rows on which we can place rooks. With rows decided, we now only need to see which columns we have to place rook. This problem can be shown equivalent to finding minimum number of lines to cut given set of intervals (where every interval must be cut by a line). The solution to this is, sort the intervals and greedily pick an interval to cut. All intervals intersecting with this interval are also cut by line and hence are removed. Repeat until no interval is left.
EXPLANATION:
The editorial will be modularized into parts. You can feel free to jump at and start from the part unclear to you, provided you are clear with the parts before it
1. How to arrive at N \leq 16 result-
First convince yourself that the board’s area is \leq 1024. Now, this means, that at least one dimension must be \leq \sqrt{1024}, i.e. \leq 32. Without loss of generality, assume that N \leq M, as if thats not the case, we can rotate the matrix by 90^\circ and get same result.
Now, put kings alternatively on these 32 rows. That is, keep one king at row 1, next at row 3…and so on. We see that we have exactly 16 rows with kings, and 16 free rows.
We are to see that we can, at no instance, have more than 16 free rows. Now, addition of any more kings cannot increase number of free rows, as a rook cannot be placed in same row as of king. Also, if we remove any of the king, then the group of free rows will merge, again decreasing number of free rows. Also, we can see that the configuration we chose is a maximal one, no other configuration exists which has more free rows than this.
Hence, number of rows where rooks can be put (or free rows as I called earlier) are \leq 16
2. Getting bit-masking (without dp :p) into the picture-
The above observation of N \leq16 makes life veryy convenient. The reason being, now we have 2^16 possible choices of keeping rooks on the rows. This is, =65536 combinations to try, at max. Hence, we can simply try all the combinations!
Once we are decided on which rows we have to put the rooks, I think you all can agree that the problem becomes somewhat simpler, and something which can be done with much less effort than what we imagined on reading the statement.
Next part deals with how to calculate the final answer. In case you wish to do an independent try without any more spoilers from editorial, this is your checkpoint
Calculating the final answer-
We store the column and row numbers of positions where kings are placed, so as to not to place rooks on them. Now, lets say we are placing rooks at row i and row j, as per our current combination. Obviously, there should not be two kings on same column, else they can reach each other (recall that rooks are being placed ONLY at rows specified by our mask). Let me call the part of board bounded by row i and row j as region.
Now, you can model the problem as, “Given possible intervals of free columns, where we have to place rooks (recall we need to place rooks between 2 kings in same region!), what are minimum number of rooks to place such that each interval has at least 1 rook assigned to it.”
The solution for this is to, sort all such intervals and greedily place rooks at larger interval, and eliminate intervals where rooks are already set.
“I cannot understand how to make these intervals.”
In case those are your words, then read on
Now, for all columns from [1,M], start from row i+1 and iterate up to row j-1. Make a vector v to store column numbers of any kings up see. This ensures columns are put in sorted order in v.
Now, lets say we are looking at king present on index idx on v. Our interval would be, all free columns after column of king at v[idx-1] and all columns before those of king at v[idx]. A rough implementation is below-
Click to view
for(auto j:blocked){//blocked= Rows which can be blocked by rooks.
vector<int>v;
for(int iy=0;iy<M;iy++){//the way of iteration is as mentioned.
for(int ix=last+1;ix<j;ix++){
if(x[ix][iy]=='K')v.emplace_back(iy);//put column number of king encountered
}
}
rep(idx,1,sz(v)){//Iterating from 1 to v.size() as no need to rooks before first king.
int l=*king_cols.upper_bound(v[idx-1]);//First free column after that king
int r=*(--king_cols.lower_bound(v[idx]));//First free column before this king
if(l>r)good=0;//If 2 or more kings in same column
else intervals.emplace_back(mp(l,r));//Insert interval [l,r]
}
last=j;
}
SOLUTION
Click to view
#include<bits/stdc++.h>
using namespace std;
typedef long long int uli;
int rint(char nxt){
char ch=getchar();
int v=0;
int sgn=1;
if(ch=='-')sgn=-1;
else{
assert('0'<=ch&&ch<='9');
v=ch-'0';
}
while(true){
ch=getchar();
if('0'<=ch && ch<='9')v=v*10+ch-'0';
else{
assert(ch==nxt);
break;
}
}
return v*sgn;
}
string rword(char nxt){
string s;
while(true){
char ch=getchar();
if(ch!=nxt)s.push_back(ch);
else{
assert(ch==nxt);
break;
}
}
return s;
}
struct ev{
int r,c,t,id;
bool operator <(ev e)const{
if(c!=e.c)return c<e.c;
return t<e.t;
}
};
int n,m;
string b[1030];
string board[1030];
vector<int>kings[1030];
vector<int>op[1030];
vector<pair<int,int> >cl[1030];
bool rem[5030];
int main(){
clock_t tStart = clock();
// freopen("secret/0.in","r",stdin);
// freopen("secret/0.out","w",stdout);
int t=rint('\n');
int sm=0;
while(t--){
n=rint(' ');
m=rint('\n');
sm+=n*m;
assert(sm<=1024);
for(int i=0;i<n;i++){
b[i]=rword('\n');
assert((int)b[i].size()==m);
board[i]=b[i];
}
if(n>m){
for(int i=0;i<m;i++){
board[i].resize(n);
for(int j=0;j<n;j++){
board[i][j]=b[n-1-j][i];
}
}
swap(n,m);
}
for(int j=0;j<m;j++)kings[j].clear();
// for(int i=0;i<n;i++)cerr<<board[i]<<endl;
bool ok=true;
for(int j=0;j<m;j++)
for(int i=0;i<n;i++)
if(board[i][j]=='K'){
kings[j].push_back(i);
if(i+1<n && board[i+1][j]=='K')ok=false;
if(j+1<m && board[i][j+1]=='K')ok=false;
}
if(!ok){
puts("-1");
continue;
}
vector<int>rows;
int bef=-1;
for(int i=0;i<n;i++){
int c=0;
for(int j=0;j<m;j++)c+=(board[i][j]=='K');
if(c>0){
if(bef!=-1 && bef!=i-1)rows.push_back(i-1);
bef=i;
}
}
int nr=rows.size();
// cerr<<"rows=";for(int rr:rows)cerr<<rr<<" ";cerr<<endl;
int ans=-1;
for(int b=0;b<(1<<nr);b++){
vector<int>idx;
for(int i=0;i<nr;i++)if(b&(1<<i))idx.push_back(rows[i]);
idx.push_back(n);
vector<int>bef(idx.size(),-1);
vector<ev>all;
ok=true;
int id=0;
for(int j=0;j<m;j++){
op[j].clear();
cl[j].clear();
}
for(int j=0;j<m && ok;j++){
int region=0;
for(int i:kings[j]){
while(i>idx[region])region++;
if(bef[region]!=-1){
if(bef[region]>j-1){
ok=false;
break;
}
op[bef[region]].push_back(id);
cl[j-1].push_back({id,bef[region]});
rem[id++]=false;
/*
all.push_back({region,bef[region],0,id});
all.push_back({region,j-1,1,id++});
*/
}
bef[region]=j+1;
}
}
if(!ok)continue;
queue<pair<int,int> >li;//<left,id>
int col=-1;
int bet=0;
for(int j=0;j<m && ok;j++){
if(kings[j].empty())col=j;
for(int id:op[j]){
li.push({j,id});
}
for(auto u:cl[j]){
int id=u.first;
int lft=u.second;
if(rem[id])continue;
if(col<lft){
ok=false;
break;
}
bet++;
while(!li.empty() && li.front().first<=col){
rem[li.front().second]=true;
li.pop();
}
}
}
if(!ok)continue;
bet=max(bet,int(__builtin_popcount(b)));
if(ans==-1 || bet<ans)ans=bet;
}
printf("%d\n",ans);
}
assert(getchar()==EOF);
cerr<<"time="<<(double)(clock() - tStart)/CLOCKS_PER_SEC<<" seconds"<<endl;
return 0;
}
Click to view
#ifndef _GLIBCXX_NO_ASSERT
#include <cassert>
#endif
#include <cctype>
#include <cerrno>
#include <cfloat>
#include <ciso646>
#include <climits>
#include <clocale>
#include <cmath>
#include <csetjmp>
#include <csignal>
#include <cstdarg>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <ctime>
#if __cplusplus >= 201103L
#include <ccomplex>
#include <cfenv>
#include <cinttypes>
#include <cstdbool>
#include <cstdint>
#include <ctgmath>
#include <cwchar>
#include <cwctype>
#endif
// C++
#include <algorithm>
#include <bitset>
#include <complex>
#include <deque>
#include <exception>
#include <fstream>
#include <functional>
#include <iomanip>
#include <ios>
#include <iosfwd>
#include <iostream>
#include <istream>
#include <iterator>
#include <limits>
#include <list>
#include <locale>
#include <map>
#include <memory>
#include <new>
#include <numeric>
#include <ostream>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <stdexcept>
#include <streambuf>
#include <string>
#include <typeinfo>
#include <utility>
#include <valarray>
#include <vector>
#if __cplusplus >= 201103L
#include <array>
#include <atomic>
#include <chrono>
#include <condition_variable>
#include <forward_list>
#include <future>
#include <initializer_list>
#include <mutex>
#include <random>
#include <ratio>
#include <regex>
#include <scoped_allocator>
#include <system_error>
#include <thread>
#include <tuple>
#include <typeindex>
#include <type_traits>
#include <unordered_map>
#include <unordered_set>
#endif
#define ll long long
#define pb push_back
#define mp make_pair
#define pii pair<int,int>
#define vi vector<int>
#define all(a) (a).begin(),(a).end()
#define F first
#define S second
#define sz(x) (int)x.size()
#define hell 1000000007
#define endl '\n'
#define rep(i,a,b) for(int i=a;i<b;i++)
using namespace std;
string to_string(string s) {
return '"' + s + '"';
}
string to_string(const char* s) {
return to_string((string) s);
}
string to_string(bool b) {
return (b ? "true" : "false");
}
string to_string(char ch) {
return string("'")+ch+string("'");
}
template <typename A, typename B>
string to_string(pair<A, B> p) {
return "(" + to_string(p.first) + ", " + to_string(p.second) + ")";
}
template <class InputIterator>
string to_string (InputIterator first, InputIterator last) {
bool start = true;
string res = "{";
while (first!=last) {
if (!start) {
res += ", ";
}
start = false;
res += to_string(*first);
++first;
}
res += "}";
return res;
}
template <typename A>
string to_string(A v) {
bool first = true;
string res = "{";
for (const auto &x : v) {
if (!first) {
res += ", ";
}
first = false;
res += to_string(x);
}
res += "}";
return res;
}
void debug_out() { cerr << endl; }
template <typename Head, typename... Tail>
void debug_out(Head H, Tail... T) {
cerr << " " << to_string(H);
debug_out(T...);
}
template <typename A, typename B>
istream& operator>>(istream& input,pair<A,B>& x){
input>>x.F>>x.S;
return input;
}
template <typename A>
istream& operator>>(istream& input,vector<A>& x){
for(auto& i:x)
input>>i;
return input;
}
#ifdef PRINTERS
#define debug(...) cerr << "[" << #__VA_ARGS__ << "]:", debug_out(__VA_ARGS__)
#else
#define debug(...) 42
#endif
void solve(){
int N,M;
cin>>N>>M;
vector<string> x(N);
cin>>x;
if(M<N){
vector<string> y(M);
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
y[i].push_back(x[j][i]);
}
}
x=y;
swap(N,M);
}
vi king_rows;
set<int>king_cols;
rep(i,-1,M+1)king_cols.insert(i);
for(int i=0;i<N;i++){
if(count(all(x[i]),'K'))king_rows.emplace_back(i);
for(int j=0;j<M;j++){
if(x[i][j]=='K')king_cols.erase(j);
}
}
if(king_rows.empty()){
cout<<0<<endl;
return;
}
vi pos_empty;
for(int i=0;i<N;i++){
if(binary_search(all(king_rows),i)){
continue;
}
if((i==0 or binary_search(all(king_rows),i-1)))pos_empty.emplace_back(i);
}
int ans=INT_MAX;
for(int i=0;i<(1<<pos_empty.size());i++){
vector<int> blocked;
for(int j=0;j<pos_empty.size();j++){
if(i&(1<<j))blocked.emplace_back(pos_empty[j]);
}
int last=-1;
blocked.emplace_back(N);
bool good=1;
vector<pii>intervals;
for(auto j:blocked){
vector<int>v;
for(int iy=0;iy<M;iy++){
for(int ix=last+1;ix<j;ix++){
if(x[ix][iy]=='K')v.emplace_back(iy);
}
}
rep(idx,1,sz(v)){
int l=*king_cols.upper_bound(v[idx-1]);
int r=*(--king_cols.lower_bound(v[idx]));
if(l>r)good=0;
else intervals.emplace_back(mp(l,r));
}
last=j;
}
if(!good)continue;
sort(all(intervals),[](auto a,auto b){return a.S<b.S;});
last=-1;
int cnt=0;
for(auto j:intervals){
if(j.F>last){
cnt++;
last=j.S;
}
}
ans=min(ans,max(cnt,__builtin_popcount(i)));
}
cout<<(ans==INT_MAX?-1:ans)<<endl;
}
int main(){
ios_base::sync_with_stdio(false);
cin.tie(0);
cout.tie(0);
int t=1;
cin>>t;
while(t--){
solve();
}
return 0;
}
Time Complexity=O(2^{min(N,M)}*M*N)
Space Complexity=O(N*M)
CHEF VIJJU’S CORNER
1. Given N*M dimensional chess board, how many knights can you put at it such that no knight attacks each other? Solution
2. Given a N*N dimensional chess board, what if we ask to place bishops instead of knights in above problem. Can you draw a recurrence for it? Solution
3. Setter’s Solution-
Click to view
Since the number of cells is 1000, then there should be a dimension of size <=31. Let’s suppose that the number of rows is smallest than the number of columns. Rooks cover horizontal and vertical directions. After placing rooks no king should be attacked, so the number of rows covered is at most 16. Brute force the rows covered (2^16 possibilities). Now we have to find the minimum number of columns to separate all kings. This is equivalent to finding the minimum number of lines that cuts a given set of intervals.
4. Tester’s Solution-
Click to view
Assume N<=M without loss of generality. (You can transpose matrix otherwise).This gives us N<=32.
Now, if we have consecutive rows i...j, none of which contains a king, we do not have to consider rows i+1...j, we can instead put the rook in row i and same column as the other row. So, we can have number of empty (not having king) rows <= 16.
Now, iterate over all subsets of such rows. There will be 2^16 such subsets. For each subset, we calculate answer independently and calculate the min.
Now, when we block some rows, we get smaller sub-matrices (stacked vertically) to consider. Sort kings in each sub-matrix by column. We need to have one column between each pair consecutive kings, which does not contain a king. So you get some intervals, each of which must be intersected by a line. This is a well known problem. (Sort by endpoint and take greedily).
5. Related Problems-