九、十月刷题记录2

C. Portal(矩阵dp,n^4优化)

//
// Created by artist on 2021/10/8.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'
#define pii pair<int,int>

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
const int maxn = 403;
int mp[maxn][maxn];
int dp[maxn][maxn]; // i:up, j:down, minimum
//char srr[maxn];

char s[402];
int sum[401][401],f[401];

// 一个矩阵的1的个数
inline int GetSum(int lx,int ly,int rx,int ry){
    return sum[rx][ry]-sum[rx][ly-1]-sum[lx-1][ry]+sum[lx-1][ly-1];
}

inline void Solve(){
    int n,m,i,j,k,ans=999999,cur;
    scanf("%d%d",&n,&m);
    for(i=1;i<=n;i++){
        scanf("%s",s+1);
        for(j=1;j<=m;j++){
            sum[i][j]=sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1]+(s[j]=='1'); // 左上角的矩形中有多少个1
        }
    }
    // upper line
    for(i=1;i!=n;i++){
        // down line
        for(j=i+4;j<=n;j++){
            // right line
            for(k=4;k<=m;k++){
                f[k]=GetSum(i+1,1,j-1,k-1)-GetSum(i,1,i,k-1)-GetSum(j,1,j,k-1)-GetSum(i+1,k,j-1,k)+(k<<1)+j-i-3;
            }
            for(k=m-1;k!=3;k--){
                if(f[k+1]<f[k]){
                    f[k]=f[k+1];
                }
            }
            // left line
            for(k=1;k!=m-2;k++){
                cur=f[k+3]-GetSum(i+1,1,j-1,k)+GetSum(i,1,i,k)+GetSum(j,1,j,k)-(k<<1)-GetSum(i+1,k,j-1,k)+j-i-1;
                if(cur<ans){
                    ans=cur;
                }
            }
        }
    }
    printf("%d\n",ans);
}

signed main() {
    int t;cin>>t;
    while(t--) {
        Solve();
    }
}

E. Train Maintenance(分块)

#include<bits/stdc++.h>
using namespace std;
const int maxn = 2e5+5;
const int B = 455;
int x[maxn],y[maxn];
int st[maxn],mp[B][B];
int pre[maxn];
int main() {
    int n,m;scanf("%d%d",&n,&m);
    for(int i=1;i<=n;++i) {
        scanf("%d%d",&x[i],&y[i]);
    }
    int ans = 0;
    for(int i=1;i<=m;++i) {
        int op,k;scanf("%d%d",&op,&k);
        int p = x[k] + y[k];
        if(op==1) st[k] = i;
        if(p<=450) {
            for(int j=st[k]+x[k];j<st[k]+p;++j) mp[p][j%p]+=op==1?1:-1;
        } else {
            for(int j=st[k]+x[k];j<=m;j+=p) pre[j]+=op==1?1:-1;
            for(int j=st[k]+p;j<=m;j+=p) pre[j]-=op==1?1:-1;
            if(op==2&&((i-st[k])%p>x[k]||(i-st[k])%p==0)) ans--;
        }
        ans += pre[i];
        int cnt = 0;
        for(int j=2;j<=450;++j) cnt += mp[j][i%j];
        printf("%d\n",ans+cnt);
//        cout<<ans+cnt<<endl;
    }
}

Team Rocket(线段树)

一道很有意思的题,线段树存区间,询问点,每个点删掉所属的所有区间。
下标为每个区间的左端点。值为该左端点的区间中右端点最大为多少。
每个叶子开个数组存所有属于这个叶子的区间。

//
// Created by artist on 2021/10/6.
//


#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'
#define pii pair<int,int>

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
int n,m;
const int maxn = 2e5+5;
const int mod = 998244353;
struct node {
    int l,r;
}tra[maxn];

int ql,qr,val;
ll res;
vector<pair<int,int> > lef[maxn];
ll lst;
int tim[maxn]; // 每一个火车被炸掉的时间
// 树节点
//set<int> lef[maxn];
int tr[maxn<<2];
int tttt;

