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}
∑k∈Sakaj。
这道题用到了一个点:
在一个偏好序列中,第j个元素前面的元素构成给定的一个集合的子集的概率。
等价于:在一个偏好序列中,第j个元素排在所有该给定集合外的元素中的第一位的概率。
而这个概率
=
a
j
∑
k
∈
S
′
a
k
=\frac{a_j}{\sum_{k\in S'}a_k}
=∑k∈S′akaj,其中
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
*/