Codeforces Round #815 (Div. 2)
Codeforces Round #815 (Div. 2)
D2. Xor-Subsequence (hard version)
题目大意
给定一个a数组,定义b数组是a的子数组当且仅当b数组由a的下标递增而形成。要求一个美丽的b数组满足\(a_{b_p}\oplus b_{p+1}<a_{b_{p+1}}\oplus b_p\)
,\(a[i]\leq10^9\),求最长的美丽数列。
分析
直接说,这次的分析重点在于,如何转化\(a_{b_p}\oplus b_{p+1}<a_{b_{p+1}}\oplus b_p\)。我们一步步来。
先说结论,我们如何通过一种快速的方式找到一个对应的j使得a[j]^i最大呢?我们将每个数的a[i]^i的二进制健在字典树中。
假设目前我们要计算的位置是i,我们想要a[j]^i和a[i]^i相近,也就是说从高位开始a[j]^i和a[i]^j前k位相同,而在`k+1位发生了不同。
但是这个并不好找,因为虽然确定了i位,但j因为是我们要找的位置,其实一直发生变化的。我们总不能全部暴力枚举,那铁寄。
因此这里用到了一个很奇妙的转化,我们可以发现,a[j]^i和a[i]^j的前k位相同,k+1位不同,则a[j]^j与a[i]^i的前k位相同,而k+1位不同。
到此为止,我们完成了转化,我们将原本需要用到两个位置的式子,转化为了只用一个位置的式子。那转移怎么写呢?
这里也很容易,我们假设,前k位相同的情况下,第k+1位,a[j]^j=0,a[i]^i=1,则我们能合法转移过来的都是a[j]与i第k+1位相同的。
根据此,我们设计数组tr[x][0/1],f[x][0/1]
tr[x][0/1]表示字典树节点
f[x][0/1]表示在字典树x节点上,u = 0或者u = 1的最长美丽子序列。为什么要分0和1呢?我们在找到k + 1个不同的位时,必须要保证a[j] ^ i < a[i] ^ j。想要保证a[j] ^ i < a[i] ^ j,比如使得 a[j]的k+1位 ^ i的k+1位 = 0, a[i]的k+1位 ^ j的k+1位 = 1,因此我们要分四种情况讨论。
x(k) 表示x的第k位
1: j(k) = 0, a[j](k) = 0, i(k) = 0, a[i](k) = 1
2: j(k) = 0, a[j](k) = 1, i(k) = 1, a[i](k) = 1
3: j(k) = 1, a[j](k) = 0, i(k) = 0, a[i](k) = 0
4: j(k) = 1, a[j](k) = 1, i(k) = 1, a[i](k) = 0
总的来说,就是在保证了k+1位不同后,需要根据此时i^a[i]与i的第k位,决定可以转移过来的所有方案。
接下来看看代码。
Ac_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
typedef long long LL;
using namespace std;
const int N = 3e5 + 10,M = N*2;
int tr[N*35][2];
int f[N*35][2];
int n,m,a[N],idx;
void insert(int x,int y)
{
int p = 0;
for(int i=30;i>=0;i--)
{
int u = x >> i & 1,v = y >> i & 1;
if(u==v)
{
if(!tr[p][0]) tr[p][0] = ++ idx;
p = tr[p][0];
}
else
{
if(!tr[p][1]) tr[p][1] = ++ idx;
p = tr[p][1];
}
}
}
int query(int x,int y)
{
int p = 0,maxv = 0;
for(int i = 30 ; i >= 0;i --)
{
int u = x >> i & 1,v = y >> i & 1;
if(u != v)
{
if(tr[p][0]) maxv = max(maxv,f[tr[p][0]][u]);
if(!tr[p][1]) break;
p = tr[p][1];
}
else
{
if(tr[p][1]) maxv = max(maxv,f[tr[p][1]][!u]);
if(!tr[p][0]) break;
p = tr[p][0];
}
}
return maxv + 1;
}
void dp(int x,int y,int maxv)
{
int p = 0;
for(int i = 30;i >= 0;i --)
{
int u = x >> i & 1,v = y >> i & 1;
if(u==v) p = tr[p][0];
else p = tr[p][1];
f[p][u] = max(f[p][u],maxv);
}
}
void solve()
{
cin>>n;
for(int i=0;i<n;i++) cin>>a[i];
int res = 0;
for(int i=0;i<n;i++)
{
int maxv = query(i,a[i]);
res = max(res,maxv);
insert(i,a[i]);
dp(i,a[i],maxv);
}
cout<<res<<'\n';
for(int i=0;i<=idx;i++)
{
tr[i][0] = tr[i][1] = 0;
f[i][0] = f[i][1] = 0;
}
idx = 0;
}
int main()
{
ios;
int T=1;
cin>>T;
while(T -- ) {
solve();
}
return 0;
}
E. Misha and Paintings
题目大意
有一个n * n的矩阵(n <= 500),每个位置有一个值v(1<=v <=n^2),每次操作可以选择一个子方阵,然后改变这些值为同一个x(1<=x<= n^2)。现在给定一个k,问最少多少次操作使得矩阵中不同的数字刚好有k个。
分析
设初始时,矩阵中有cnt个不同的数。
结论:如果k >= cnt,则最少操作数等于k - cnt,因为每次操作至多增加一个数。
如果k < cnt,则答案为1或者为2。
证明可以看这里大佬的,Ander
通过证明后,我们对于k<cnt的情况,目标就是寻找一个方阵,使其外的数字种类为k-1或k即只用变化一次。
接下来这个枚举也蛮奇妙,因为直接暴力枚举铁T。
因此我们观察到数字的范围不大,我们考虑对于每个数,考虑维护其能到达的最远的x,y,若当前枚举的方阵长度为len,找到包含该数字所有的左上角最靠左上角的方阵,接下来找到左上角不包含的该数字方阵,该方阵中的每个位置中都可以包含所有的该数字,则对该方阵的每个位置+1,则可以通过二维差分完成。
Ac_code
#include <bits/stdc++.h>
#define fi first
#define se second
#define endl '\n'
#define ios ios::sync_with_stdio(false); cin.tie(0), cout.tie(0)
typedef long long LL;
using namespace std;
const int N = 1e5 + 10,M = N*2,INF = 0x3f3f3f3f;
struct Node
{
int mix,mxx,miy,mxy;
Node():mix(INF),mxx(-INF),miy(INF),mxy(-INF){};
};
void solve() {
int n,k;cin>>n>>k;
vector<Node> b(n*n);
vector<int> c(n*n);
int cnt = 0;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
int x;cin>>x;--x;
if(!c[x]++) ++cnt;
b[x].mix = min(b[x].mix,i);b[x].mxx = max(b[x].mxx,i);
b[x].miy = min(b[x].miy,j);b[x].mxy = max(b[x].mxy,j);
}
}
if(k>=cnt)
{
cout<<k-cnt<<'\n';
return ;
}
vector<vector<int>> s(n+2,vector<int>(n+2));
for(int len=0;len<n;len++)
{
for(int i=1;i<=n-len;i++) fill(s[i].begin(),s[i].end(),0);
for(int i=0;i<n*n;i++)
{
if(!c[i]||b[i].mxx-b[i].mix>len||b[i].mxy-b[i].miy>len) continue;
int mxx = b[i].mix,mix = max(b[i].mxx-len,1);
int mxy = b[i].miy,miy = max(b[i].mxy-len,1);
++s[mix][miy];--s[mix][mxy+1];
--s[mxx+1][miy];++s[mxx+1][mxy+1];
}
for(int i=1;i<=n-len;i++)
for(int j=1;j<=n-len;j++)
s[i][j] += s[i][j-1] + s[i-1][j] - s[i-1][j-1];
for(int i=1;i<=n-len;i++)
for(int j=1;j<=n-len;j++)
if(cnt-s[i][j]==k||cnt-s[i][j]==k-1)
{
cout<<"1\n";
return ;
}
}
cout<<"2\n";
}
int main()
{
ios;
int T=1;
// cin>>T;
while(T -- ) {
solve();
}
return 0;
}

浙公网安备 33010602011771号