// 必须探到底,才知道要删除哪些
void update(int l,int r,int rt) {
    if(ql<=l && qr>=r) {
        if(tr[rt]<val) return;
    }
    if(l==r) {
        int tmp;
        // 第一个大于等于val的位置(总数-tmp+1)
        for(int i=lef[l].size()-1;i>=0;--i) {
            if(lef[l][i].fi>=val) {
                lst=lst*lef[l][i].se%mod;
                tim[lef[l][i].se] = tttt;
                tmp = i;
                res++;
            } else break;
        }
        lef[l].erase(lef[l].begin()+tmp,lef[l].end());
        if(lef[l].size()) tr[rt] = lef[l][lef[l].size()-1].first;
        else tr[rt] = -2e9;
        return;
    }
    int mid = (l+r)>>1;
    if(ql<=mid && tr[rt<<1]>=val) update(l,mid,rt<<1);
    if(qr>mid && tr[rt<<1|1]>=val) update(mid+1,r,rt<<1|1);
    tr[rt] = max(tr[rt<<1],tr[rt<<1|1]);
}

void build(int l,int r,int rt) {
    if(l==r) {
        tr[rt] = -2e9;
        lef[l].clear();
        return;
    }
    int mid = (l+r)>>1;
    build(l,mid,rt<<1);
    build(mid+1,r,rt<<1|1);
    tr[rt] = max(tr[rt<<1],tr[rt<<1|1]);
}

int id;
void insert(int l,int r,int rt) {
    if(l==r) {
        tr[rt] = max(tr[rt],val);
        lef[l].pb(mkp(val,id));
        return;
    }
    int mid = (l+r)>>1;
    if(ql<=mid) insert(l,mid,rt<<1);
    else insert(mid+1,r,rt<<1|1);
    tr[rt] = max(tr[rt<<1],tr[rt<<1|1]);
}

int c[maxn];
signed main() {
    io();
    int t;cin>>t;
    for(int cas=1;cas<=t;++cas) {
        // 对每一个区间进行离散化:线段树的边界是n吗??
        cout<<"Case #"<<cas<<":"<<endl;
        cin>>n>>m;
        for(int i=1;i<=n;++i) {
            cin>>tra[i].l>>tra[i].r;
            c[i] = tra[i].l;
        }
        for(int i=1;i<=n;++i) tim[i] = 0;
        sort(c+1,c+1+n);
        int cnt;
        cnt = unique(c+1,c+1+n) - c - 1;
        // 值域:1-cnt
        build(1,cnt,1); // 清空
        for(int i=1;i<=n;++i) {
            tra[i].l = lower_bound(c+1,c+1+cnt,tra[i].l) - c;
            ql = tra[i].l,val = tra[i].r,id = i;
            insert(1,cnt,1);
        }
        for(int i=1;i<=cnt;++i) sort(lef[i].begin(),lef[i].end());
        lst = 0;
        for(int i=1;i<=m;++i) {
            tttt = i;
            int que;cin>>que;
            que = que^(lst%mod);
            lst = 1, res = 0;
            qr = upper_bound(c+1,c+1+cnt,que) - c - 1;
//        DB1(qr,que);
            ql = 1,val = que;
            if(qr) update(1,cnt,1);
            if(res==0) lst=0;
            cout<<res<<endl;
        }
        // 输出每一个火车什么时候被炸掉
        for(int i=1;i<=n;++i) {
            cout<<tim[i]<<" ";
        }
        cout<<endl;
    }
}

J. Sudoku Subrectangles(矩阵dp+鸽巢+单调队列)

虽然写法不是最简单的,但是至少对了()

//
// Created by artist on 2021/10/7.
//


#include<bits/stdc++.h>
#define int long long
using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'
#define pii pair<int,int>

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
const int maxn = 1e3+4;

int mpp(char ch) {
    if(ch>='a'&&ch<='z') return ch-'a'+1;
    else return ch-'A'+27;
}

