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]^ia[i]^i相近,也就是说从高位开始a[j]^ia[i]^jk位相同,而在`k+1位发生了不同。

但是这个并不好找,因为虽然确定了i位,但j因为是我们要找的位置,其实一直发生变化的。我们总不能全部暴力枚举,那铁寄。

因此这里用到了一个很奇妙的转化,我们可以发现,a[j]^ia[i]^j的前k位相同,k+1位不同,则a[j]^ja[i]^i的前k位相同,而k+1位不同。

到此为止,我们完成了转化,我们将原本需要用到两个位置的式子,转化为了只用一个位置的式子。那转移怎么写呢?

这里也很容易,我们假设,前k位相同的情况下,第k+1位,a[j]^j=0a[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 ^ ik+1位 = 0, a[i]k+1^ jk+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-1k即只用变化一次。

接下来这个枚举也蛮奇妙,因为直接暴力枚举铁T。

因此我们观察到数字的范围不大,我们考虑对于每个数,考虑维护其能到达的最远的xy,若当前枚举的方阵长度为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;
}
posted @ 2022-09-15 18:18  艾特玖  阅读(26)  评论(0)    收藏  举报