A - Auto-correction
留坑。
B - Breaking Down News
表示前个数在合法划分情况下的最大值。
转移方程:,其中,是前缀和。
考虑权值线段树优化,分类讨论如下:
- ,
- ,
- ,
对于这个条件需要单调队列维护。
因为我常数太大了,需要手写,快读才能过。
#include<bits/stdc++.h>
using namespace std ;
const int maxn = 2e6 + 10 ;
int mx[maxn << 2] ;
vector<int> v[maxn] ;
int l[maxn] , r[maxn] ;
int ls(int x)
{
return x << 1 ;
}
int rs(int x)
{
return x << 1 | 1 ;
}
void build(int id , int l , int r)
{
if(l == r){mx[id] = -1e9 ; return ;}
int mid = (l + r) >> 1 ;
build(ls(id) , l , mid) ;
build(rs(id) , mid + 1 , r) ;
mx[id] = max(mx[ls(id)] , mx[rs(id)]) ;
}
void update(int id , int l , int r , int x , int y)
{
int mid = (l + r) / 2 ;
if(l == r && l == x){mx[id] = y ; return ;}
if(x <= mid) update(ls(id) , l , mid , x , y) ;
else update(rs(id) , mid + 1 , r , x , y) ;
mx[id] = max(mx[ls(id)] , mx[rs(id)]) ;
}
int query(int id , int l , int r , int x , int y)
{
int ans = -1e9 ;
int mid = (l + r) / 2 ;
if(y < x || y < l || x > r) return -1e9 ; //不合法的询问
if(x <= l && r <= y) return mx[id] ;
if(x <= mid) ans = max(ans , query(ls(id) , l , mid , x , y)) ;
if(y > mid) ans = max(ans , query(rs(id) , mid + 1 , r , x , y)) ;
return ans ;
}
template<typename T> void read(T &res) //read(n) ;
{
bool flag = 0 ;
char ch ;
while(!isdigit(ch = getchar()))(ch == '-') && (flag = 1) ;
for(res = ch - 48 ;
isdigit(ch = getchar()) ;
res = (res << 1) + (res << 3) + ch - 48) ;
flag && (res = -res) ;
}
int main()
{
//std::ios::sync_with_stdio(false) , cin.tie(0) ;
int T ;
read(T) ;
while(T --)
{
int n , L , R ;
read(n) ;
read(L) ;
read(R) ;
vector<int> a(n + 1 , 0) ;
for(int i = 1 ; i <= n ; i ++) read(a[i]) , a[i] += a[i - 1] ;
int m = n * 2 + 1 ; //[-n , n] -> [1 , 2 * n + 1]
build(1 , 1 , m) ;
for(int i = 1 ; i <= m ; i ++) v[i].clear() , l[i] = 0 , r[i] = -1 ;
vector<int> dp(n + 1 , -1e9) ;
dp[0] = 0 ;
// i - R <= j <= i - L
// a[j] > a[i] dp[i] = max(dp[j] - 1)
// a[j] < a[i] dp[i] = max(dp[j] + 1)
// a[j] = a[i] dp[i] = max(dp[j])
auto change = [&](int u)
{
return u + n + 1 ;
} ;
auto del = [&](int i)
{
if(i < 0 || dp[i] < -1e8) return ;
int t = change(a[i]) ;
if(l[t] <= r[t] && v[t][l[t]] == i) l[t] ++ ;
if(l[t] > r[t]) update(1 , 1 , m , t , -1e9) ;
else update(1 , 1 , m , t , dp[v[t][l[t]]]) ;
} ;
auto add = [&](int i)
{
if(i < 0 || dp[i] < -1e8) return ;
int t = change(a[i]) ;
while(l[t] <= r[t] && dp[v[t][r[t]]] <= dp[i]) r[t] -- ;
if(r[t] + 1 <= (int)v[t].size() - 1) r[t] ++ , v[t][r[t]] = i ;
else r[t] ++ , v[t].push_back(i) ;
update(1 , 1 , m , t , dp[v[t][l[t]]]) ;
} ;
auto ask = [&](int i)
{
dp[i] = max(dp[i] , query(1 , 1 , m , 1 , change(a[i] - 1)) + 1) ;
dp[i] = max(dp[i] , query(1 , 1 , m , change(a[i] + 1) , m) - 1) ;
dp[i] = max(dp[i] , query(1 , 1 , m , change(a[i]) , change(a[i]))) ;
} ;
for(int i = 1 ; i <= n ; i ++)
{
del(i - R - 1) ;
add(i - L) ;
ask(i) ;
}
printf("%d\n" , dp[n]) ;
}
return 0 ;
}
C - Clockwise or Counterclockwise
温暖的签到题。
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
ll cross(int x1, int y1, int x2, int y2, int x3, int y3)
{
return 1ll*(x2-x1)*(y3-y1) - 1ll*(y2-y1)*(x3-x1);
}
void solve()
{
int x1, y1, x2, y2, x3, y3;
scanf("%d%d%d%d%d%d", &x1, &y1, &x2, &y2, &x3, &y3);
if(cross(x1, y1, x2, y2, x3, y3)<0) puts("Clockwise");
else puts("Counterclockwise");
}
int main()
{
int _; scanf("%d", &_);
while(_--) solve();
return 0;
}
D - Discovery of Cycles
定义表示区间有环且是相对于的最小值。
显然是递增的,可以双指针计算。
环可以通过维护。
#include <bits/stdc++.h>
using namespace std;
const int N = 6e5 + 5;
struct LCT
{
int ch[N][2], fa[N], rev[N];
int id[N], mn[N];
void clear(int x)
{
ch[x][0] = ch[x][1] = fa[x] = rev[x] = 0;
id[x] = mn[x] = N;
}
void init(int n) { for(int i=1; i<=n; i++) clear(i); }
#define rs(x) (ch[fa[x]][1]==x)
int notrt(int x) { return ch[fa[x]][0]==x || ch[fa[x]][1]==x; }
void up(int x)
{
mn[x] = id[x];
if(ch[x][0]) mn[x] = min(mn[x], mn[ch[x][0]]);
if(ch[x][1]) mn[x] = min(mn[x], mn[ch[x][1]]);
}
void setrev(int x)
{
swap(ch[x][0], ch[x][1]);
rev[x] ^= 1;
}
void down(int x)
{
if(rev[x])
{
if(ch[x][0]) setrev(ch[x][0]);
if(ch[x][1]) setrev(ch[x][1]);
rev[x] = 0;
}
}
void downall(int x)
{
if(notrt(x)) downall(fa[x]);
down(x);
}
void rotate(int x)
{
int y = fa[x], z = fa[y], k = rs(x), w = rs(y);
if(notrt(y)) ch[z][w] = x;
ch[y][k] = ch[x][!k]; fa[ch[x][!k]] = y;
ch[x][!k] = y; fa[y] = x; fa[x] = z;
up(y); up(x); up(z);
}
void splay(int x)
{
downall(x);
for(int f=fa[x]; f=fa[x], notrt(x); rotate(x))
if(notrt(f)) rotate(rs(x) == rs(f) ? f : x);
}
void access(int x)
{
for(int y=0; x; x=fa[y=x])
splay(x), ch[x][1] = y, up(x);
}
void makeroot(int x)
{
access(x); splay(x);
setrev(x);
}
int findroot(int x)
{
access(x); splay(x);
while(ch[x][0]) down(x), x = ch[x][0];
splay(x);
return x;
}
void split(int x, int y)
{
makeroot(x);
access(y); splay(y);
}
void link(int x, int y)
{
makeroot(x);
fa[x] = y;
}
void cut(int x, int y)
{
split(x, y);
fa[x] = ch[y][0] = 0;
up(y);
}
}T;
struct edge
{
int u, v;
}e[N];
int n, m, q, pre[N];
int fa[N];
void init(int n) { iota(fa+1, fa+n+1, 1); }
int find(int x) { return x==fa[x] ? x : fa[x] = find(fa[x]); }
void merge(int x, int y)
{
int fx = find(x), fy = find(y);
if(fx!=fy) fa[fx] = fy;
}
void solve()
{
scanf("%d%d%d", &n, &m, &q);
T.init(n+m);
init(n);
for(int i=1; i<=m; i++)
{
T.mn[n+i] = T.id[n+i] = i;
scanf("%d%d", &e[i].u, &e[i].v);
if(find(e[i].u)!=find(e[i].v))
{
T.link(e[i].u, n+i); T.link(e[i].v, n+i);
merge(e[i].u, e[i].v);
pre[i] = 1;
}
else
{
T.split(e[i].u, e[i].v);
int id = T.mn[e[i].v];
T.cut(e[id].u, e[id].v);
pre[i] = id + 1;
T.link(e[i].u, n+i); T.link(e[i].v, n+i);
}
pre[i] = max(pre[i], pre[i-1]);
}
int lst = 0;
for(int i=1; i<=q; i++)
{
int l, r; scanf("%d%d", &l, &r);
int k1 = (l^lst)%m + 1;
int k2 = (r^lst)%m + 1;
l = min(k1, k2), r = max(k1, k2);
if(pre[r]>l) puts("Yes"), lst = 1;
else puts("No"), lst = 0;
}
}
int main()
{
int _; scanf("%d", &_);
while(_--) solve();
return 0;
}
E - Easy NPC Problem
留坑。
F - Fluctuation Limit
温暖的签到题。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 5;
int n, k, l[N], r[N], x[N];
void solve()
{
scanf("%d%d", &n, &k);
for(int i=1; i<=n; i++) scanf("%d%d", l+i, r+i);
for(int i=2; i<=n; i++)
{
int pl = l[i-1] - k, pr = r[i-1] + k;
l[i] = max(l[i], pl);
r[i] = min(r[i], pr);
if(l[i]>r[i]) { puts("NO"); return; }
}
puts("YES");
x[n] = l[n];
for(int i=n-1; i>=1; i--)
{
int pl = max(x[i+1]-k, l[i]);
x[i] = pl;
}
for(int i=1; i<=n; i++) printf("%d%c", x[i], " \n"[i==n]);
}
int main()
{
int _; scanf("%d", &_);
while(_--) solve();
return 0;
}
G - Gaming of Co-prime Disallowance
迷一样的题意,不知道在说啥,只好自己脑补了另一个题意。
按照下面题意写代码才能过:
取过k张牌,如果k张牌的gcd=1,那么取第k张牌的人赢。
1.n个数的gcd>1,问你先手赢的概率
2.n个数的gcd=1,问你后手赢的概率
其实主要问题是怎么算一方赢的概率,不妨设为先手。
首先一个不超过10w的数最多有6个不同质因子。枚举第一张牌,对于剩下所有数的质因子要么是第一张牌中的质因子,要么不是,考虑状压枚举。
表示选择了张牌,且质因子状压是的概率。通过这个可以算出答案。
考虑到精度的问题,需要边加边除。不然你直接除个直接凉凉。
具体转移看代码吧,细节有一丢丢麻烦。
#include<bits/stdc++.h>
using namespace std ;
typedef double db ;
const int maxn = 1e5 + 10 ;
const db eps = 1e-6 ;
int cnt = 0 ;
bool vis[maxn] ;
int prime[maxn] ;
void get_prime(int up) //??ɸ
{
memset(vis , 0 , sizeof(vis)) ;
vis[1] = 1 ;
for(int i = 2 ; i <= up ; i ++)
{
if(!vis[i])
prime[++ cnt] = i ;
for(int j = 1 ; j <= cnt && i * prime[j] <= up ; j ++)
{
vis[i * prime[j]] = 1 ;
if(i % prime[j] == 0) break ;
}
}
}
int main()
{
std::ios::sync_with_stdio(false) , cin.tie(0) ;
int T ;
cin >> T ;
get_prime(100000) ;
while(T --)
{
int n ;
cin >> n ;
vector<int> a(n + 1) ;
for(int i = 1 ; i <= n ; i ++) cin >> a[i] ;
int d = a[1] ;
for(int i = 2 ; i <= n ; i ++) d = __gcd(d , a[i]) ;
vector<vector<int>> p(n + 1) ;
auto init = [&]()
{
for(int i = 1 ; i <= n ; i ++)
{
int tmp = a[i] ;
for(int j = 1 ; prime[j] * prime[j] <= a[i] ; j ++)
{
if(a[i] % prime[j] == 0)
{
p[i].push_back(prime[j]) ;
while(a[i] % prime[j] == 0) a[i] /= prime[j] ;
}
}
if(a[i] > 1) p[i].push_back(a[i]) ;
a[i] = tmp ;
}
} ;
init() ;
db ans = 0.0 ;
auto cal = [&](int s)
{
if(a[s] == 1) return 1.0 / n ;
int num = (1 << p[s].size()) ;
vector<vector<db>> dp(n + 1 , vector<db>(num , 0.0)) ;
vector<int> c(num , 0) , d(num , 0) , e(num , 0) ;
vector<vector<int>> f(num , vector<int>(num , 0)) ;
db res = 0.0 ;
for(int i = 1 ; i <= n ; i ++)
{
int t = 0 ;
for(int j = 0 ; j < (int)p[s].size() ; j ++)
{
if(a[i] % p[s][j] == 0) t ^= (1 << j) ;
}
c[t] ++ ;
}
for(int i = 0 ; i < num ; i ++) for(int j = 0 ; j < num ; j ++)
if((i & j) == i) d[i] += c[j] ;
for(int i = 0 ; i < num ; i ++) for(int j = 0 ; j < num ; j ++)
if((i & j) == 0) e[i] += c[j] ;
for(int i = 0 ; i < num ; i ++) for(int j = 0 ; j < num ; j ++) for(int k = 0 ; k < num ; k ++)
if((i & j) == k) f[i][k] += c[j] ;
dp[1][num - 1] = 1.0 / n ;
for(int i = 1 ; i <= n - 1 ; i ++)
{
for(int j = 1 ; j < num ; j ++)
{
for(int k = j ; k ; k = (k - 1) & j)
{
if(k == j && d[k] > i) dp[i + 1][k] += dp[i][j] * (d[k] - i) / (n - i) ;
else if(k != j && f[j][k]) dp[i + 1][k] += dp[i][j] * f[j][k] / (n - i) ;
}
if((i + 1) % 2 == 1) res += dp[i][j] * e[j] / (n - i) ;
}
}
return res ;
} ;
if(d > 1)
{
if(n % 2 == 1) cout << "1.000000000\n" ;
else cout << "0.000000000\n" ;
}
else
{
for(int i = 1 ; i <= n ; i ++) ans += cal(i) ;
cout << fixed << setprecision(9) << 1 - ans << '\n' ;
}
}
return 0 ;
}
H - Hexagon
需要发现交替出现的规律才会很好写。不过强力队友没发现此规律硬写三个小时也通过了。膜!
#include<bits/stdc++.h>
using namespace std;
#define M 1005
string ans;
int rx[]={0,1,1,0,-1,-1};
int ry[]={2,1,-1,-2,-1,1};
int mark[M<<1][M<<1];
int kase;
bool In(int x,int y,int n){
return x<=n&&x>=-n&&x+y<=2*n&&x+y>=-2*n&&x-y<=2*n&&x-y>=-2*n;
}
bool check(int x,int y,int n){
if(x==n||x==-n||x+y==2*n||x+y==-2*n||x-y==2*n||x-y==-2*n)return 1;
return 0;
}
bool Near(int x,int y,int prex,int prey){
for(int i=0;i<6;i++){
if(x+rx[i]==prex&&y+ry[i]==prey)return 1;
}
return 0;
}
void move(int &x,int &y,int n){
if(check(x,y,n)){
for(int i=0;i<6;i++){
int X=x+rx[i],Y=y+ry[i];
if(In(X,Y,n)&&mark[X+M][Y+M]!=kase&&!check(X,Y,n)){
ans+=(i+1+'0');
x=X;y=Y;
return;
}
}
for(int i=0;i<6;i++){
int X=x+rx[i],Y=y+ry[i];
if(In(X,Y,n)&&mark[X+M][Y+M]!=kase&&check(X,Y,n)){
ans+=(i+1+'0');
x=X;y=Y;
return;
}
}
}
else {
for(int i=0;i<6;i++){
int X=x+rx[i],Y=y+ry[i];
if(In(X,Y,n)&&mark[X+M][Y+M]!=kase&&check(X,Y,n)){
ans+=(i+1+'0');
x=X;y=Y;
return;
}
}
for(int i=0;i<6;i++){
int X=x+rx[i],Y=y+ry[i];
if(In(X,Y,n)&&mark[X+M][Y+M]!=kase&&!check(X,Y,n)){
ans+=(i+1+'0');
x=X;y=Y;
return;
}
}
}
}
void solve(){
int n;
++kase;
scanf("%d",&n);
ans="";
string tmp="612423534645156261";
if(n&1){
int x=0,y=0;
mark[0+M][0+M]=kase;
for(int i=3;i<=n;i+=2){
ans+="61";
mark[x-1+M][y+1+M]=kase;
x--;y+=3;
mark[x+M][y+M]=kase;
int m=(i+i-3)*6;
while(m--){
move(x,y,i-1);
mark[x+M][y+M]=kase;
}
}
for(int i=0;i<18;i++)ans[i]=tmp[i];
}
else{
ans+="134561";
int x=-1,y=1;
mark[0+M][0+M]=kase;
mark[0+M][2+M]=kase;
mark[1+M][1+M]=kase;
mark[1+M][-1+M]=kase;
mark[0+M][-2+M]=kase;
mark[-1+M][-1+M]=kase;
mark[-1+M][1+M]=kase;
for(int i=4;i<=n;i+=2){
ans+="61";
mark[x-1+M][y+1+M]=kase;
x--;y+=3;
mark[x+M][y+M]=kase;
int m=(i+i-3)*6;
while(m--){
move(x,y,i-1);
mark[x+M][y+M]=kase;
}
}
}
cout<<ans<<'\n';
}
int main(){
int T;
scanf("%d",&T);
while(T--)solve();
return 0;
}
I - Isomorphic Strings
枚举,对于的个循环同构串进行,然后枚举是否是的循环同构串。
手写可以通过。或者大力卡常。
#include <bits/stdc++.h>
#include <ext/pb_ds/assoc_container.hpp>
using namespace std;
using namespace __gnu_pbds;
using ull = uint64_t;
const ull base = 131, mod = 1e9 + 7;
const int N = 5e6 + 5;
const int M = 2174729;
struct HashSet
{
struct node { int next; ull k; }e[M+5];
int head[M], st[N], tp, sz;
int f(ull k) { return k % M; }
void clear() { sz = 0; while(tp) head[st[tp--]] = 0; }
bool get(ull k)
{
for(int p = head[f(k)]; p; p = e[p].next)
if(e[p].k==k) return true;
return false;
}
void ins(ull k)
{
if(get(k)) return;
int t = f(k);
e[++sz] = {head[t], k};
head[t] = sz; st[++tp] = t;
}
}st;
int n;
char s[N];
ull pw[N], h[N];
void init()
{
pw[0] = 1;
for(int i=1; i<=n; i++)
{
h[i] = (h[i-1]*base + s[i])%mod;
pw[i] = pw[i-1]*base%mod;
}
}
ull get(int l, int r) { return (h[r]-h[l-1]*pw[r-l+1]%mod+mod)%mod; }
bool check(int k)
{
static ull ht[N];
for(int i=1; i<=k; i++) ht[i] = (ht[i-1]*base + s[i])%mod;
for(int i=1; i<=k; i++) ht[i+k] = (ht[i+k-1]*base + s[i])%mod;
st.clear();
for(int l=1; l<=k; l++)
{
int r = l + k - 1;
ull cur = (ht[r]-ht[l-1]*pw[r-l+1]%mod+mod)%mod;
st.ins(cur);
}
for(int l=1; l<=n; l+=k) if(!st.get(get(l, l+k-1))) return false;
puts("Yes");
return true;
}
void solve()
{
scanf("%d%s", &n, s+1);
init();
if(n==1) { puts("No"); return; }
bool same = 1;
for(int i=2; i<=n; i++) same &= (s[i]==s[i-1]);
if(same) { puts("Yes"); return; }
for(int k=2; k*k<=n; k++)
{
if(n%k==0)
{
if(check(k)) return;
if(k*k!=n && check(n/k)) return;
}
}
puts("No");
}
int main()
{
int _; scanf("%d", &_);
while(_--) solve();
return 0;
}
J - Jumping on a Cactus
留坑。
K - Kidnapper's Matching Problem
设构成的线性基是,消除中的位后剩下,消除中的位后剩下。
结论是。
证明:,其中可以被表示。因为,则,即。
证明:考虑反证,即证明。都有无法表示的位,因为,则有无法表示的位,即。
然后把在线性基中跑一遍后,再做一遍即可。
#include<bits/stdc++.h>
using namespace std ;
const int maxn = 2e5 + 10 ;
const int mod = 1e9 + 7 ;
struct Linear_base
{
bool zero ;
int cnt ;
int b[35] , p[35] ;
void init()
{
zero = 0 ;
cnt = 0 ;
memset(b , 0 , sizeof(b)) ;
memset(p , 0 , sizeof(p)) ;
}
bool insert(int x)
{
for(int i = 29 ; i >= 0 ; i --)
if(x & (1 << i))
{
if(b[i]) x ^= b[i] ;
else
{
b[i] = x ;
return 1 ;
}
}
zero = 1 ;
return 0 ;
}
int get(int x)
{
for(int i = 29 ; i >= 0 ; i --)
if(x & (1 << i))
{
if(b[i]) x ^= b[i] ;
}
return x ;
}
} linear_base ;
int qpow(int a , int b) //快速幂
{
if(b < 0) return 0 ;
int ans = 1 ;
a %= mod ;
while(b)
{
if(b & 1) ans = (1ll * ans * a) % mod ;
b >>= 1 , a = (1ll * a * a) % mod ;
}
return ans % mod ;
}
struct KMP
{
int s1[maxn] , s2[maxn] ;
int len1 , len2 ;
int nxt[maxn] ;
void get_nxt(vector<int> a , vector<int> b)
{
nxt[0] = -1 ;
int j = 0 , k = -1 ;
len1 = a.size() - 1 ;
len2 = b.size() - 1 ;
for(int i = 0 ; i < len1 ; i ++) s1[i] = a[i + 1] ;
for(int i = 0 ; i < len2 ; i ++) s2[i] = b[i + 1] ;
while(j < len2)
{
if(k == -1 || s2[j] == s2[k]) j ++ , k ++ , nxt[j] = k ;
else k = nxt[k] ;
}
}
void solve()
{
int ans = 0 ;
int i = 0 , j = 0 ;
while(i < len1)
{
if(j == -1 || s1[i] == s2[j]) i ++ , j ++ ;
else j = nxt[j] ;
if(j == len2)
{
ans += qpow(2 , i - len2) ;
ans %= mod ;
j = nxt[j] ;
}
}
cout << ans << '\n' ;
}
} kmp ;
int main()
{
std::ios::sync_with_stdio(false) , cin.tie(0) ;
int T ;
cin >> T ;
while(T --)
{
int n , m , k ;
cin >> n >> m >> k ;
vector<int> a(n + 1) , b(m + 1) , c(k + 1) ;
for(int i = 1 ; i <= n ; i ++) cin >> a[i] ;
for(int i = 1 ; i <= m ; i ++) cin >> b[i] ;
for(int i = 1 ; i <= k ; i ++) cin >> c[i] ;
linear_base.init() ;
for(int i = 1 ; i <= k ; i ++) linear_base.insert(c[i]) ;
for(int i = 1 ; i <= n ; i ++) a[i] = linear_base.get(a[i]) ;
for(int i = 1 ; i <= m ; i ++) b[i] = linear_base.get(b[i]) ;
kmp.get_nxt(a , b) ;
kmp.solve() ;
}
return 0 ;
}
L - Linuber File System
表示节点的所有祖先的修改的权值和是的以为根的子树的最小操作次数。
其中只能是若干个及中的一个,如果不是,可以在不增加操作次数的前提下调整到是。具体原因只能脑补,不太会严格证明。有了这个结论就可以对离散化了。
如果,节点可以选择不调整,也可以选择调整至范围内的其他权值。
如果,节点只能选择调整至范围内的其他权值。
#include<bits/stdc++.h>
using namespace std ;
int main()
{
std::ios::sync_with_stdio(false) , cin.tie(0) ;
int T ;
cin >> T ;
while(T --)
{
int n ;
cin >> n ;
vector<vector<int>> g(n + 1) ;
vector<int> l(n + 1) , r(n + 1) ;
vector<int> p ;
for(int i = 1 ; i <= n - 1 ; i ++)
{
int u , v ;
cin >> u >> v ;
g[u].push_back(v) ;
g[v].push_back(u) ;
}
for(int i = 1 ; i <= n ; i ++) cin >> l[i] >> r[i] ;
p.push_back(0) ;
for(int i = 1 ; i <= n ; i ++) p.push_back(l[i]) , p.push_back(r[i]) ;
sort(p.begin() , p.end()) ;
p.erase(unique(p.begin() , p.end()) , p.end()) ;
int siz = p.size() ;
vector<vector<int>> dp(n + 1 , vector<int>(siz , n)) ;
for(int i = 1 ; i <= n ; i ++)
{
l[i] = lower_bound(p.begin() , p.end() , l[i]) - p.begin() ;
r[i] = lower_bound(p.begin() , p.end() , r[i]) - p.begin() ;
}
//dp[i][j]表示节点i的所有祖先的修改的权值和是j的以i为根的子树的最小修改次数
//j只能是若干个l[i],r[i]及0中的一个,如果不是,可以调整到是。
function<void(int , int)> dfs = [&](int fa , int u)
{
vector<int> sum(siz , 0) ;
vector<int> pre(siz , n) ;
vector<int> suf(siz , n) ;
for(auto v : g[u])
{
if(v == fa) continue ;
dfs(u , v) ;
for(int i = 0 ; i < siz ; i ++) sum[i] += dp[v][i] ;
}
for(int i = 0 ; i < siz ; i ++)
{
if(i == 0)
{
if(l[u] <= i && i <= r[u]) pre[i] = sum[i] ;
}
else
{
if(l[u] <= i && i <= r[u]) pre[i] = min(pre[i - 1] , sum[i]) ;
else pre[i] = pre[i - 1] ;
}
}
for(int i = siz - 1 ; i >= 0 ; i --)
{
if(i == siz - 1)
{
if(l[u] <= i && i <= r[u]) suf[i] = sum[i] ;
}
else
{
if(l[u] <= i && i <= r[u]) suf[i] = min(suf[i + 1] , sum[i]) ;
else suf[i] = suf[i + 1] ;
}
}
for(int i = 0 ; i < siz ; i ++)
{
if(l[u] <= i && i <= r[u])
{
dp[u][i] = min(dp[u][i] , sum[i]) ;
int tmp1 = n ;
if(i > 0) tmp1 = pre[i - 1] ;
int tmp2 = n ;
if(i + 1 < siz) tmp2 = suf[i + 1] ;
dp[u][i] = min({dp[u][i] , tmp1 + 1 , tmp2 + 1}) ;
}
else
{
int tmp1 = pre[r[u]] ;
int tmp2 = suf[l[u]] ;
dp[u][i] = min({dp[u][i] , tmp1 + 1 , tmp2 + 1}) ;
}
}
} ;
dfs(1 , 1) ;
int zero = lower_bound(p.begin() , p.end() , 0) - p.begin() ;
cout << dp[1][zero] << '\n' ;
}
return 0 ;
}