int mp1[maxn][maxn];
int d[maxn][maxn];
int r[maxn][maxn];
char mp[maxn][maxn];
int pos[55];
deque<pair<int,int> > q;
signed main() {
    int n,m;scanf("%lld%lld",&n,&m);
    for(int i=1;i<=n;++i) scanf("%s",mp[i]+1);
    for(int i=1;i<=n;++i) {
        for(int j=1;j<=m;++j) {
            mp1[i][j] = mpp(mp[i][j]);
        }
    }

    // 每个位置下面最近的可行位置颜色在哪
    // 对每一行,清空
    for(int i=1;i<=m;++i) {
        int mn = 1e18;
        for(int j=1;j<=52;++j) pos[j]=n+1;
        for(int j=n;j;--j) {
            d[j][i] = pos[mp1[j][i]]-1;
            mn = min(mn,d[j][i]);
            d[j][i] = mn;
            pos[mp1[j][i]] = j;
        }
    }

    // 每个位置右边最近的可行位置在哪
    for(int i=1;i<=n;++i) {
        int mn = 1e18;
        for(int j=1;j<=52;++j) pos[j]=m+1;
        for(int j=m;j;--j) {
            r[i][j] = pos[mp1[i][j]]-1;
            mn = min(mn,r[i][j]);
            r[i][j] = mn;
            pos[mp1[i][j]] = j;
        }
    }

    ll ans = 0;

    for(int U=1;U<=n;++U) {
        for(int L=1;L<=m;++L) {
            int mn = 1e18;
            while(q.size()) q.pop_back();
            for(int R=r[U][L];R>=L;--R) {
                while(q.size() && q.back().fi>=d[U][R]) q.pop_back();
                q.push_back(mkp(d[U][R],R));
            }
            // 枚举该位置往下
            for(int D=U;D<=d[U][L];++D) {
                mn = min(mn,r[D][L]);
                int R = mn;
                while(q.front().se>R) q.pop_front();
                while(q.front().fi<D) {
                    while(q.front().se <= R) R--;
                    q.pop_front();
                }
                ans += R-L+1;
                mn = min(mn,R);
//                DB1(U,L,D,R-L+1);
            }
        }
    }

    printf("%lld\n",ans);
}

写法2:
思路源自zxl的代码:
枚举矩阵右下角的点。(为什么是右下角的点?因为到达右下角的时候,我们已经遍历了大矩阵的左上部分,得出了一些预处理信息)
枚举以该点往左伸展的距离K,即矩阵的宽度。(为什么是宽度不是下标?等下就知道了)
已知右下角的点的位置和宽度,我们要求一个符合条件的往上的长度。这就是贡献。(显然)
考虑一个矩阵从枚举顺序中如何更新过来(状态转移)。
在这里插入图片描述

#include <bits/stdc++.h>
#define FOR(I, A, B) for (int I = (A); I <= (B); ++I)
#define ll long long
using namespace std;
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
void dbg() { std::cout << "  #\n"; }
template<typename T, typename...Args>
void dbg(T a, Args...args) { std::cout << a << ' '; dbg(args...); }
template <typename T> void inline read(T &x) {
    int f = 1; x = 0; char s = getchar();
    while (s < '0' || s > '9') { if (s == '-') f = -1; s = getchar(); }
    while (s <= '9' && s >= '0') x = x * 10 + (s ^ 48), s = getchar();
    x *= f;
}

//var
const int maxn=1000+10;
const int MAX=1000;
const int inf=0x3f3f3f3f;
const int mod=1e9+7;
//head
int n,m;
char s[maxn][maxn];
int pos[maxn];
int l[maxn][maxn];
int u[maxn][maxn];
int minn[maxn];
void solve()
{
    read(n);read(m);
    FOR(i,0,n-1) scanf("%s",s[i]);
    FOR(i,0,n-1)
    {
        FOR(j,'A','z')
        {
            pos[j]=-1;
        }
        FOR(j,0,m-1)
        {
            if (!j)
            {
                l[i][j]=1;
            }
            else
            {
                l[i][j]=min(l[i][j-1]+1,j-pos[s[i][j]]); // 往左可走的长度
            }
            pos[s[i][j]]=j;
        }
    }

    FOR(j,0,m-1)
    {
        FOR(i,'A','z')
        {
            pos[i]=-1;
        }
        FOR(i,0,n-1)
        {
            if (!i)
            {
                u[i][j]=1;
            }
            else
            {
                u[i][j]=min(u[i-1][j]+1,i-pos[s[i][j]]); // 往上可走的长度
            }
            pos[s[i][j]]=i;
        }
    }

    ll ans=0;
    FOR(j,0,m-1)
    {
        FOR(i,1,52) minn[i]=0;
        FOR(i,0,n-1)
        {
            FOR(k,1,l[i][j])
            {
                int left=j-k+1;
                // minn[k]有点类似于滚动数组
                if (k==1) minn[k]=u[i][left]; // 横向长度为k,能往上伸展的最大距离
                //                    右边矩阵处理的结果 这一列 上边矩阵处理的结果
                else minn[k]=min({minn[k-1],u[i][left],minn[k]+1});
                ans+=minn[k];
            }
            FOR(k,l[i][j]+1,52) minn[k]=0;// 超过了,说明之后都到不了.
        }
    }
    printf("%lld\n",ans);
}
signed main()
{
    int TestCase = 1;
    while (TestCase--)
    {
        solve();
    }
}


