atcoder 杂题 #04
- abc126_f XOR Matching
- arc081_d Flip and Rectangles
- arc080_c Young Maids
- abc383_g Bar Cover
abc126_f
挺有意思的一道题,让我猜到结论了。
由于长度是值域的两倍,所以不难想到每个数出现两次,不然发现对于 \(a_i=a_j=a_k\) 的三个数,当 \(a_i\oplus \cdots\oplus a_j=a_j\oplus \cdots\oplus a_k=K\) 时,一定有 \(a_i\oplus \cdots\oplus a_k=a_j=K\),此时就无法构造了。
首先 \(K=0\) 的情况就是 \(0,0,1,1,\cdots,2^m-1,2^m-1\)。
对于 \(K\ge2^m\) 则无解。
由于我们要让异或和为 \(K\),不妨让 \(K\) 放中间,然后每次往两边添加两个相同的数,最后在一边再添加 \(K\),这就要求 $ \oplus_{i=0}{2m-1} i=0$,才能使两个 \(K\) 之间的异或和为 \(K\)。
即形如 \(0,1,2,\cdots ,2^m-1,K,2^m-1,\cdots,2,1,0,K\)。
做这道题的时候认为异或和不为 0 就是无解。
但写到这才发现由于范围是 \(0\sim 2^m-1\),所以异或和必定为 0,所以我们的构造方式是正确的。
AC 代码:
int n,m;
signed main(){cin>>n>>m;if(m==0){fu(i,0,1<<n)cout<<i<<' '<<i<<" ";return 0;}int s=0;fu(i,0,1<<n)s^=i;if(s==0&&m<(1<<n)){fu(i,0,1<<n)if(i!=m)cout<<i<<" ";cout<<m<<" ";fd(i,(1<<n)-1,0)if(i!=m)cout<<i<<" ";cout<<m<<" ";return 0;}else cout<<"-1";return 0;
}
arc081_d
没有想出来。
其实是一个经典结论,考虑怎样的矩形满足可以经过翻转行和列使得变成全 1 的矩形。
结论就是,如果一个矩形内所有 \(2\times 2\) 的子矩形中 1 和 0 都是偶数个(即异或和为 0),那么这个矩形就是合法的。
证明:
对于一个 \(n\times m\) 的矩形,有 \(2^{n+m}\) 个矩形满足可以从全 1 的矩形得到,这些矩形都是合法的。
我们又发现,合法的矩形一定满足每行都与第一行每位相同或每位相反,列也同理。因为从全 1 矩形开始操作,无论怎么操作都满足这个性质。
于是每行也与上一行每位相同或每位相反,因此可以得到,合法的矩形满足每个 \(2\times 2\) 的矩形异或和为 0。
这就证明了必要性,充分性呢?
发现如果确定了第一行和第一列,那么根据 \(2\times 2\) 子矩形异或和为 0 的性质,其他位置也都确定了。
而确定第一行和第一列的方式恰好是 \(2^{n+m}\) 种,于是每一种满足 \(2\times 2\) 子矩形异或和为 0 的矩形,都是合法矩形。
然后怎么做呢?
考虑根据每一个 \(2\times 2\) 的子矩形是否合法构造一个新的矩阵,然后就是求面积最大的子矩阵,这是经典问题,可以使用单调栈在 \(O(n^2)\) 解决。
AC 代码:
const int N=2005;
char a[N][N];
int c[N][N];
int n,m;
pair<int,int> stk[N];
int top,l[N],r[N];
signed main(){read(n,m);fo(i,1,n)fo(j,1,m)read(a[i][j]);fu(i,1,n)fu(j,1,m){int x=a[i][j]^a[i+1][j]^a[i][j+1]^a[i+1][j+1];if(x==0)c[i][j]=c[i-1][j]+1;}int ans=max(n,m);fu(i,1,n){top=0;fu(j,1,m){l[j]=r[j]=j;while(top&&stk[top].first>=c[i][j])--top;l[j]=stk[top].second;stk[++top]={c[i][j],j};}stk[top=1]={-1,m};fd(j,m-1,1){while(top&&stk[top].first>=c[i][j])--top;r[j]=stk[top].second;stk[++top]={c[i][j],j}; if(c[i][j])ans=max(ans,(c[i][j]+1)*(r[j]-l[j]));}}write(ans);return 0;
}
arc080_c
好题。
首先这种字典序最小就是贪心,由于每次是往前面加,所以考虑倒序。
考虑如果选择 \(i,j(i<j)\),那么在正序时就表现其他都选完了后,再选了它们。
那么第一次选择 \(i,j\) 合法当且仅当 \([1,i-1],[i+1,j-1],[j+1,n]\) 都是偶数长度。
则 \(i\) 为奇数,\(j\) 为偶数。
发现这三个区间的问题是互相独立的,可以递归求解。
每次在 \([L,R]\) 选择 \(i,j\) 就要满足 \(i\) 和 \(L\) 的奇偶性相同,\(j\) 和 \(L\) 的奇偶性不同。
考虑怎么快速求出一个区间内最优的 \(i,j\)。
贪心地,选出奇数(或偶数)位上最小的数作为 \(i\),由于区间长度是偶数,\(i\) 后面一定至少选出一个 \(j\),把 \(i\) 后面与 \(i\) 奇偶性不同的位中取最小值作为 \(j\) 即可,可以用 ST 表维护奇数(偶数)位的区间最小值,同时也可以得到最小值所在的位置。
考虑怎么合并每个子区间,发现直接合并类似于归并的过程,然而每次的时间都是合并的两个区间长度之和,时间复杂度不能接受。
考虑求出所有 \(i,j\) 对后,从大区间往小区间做。开始 \([1,n]\) 的 \(i,j\) 对丢进堆里,每次从堆里选完一个区间 \(i,j\) 后,把每个子区间的 \(i,j\) 丢进堆里,因为子区间要在父区间之后选。
时间复杂度的瓶颈在于预处理 ST 表和最后的堆,为 \(O(n\log n)\)。
AC 代码:
const int N=2e5+5;
int n;
int a[N];
struct S_list{pair<int,int> mn[18][N];pair<int,int> get(int l,int r){int len=r-l+1;return min(mn[__lg(len)][l],mn[__lg(len)][r-(1<<__lg(len))+1]);}void make(){fo(i,1,__lg(n)){fo(j,1,n-(1<<i)+1){mn[i][j]=min(mn[i-1][j],mn[i-1][j+(1<<i-1)]);}}}
}s[2];
vector<int> g[N];
int ans1[N],ans2[N];
int tot;
int solve(int l,int r){int x=++tot;int opt=l&1;auto t1=s[opt^1].get(l,r);auto t2=s[opt].get(t1.second+1,r);ans1[x]=t1.first,ans2[x]=t2.first;if(t1.second>l)g[x].push_back(solve(l,t1.second-1));if(t1.second+1<t2.second)g[x].push_back(solve(t1.second+1,t2.second-1));if(t2.second<r)g[x].push_back(solve(t2.second+1,r));return x;
}
priority_queue<pair<int,int>,vector<pair<int,int>>,greater<> > q;
signed main(){cin>>n;fo(i,1,n){cin>>a[i];s[0].mn[0][i]=s[1].mn[0][i]={N,N};if(i&1)s[0].mn[0][i]={a[i],i};else s[1].mn[0][i]={a[i],i};}s[0].make(),s[1].make();solve(1,n);q.push({ans1[1],1});while(q.size()){int u=q.top().second; q.pop();cout<<ans1[u]<<" "<<ans2[u]<<' ';for(auto v:g[u]){q.push({ans1[v],v});}}return 0;
}
abc383_g
若干个经典结论。
我们先把每 \(K\) 个数的和构成一个新的数组,要求对每个 \(x\) 求出选 \(x\) 个数且两两之间必须间隔 \(K-1\) 个数,问最大和。
考虑分治,我们对于每个区间 \([L,R]\) 求出 \(f_{i,j,k}\) 表示区间左边有 \(i\) 个数没选,右边有 \(j\) 个数没选,选了 \(k\) 个数且这些数已合法的最大和。
那么我们枚举 \(i,j\) 后考虑合并两个区间的数组,每个数组都是一个值关于 \(k\) 的上凸函数,感性理解就是选的越多加的越少。
合并两个凸函数可以做到 \(O(Len)\),其中 \(Len\) 是定义域。这是一个经典结论,具体地,如果我们知道 \(f_i+g_j\to h_{i+j}\) 是转移到 \(i+j\) 最优的 \(i,j\),那么转移到 \(h_{i+j+1}\) 最优的 \(i,j\) 一定是 \(f_{i+1}+g_{j}\) 或 \(f_{i}+g_{j+1}\),取最大的作为新的 \(i,j\) 即可。
计算时间复杂度,枚举两边每选的 \(O(K^2)\),枚举中间没选的 \(O(K)\),因为两个区间中间没选的和为 \(K-1\),枚举一个即可。然后枚举选的个数 \(O(\frac n K)\),再乘上分治的 \(O(\log n)\) 层。
于是总的就是 \(O(n K^2\log n)\)。
AC 代码:
const int N=2e5+5;
const ll inf=0x3f3f3f3f3f3f3f3fll;
int n,K;
int a[N];
ll b[N];
struct arr{vector<ll> f[5][5];
};
void mx(ll &x,ll y){x=max(x,y);
}
void solve(arr &x,int l,int r){int sz=(r-l+K)/K+1;fu(i,0,5)fu(j,0,5)x.f[i][j]=vector<ll>(sz,-inf);if(l==r){x.f[0][0][1]=b[l];return;}arr L,R;int mid=(l+r)>>1;int ls=(mid-l+K)/K+1,rs=(r-mid-1+K)/K+1;solve(L,l,mid),solve(R,mid+1,r);fu(i,0,5)fu(j,0,5){fu(p,1,sz){if(p<ls)mx(x.f[i][min(4,j+r-mid)][p],L.f[i][j][p]);if(p<rs)mx(x.f[min(4,i+mid-l+1)][j][p],R.f[i][j][p]);}fu(k,0,K){int y=0,z=0;fu(p,1,sz){if(y+1==ls)++z;else if(z+1==rs)++y;else if(L.f[i][k][y+1]+R.f[K-1-k][j][z]>L.f[i][k][y]+R.f[K-1-k][j][z+1])++y;else ++z;if(y<ls&&z<rs)mx(x.f[i][j][p],L.f[i][k][y]+R.f[K-1-k][j][z]);}}}fd(i,4,0)fd(j,3,0)fu(p,1,sz)mx(x.f[i][j][p],x.f[i][j+1][p]);fd(j,4,0)fd(i,3,0)fu(p,1,sz)mx(x.f[i][j][p],x.f[i+1][j][p]);
}
signed main(){cin>>n>>K;fo(i,1,n)cin>>a[i];fo(i,K,n)fo(j,i-K+1,i)b[i]+=a[j];arr Main;solve(Main,K,n);fo(i,1,n/K){ll ans=-inf;fu(j,0,5)fu(k,0,5)mx(ans,Main.f[j][k][i]);cout<<ans<<' ';}return 0;
}