[衝刺國賽2022] 模擬賽2
阿新 • • 發佈:2022-05-27
矩陣
題目描述
有一個 \(n\times n\) 的矩陣,初始時所有位置上都是 \(0\),每次你可以選擇若干行 \(r_1,r_2...r_p\) 和若干列 \(c_1,c_2...c_q\),把這些行列交點處的位置都變成 \(1\),即把 \((r_i,c_j),1\leq i\leq p,1\leq j\leq q\) 這些位置上的數字都變成 \(1\)
\(n\leq 3000\),詢問次數 \(\leq 26\)
解法
首先考慮簡化問題,如果只是對角線上的元素為 \(0\) 怎麼辦?由於同行同列不能同時選,可以考慮二進位制分組。對於每個二進位制位,每次把為 \(0\) 的行和為 \(1\) 的列(還有為 \(1\)
本題就是多了對角線下面的那一列,嘗試把上面的做法拓展下來。我們把列奇偶分組,把奇數列和所有的行做,把偶數列和所有的行做。比如在做奇數列的時候,我們可以把 \(1,2\) 壓縮成一行,這樣就轉化到了上面的情況。
但是好像要花費 \(4\log n\) 的詢問次數,其實對角線的問題是可以做到 \(1\log n\) 的,具體來說我們把行和列都重新編號。值域為 \([0,2^{13})\),我們只把有 \(6\) 個 \(1\) 的數取出來,給它們重編號,這樣每次只用把為 \(0\)
這是因為重編號方式不存在數位的包含關係,所以一定存在行的某個數位是 \(0\),列的某個數位是 \(1\),因為 \({13\choose 6}>1500\),所以編號也是夠用的,那麼我們就把詢問次數優化到了 \(2\log n\)
#include <cstdio> #include <vector> using namespace std; const int M = 3005; #define pb push_back int read() { int x=0,f=1;char c; while((c=getchar())<'0' || c>'9') {if(c=='-') f=-1;} while(c>='0' && c<='9') {x=(x<<3)+(x<<1)+(c^48);c=getchar();} return x*f; } int n,m,k,bit[1<<13],id[1<<13],a[M],b[M]; vector<int> A[M],B[M]; int main() { freopen("matrix.in","r",stdin); freopen("matrix.out","w",stdout); n=read();read(); for(int i=1;i<(1<<13);i++) { bit[i]=bit[i>>1]+(i&1); if(bit[i]==6) id[++m]=i; } for(int i=1;i<=n;i+=2) a[i]=b[i]=b[i+1]=id[(i+1)>>1]; for(int t=0;t<13;t++) { k++; for(int i=1;i<=n;i++) if(!(b[i]>>t&1)) A[k].pb(i); for(int i=1;i<=n;i+=2) if(a[i]>>t&1) B[k].pb(i); if(A[k].empty() || B[k].empty()) A[k].clear(),B[k].clear(),k--; } // for(int i=0;i<=n;i+=2) a[i]=b[i]=b[i+1]=id[(i+2)>>1]; for(int t=0;t<13;t++) { k++; for(int i=1;i<=n;i++) if(!(b[i]>>t&1)) A[k].pb(i); for(int i=2;i<=n;i+=2) if(a[i]>>t&1) B[k].pb(i); if(A[k].empty() || B[k].empty()) A[k].clear(),B[k].clear(),k--; } printf("%d\n",k); for(int i=1;i<=k;i++) { printf("%d %d",A[i].size(),B[i].size()); for(int x:A[i]) printf(" %d",x); for(int x:B[i]) printf(" %d",x); puts(""); } }