ICPC网络赛 K Meal
状压dp+概率。
重点在于:我们现在有一种方式生成偏好序列。
这个序列的生成方式为:起初有一个集合S,每一轮抽一个元素并从集合中删去。抽第j个元素的概率为 a j ∑ k ∈ S a k \frac{a_j}{\sum_{k\in S}a_k} kSakaj
这道题用到了一个点:
在一个偏好序列中,第j个元素前面的元素构成给定的一个集合的子集的概率。
等价于:在一个偏好序列中,第j个元素排在所有该给定集合外的元素中的第一位的概率。
而这个概率 = a j ∑ k ∈ S ′ a k =\frac{a_j}{\sum_{k\in S'}a_k} =kSakaj,其中 S ′ S' S为给定集合外的元素构成的集合。

//
// Created by Artist on 2021/10/12.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }

const int maxn = 30;
const int maxm = 100*22;
const int N = 2e6;
const int mod = 998244353;
int a[maxn][maxn];
int ans[maxn][maxn];

int inv[maxm];
int f[N]; // 每个局面出现的概率
int tot[maxn]; // 总数
signed main() {
    io();
    int n;cin>>n;
    for(int i=1;i<=n;++i) {
        for(int j=0;j<n;++j) cin>>a[i][j],tot[i]+=a[i][j];
    }
    // 线性求逆元
    inv[1] = 1;
    for(int i=2;i<maxm;++i) {
        inv[i] = (ll)(mod-mod/i) * inv[mod%i] % mod;
    }
    f[0]=1;
    // 枚举局面(一个局面出现,其子集必然曾经出现过)
    for(int st=1,i;st<(1<<n);++st) {
        // 枚举贡献对象
        // 枚举当前是谁
        i=0;
        for(int j=0;j<n;++j) {
            if((st>>j)&1) i++;
        }
//        DB1(i);
        int sm=0;
        for(int j=0;j<n;++j) {
            if((st>>j)&1) {
                sm += a[i][j];
            }
        }
        // 枚举现在是哪碟
        for(int j=0;j<n;++j) {
            if((st>>j)&1) {
                ans[i][j] = (ans[i][j] + 1ll*f[st^(1<<j)]*a[i][j]%mod*inv[tot[i]-sm+a[i][j]]%mod)%mod;
                f[st] = (f[st] + 1ll*f[st^(1<<j)]*a[i][j]%mod*inv[tot[i]-sm+a[i][j]]%mod)%mod;
            }
        }
    }
    for(int i=1;i<=n;++i) {
        for(int j=0;j<n;++j) {
            cout<<ans[i][j];
            if(j!=n-1) cout<<" ";
        }
        if(i!=n) cout<<endl;
    }
}

Jumping Monkey

很灵活的一道树上问题+图论建模。
一开始看错题然后四个小时没出来。
好好总结

//
// Created by Artist on 2021/10/12.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
const int maxn = 1e5+5;
vector<int> G[maxn];
vector<int> G2[maxn]; // new tree
pair<int,int> a[maxn];
int fa[maxn];
int val[maxn],dep[maxn];

void dfs(int u) {
    for(auto v:G2[u]) dep[v]=dep[u]+1,dfs(v);
}

int find(int x) {
    return fa[x]=fa[x]==x?x:find(fa[x]);
}

signed main() {
    io();
    int t;cin>>t;
    while(t--) {
        int n;cin>>n;
        for(int i=1;i<=n;++i) G[i].clear();
        for(int i=1;i<=n;++i) G2[i].clear();
        for(int i=1;i<n;++i) {
            int u,v;cin>>u>>v;
            G[u].pb(v);
            G[v].pb(u);
        }
        for(int i=1;i<=n;++i) {
            cin>>a[i].fi;
            a[i].se=i;
            fa[i] = i;
            val[i]=a[i].fi;
        }
        sort(a+1,a+1+n);
        for(int i=1;i<=n;++i) {
            int u=a[i].se;
            for(auto v:G[u]) {
                if(val[v]<val[u]) {
                    int x = find(v);
                    if(x==u) continue;
                    G2[u].pb(x);
                    fa[x] = u;
                }
            }
        }
        dep[a[n].se]=1;
        dfs(a[n].se);
        for(int i=1;i<=n;++i) {
            cout<<dep[i]<<endl;
        }
    }
}

ccpc网络赛 Bigraph Extension

知道自己无缘ccpc后写的第一道题

//
// Created by Artist on 2021/10/12.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
const int maxn = 1003;
int in[maxn<<1];
vector<pair<int,int> > ans;
int fa[maxn<<1];

int find(int x) {
    return fa[x]=fa[x]==x?x:find(fa[x]);
}


signed main() {
    io();
    int t;cin>>t;
    while(t--) {
        int n,m;cin>>n>>m;
        ans.clear();
        for(int i=1;i<=n<<1;++i) in[i]=0,fa[i]=i;
        for(int i=1;i<=m;++i) {
            int u,v;cin>>u>>v;
            int x=find(u),y=find(v+n);
            if(x!=y) fa[x]=y;
            in[u]++;
            in[n+v]++;
        }
        int st=n+1;
        for(int i=1;i<=n;++i) {
            for(int j=st;j<=n<<1;++j) {
                if(in[j]<=1) {
                    int x=find(i),y=find(j);
                    if(x!=y) {
                        fa[x]=y;
                        in[j]++;
                        in[i]++;
                        ans.pb(mkp(i,j-n));
                    }
                }
                if(in[st]==2) st++;
                if(in[i]==2) break;
            }
        }
        for(int j=n+1;j<=n<<1;++j) if(in[j]==1) {
            ans.pb(mkp(n,j-n));
                break;
        }
        cout<<ans.size()<<endl;
        for(auto i:ans) cout<<i.fi<<" "<<i.se<<endl;
    }
}

F. RBS

将全排列转化为递推,然后以状压dp解决。
状压dp+括号序列问题

#include<bits/stdc++.h>
#define rep(i,x,y) for(int i=x; i<=y; ++i)

using namespace std;
typedef long long LL;
#define fi first
#define se second
const int N=22;
string s[N];
bool vis[1<<20];
pair <int,int> dat[N],f[1<<20];
int n,ans,g[N][400005];

int getint()
{
    char ch;
    while(!isdigit(ch=getchar()));
    int x=ch-48;
    while(isdigit(ch=getchar())) x=x*10+ch-48;
    return x;
}

pair <int,int> find(string s,int x)
{
    int n=s.length();
    pair<int,int> ret=make_pair(0,0);
    // fi:构成合法需要的左括号数(具有的se不够的时候才加)
    // se:若将其构成合法,其能提供的左括号数(因此,se到0之后不会再减,一遇到左括号却会再加)
    rep(i,0,n-1)
    {
        if(s[i]=='(') ++ret.se;
        else if(ret.se) --ret.se;
        else ++ret.fi;
        if(ret.se==0) ++g[x][ret.fi];
    }
    // g[x][i]:如果前面接的串给你提供i个左括号,你新贡献多少个**前缀**RBS
    return ret;
}

void solve()
{
    n=getint();
    rep(i,1,n) cin>>s[i],dat[i]=find(s[i],i);
    vis[0]=1,f[0]=make_pair(0,0);
    rep(i,0,(1<<n)-2) if(vis[i])
        {
        // vis==1:该串能够往后接新串.
        // vis==0:该串已经非法,无法往后接新串.
        // first:提供的左括号数
        // second:最多的前缀RBS数
            rep(j,1,n) if(!(i&(1<<j-1)))
                {
                    ans=max(ans,f[i].se+g[j][f[i].fi]);
                    if(dat[j].fi<=f[i].fi)
                    {
                        vis[i^(1<<j-1)]=1;
                        f[i^(1<<j-1)].fi=f[i].fi+dat[j].se-dat[j].fi;
                        f[i^(1<<j-1)].se=max(f[i^(1<<j-1)].se,f[i].se+g[j][f[i].fi]);
                    }
                }
        }
    printf("%d\n",ans);
}

int main()
{
    solve();
    return 0;
}

Longest Common Subsequence

四维偏序:cdq套cdq

//
// Created by Artist on 2021/10/16.
//

#include<bits/stdc++.h>

using namespace std;
typedef long long ll;
#define mkp make_pair
#define fi first
#define se second
#define pb push_back
#define all(x) x.begin(),x.end()
#define DB1(args...) do { cout << #args << " : "; dbg(args); } while (0)
#define endl '\n'

void dbg() { std::cout << "  #\n"; }

template<typename T, typename...Args>
void dbg(T a, Args...args) {
    std::cout << a << ' ';
    dbg(args...);
}

void io() { ios::sync_with_stdio(0), cin.tie(0), cout.tie(0); }
const int maxn = 1e4+4;
int pos[5][3][maxn];
int dp[maxn<<3];

struct node {
    int tp;
    int a,b,c,d;
}ar[maxn<<3];
node tmp[maxn<<3],tmp2[maxn<<3];
int tr[maxn];
int n;

bool cmp1(node x, node y) {
    return x.b < y.b;
}

bool cmp2(node x, node y) {
    return x.c < y.c;
}
#define lowbit(x) (x&(-x))
void add(int x,int c) {
    while(x<=n) {
        tr[x] = max(tr[x],c);
        x += lowbit(x);
    }
}

int sum(int x) {
    int ans = 0;
    while(x>0) {
        ans = max(ans,tr[x]);
        x -= lowbit(x);
    }
    return ans;
}

void clr(int x) {
    while(x<=n) {
        tr[x] = 0;
        x += lowbit(x);
    }
}

void CDQ2(int l,int r) {
    if(l==r) return;
    int mid = l+r>>1;
    CDQ2(l,mid);
    for(int i=l;i<=r;++i) tmp2[i] = tmp[i];
    sort(tmp2+l,tmp2+mid+1,cmp2);
    sort(tmp2+mid+1,tmp2+r+1,cmp2);
    int p=l,q=mid+1;
    while(p<=mid&&q<=r) {
        if(tmp2[p].c < tmp2[q].c) {
            if(tmp2[p].tp) add(tmp2[p].d,dp[tmp2[p].a]);
            p++;
        } else {
            if(!tmp2[q].tp) dp[tmp2[q].a] = max(dp[tmp2[q].a],sum(tmp2[q].d-1)+1);
            q++;
        }
    }
    while(q<=r) {
        if(!tmp2[q].tp) dp[tmp2[q].a] = max(dp[tmp2[q].a],sum(tmp2[q].d-1)+1);
        q++;
    }
    for(int i=l;i<p;++i) {
        if(tmp2[i].tp) clr(tmp2[i].d);
    }
    CDQ2(mid+1,r);
}

void CDQ(int l,int r) {
    if(l==r) return;
    int mid = l+r>>1;
    CDQ(l,mid);
    for(int i=l;i<=mid;++i) tmp[i] = ar[i], tmp[i].tp = 1;   // 打上标签
    for(int i=mid+1;i<=r;++i) tmp[i] = ar[i], tmp[i].tp = 0;
    sort(tmp+l,tmp+1+r,cmp1);
    CDQ2(l,r);
    CDQ(mid+1,r);
}

signed main() {
//    io();
    cin>>n;
    for(int i=0;i<3;++i) {
        for(int j=1;j<=n;++j) {
            int val;cin>>val;
            if(pos[i][0][val]) pos[i][1][val]=j;
            else pos[i][0][val]=pos[i][1][val]=j;
        }
    }
    int cnt = 0;
    for(int j=1;j<=n;++j) {
        int val;cin>>val;
        if(!pos[0][0][val]||!pos[1][0][val]||!pos[2][0][val]) continue;
        // 防止4中的一个位置被多次计算,倒序枚举
        for(int i=7;i>=0;--i) {
            ++cnt;
            ar[cnt].a=cnt;
            ar[cnt].b=pos[0][i&1][val];
            ar[cnt].c=pos[1][(i>>1)&1][val];
            ar[cnt].d=pos[2][(i>>2)&1][val];
//            DB1(cnt,ar[cnt].b,ar[cnt].c,ar[cnt].d);
            dp[cnt] = 1;
        }
    }
    CDQ(1,cnt);
    int ans = 0;
    for(int i=1;i<=cnt;++i) ans = max(ans,dp[i]);
    cout<<ans<<endl;
}
/*
 * 5
1 2 1 2 3
1 2 3 1 2
3 2 1 2 1
1 2 1 2 1
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值