https://codeforces.com/contest/1783
F. Double Sort II 给你两个 1~n的排列a,b
每次操作可以任选一个i, 交换 a[i] 和 a[?]=i, 交换 b[i] 和 b[?]=i
求最小操作次数 的一个具体方案 让 a,b 有序
范围 n 3000
2s
512mb
我的思路 又是排列问题, 排列的交换就是和 i -> a[i] 建立的环相关, 每次交换不同的值,环变化都是1(+1/-1),
如果单独看 a,
每次只要不是操作 swap(ai,ai), 都是 环+1
所以问题变成:
找尽量少的 下标集合, 使得a,b 中的 环中被选中的个数 >= 所在环点的个数-1
3000 似乎希望n^2 的样子
考虑 如果把一个变有序
(x-x-x-x) (x-x-x) ...
另一个怎么转移
1 2 a: 1-2 3-4 5-6-7-8 b: 1-2-3-4 5-6 7-8
题解 也是说用 i -> pi 构成环来考虑
也是, 排序后的 都是一个个自环
也是, 每次 的操作 = 环+1 , 或者自己交换等于没效果, 也是单独排序每个环操作 长度-1次
修改题目: 最大化不被选的点, 让任意两个不被选的点不在同一个环上(哦 我没想到反过来
如果希望i不被选. 在a和b的i所在环中其它的都被选了. 如果a,b 的 两个环 有相同的点, 可以让它不被选, 其它的点则必选. 构建二分图, 左边点 表示a中的环, 右边点表示b中的环. 每个i是一条从左向右的边. 所以选边和不选点i就对应上了, 而选了以后 左右两侧的都不能再备选, 这就是二分图最大匹配
代码 https://codeforces.com/contest/1783/submission/188644065
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 #include <bits/stdc++.h> using namespace std;#define rep(i,a,n) for (int i=a;i<(int)n;i++) #define per(i,a,n) for (int i=n;i-->(int)a;) int read () {int r;scanf ("%d" ,&r);return r;}int a[3010 ];int b[3010 ];int ca[3010 ]; int cb[3010 ]; vector<pair<int ,int >> g[6010 ]; int v2e[6010 ]; int vis[6010 ];int e2u (int eid) { return ca[eid]-1 ; }bool f (int u) { if (vis[u])return false ; vis[u] = true ; for (auto [v,eid]:g[u]) if (!vis[v]){ vis[v]=true ; if (!v2e[v] or f (e2u (v2e[v]))){ v2e[v]=eid; return true ; } } return false ; } int main () { int n=read (); rep (i,1 ,n+1 ) a[i]=read (); rep (i,1 ,n+1 ) b[i]=read (); int xa=0 ; rep (i,1 ,n+1 ) if (ca[i]==0 ){ xa++; for (int t=i;ca[t]==0 ;t=a[t]) ca[t] = xa; } int xb=0 ; rep (i,1 ,n+1 ) if (cb[i]==0 ){ xb++; for (int t=i;cb[t]==0 ;t=b[t]) cb[t] = xb; } auto e2v=[&](int eid){ return xa+cb[eid]-1 ;}; rep (i,1 ,n+1 ) g[e2u (i)].push_back ({e2v (i),i}); rep (i,0 ,xa) { fill (vis,vis+xa+xb,false ); f (i); } vector<int > ans (n+1 ,true ) ; rep (i,xa,xa+xb) ans[v2e[i]]= false ; int cnt=0 ; rep (i,1 ,n+1 ) cnt+=ans[i]; printf ("%d\n" ,cnt); rep (i,1 ,n+1 ) if (ans[i]) printf ("%d " ,i); printf ("\n" ); return 0 ; }
G. Weighed Tree Radius 有点权a[i]的n个点树
距离 d(u,v) = u..v路径上边的个数
带权 距离 w(u,v) = d(u,v) + a[v], 注意没有对称性 w(u,v) 不一定等于 w(v,u)
e(u) = max w(u, …)
r = min e(…)
m 个操作, 每次 修改一个点的权a, 并输出修改后的r
范围 n 2e5
ai [0,1e6]
m 1e5
6s
512mb
我的思路 对于每个u, 连出a[u]条边 得到新的点u’,
那么e(u) = max d(u,?’)
r = min e(…)
u..v’ 被选做为 r, 且 u-pu-…v’, pu 也是原始点
那么 其它的 u..?’ 的最大长度不小于 r-1, 否则 可以选pu 得到更小的r
所以 半径 = (直径+1)整除/2
所以 r = max((max(d(…’,…’))+1)/2, max(a[…]))
问题是 a[i] 很大不能真的建立, 就算要 那就是改边权, 去点权,
另一个就是如何维护直径, 如果能维护直径, 那么就是多和 a[…] 取个max而已
并不会维护直径
只能感觉到 当 a[u] 小于一个值val时 跟它无关, 而当大于val时, 就是 它增1, 直径增1, 但是这个val是由树和其它a[…]决定的, 并不是只于树有关
始终都感觉, 改一个, 都在变
既没有一个批量更改的办法, 也没找到一个不变量
题解 定义 路径 wpath(u,v) = a[u] + d(u,v) + a[v], 有 wpath(u,u) = 2a[u]
那么直径是 最大的wpath(… , …)
那么 e = 直径/2 向上取整数
观察 a[u] 变化 对 直径的影响
a[u] 增加
所有以u结尾的 都增加, 可能的新的直径为
wpath(u,u) = 2a[u]
wpath(u,v) = a[u] + d(u,v) + a[v] <= a[u] + max(d(u,x) + a[x])
d(u,v) = dep[u]+dep[v] - 2 dep[lca(u,v)]
a[u] 减少
使用 DCP(dynamic connectivity problem)
track 每个a[v], 每个可能的a[v] , 某个 v 会active 在某个 [l,r) 询问, m询问, 因此总共会有 n+m 个 区间
assign av=x on some segment of queries [l,r)
,这样 之前的a[v] 为0, 只需要处理 增加的问题
线段树维护
总结就是
当一个树的目前直径是 x—-y 时, 现在把u 延长到u’
那么 新的直径可能是
u’…u’
u’…x
u’…y
x…y
那么, 延长好处理的话
对于时刻i
就是把 所有 相关的从0 延长到 a[u]
但是这样的话是 m * m的复杂度
但是从 a[u] = val 的是一个时间区间, 所以 区间就可以用线段树维护
每次处理区间的 0 -> a[u] 后, 向子树传递 当前的 x,y,d(x,y) 即可
代码 https://codeforces.com/contest/1783/submission/188799117
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 #include <bits/stdc++.h> using namespace std;#define rep(i,a,n) for (int i=a;i<(int)n;i++) #define SEG_ROOT 0,0,m #define SEG_L ((o<<1)+1) #define SEG_R ((o<<1)+2) #define mid (l+r)/2 #define SEG_L_CHILD SEG_L,l,mid #define SEG_R_CHILD SEG_R,mid,r int read () {int r;scanf ("%d" ,&r);return r;}const int INF = 0x3f3f3f3f ;int n;vector<int > g[200010 ]; struct Lca { vector<int > log2; vector<int > tin; vector<vector<int >> hs; void dfs (int v, int p, int dep, int &T) { hs[0 ][tin[v]=T++] = dep; for (int u: g[v]) if (u!=p) { dfs (u, v, dep+1 , T); hs[0 ][T++] = dep; } } void init () { log2=vector (2 *n, 0 ); rep (i, 2 , size (log2)) log2[i] = log2[i/2 ] + 1 ; hs.assign (log2.back ()+1 , vector <int >(2 *n, INF)); tin=vector (n,0 ); int T = 0 ; dfs (0 ,-1 ,0 ,T); assert (T==2 *n-1 ); rep (pw,0 ,size (hs)-1 ) rep (i,0 ,T-(1 <<pw)) hs[pw+1 ][i] = min (hs[pw][i], hs[pw][i+(1 <<pw)]); } int lcadep (int u, int v) { if (tin[u] > tin[v]) swap (u, v); int len = log2[tin[v] + 1 - tin[u]]; return min (hs[len][tin[u]], hs[len][tin[v]+1 -(1 <<len)]); } int getDist (int u, int v) {return hs[0 ][tin[u]]+hs[0 ][tin[v]]-2 *lcadep (u,v);} } lcaST; int getFarthest (int s) { vector vis (n, false ) ; vector<int > q = {s}; vis[s] = true ; for (int i=0 ;i<(int )q.size ();i++){ int v = q[i]; for (int u: g[v]) if (!vis[u]) { vis[u] = true ; q.push_back (u); } } return q.back (); } vector<pair<int ,int >> ops[800010 ]; void setOp (int o, int l, int r, int ql, int qr, const pair<int ,int > &op) { if (ql <= l && r <= qr) return ops[o].push_back (op); if (ql < mid) setOp (SEG_L_CHILD, ql, qr, op); if (qr > mid) setOp (SEG_R_CHILD, ql, qr, op); } vector<int > a; vector<int > ans; void calcDiams (int o, int l, int r, array<int ,3 > dst ) { for (auto &op : ops[o]) { auto [v,val] = op; assert (a[v] == 0 ); a[v] = val; auto [d,s,t]=dst; array<pair<int ,int >,3> cds = {{ {s, v}, {v, t}, {v, v} }}; for (auto [x,y]: cds) dst = max (dst,array<int ,3 >{a[x]+lcaST.getDist (x, y)+a[y],x,y}); } if (r-l > 1 ) { calcDiams (SEG_L_CHILD, dst); calcDiams (SEG_R_CHILD, dst); } else { ans[l] = (dst[0 ]+1 ) / 2 ; } for (auto [v,val] : ops[o]) a[v]=0 ; } int main () { n=read (); a.resize (n); rep (i,0 ,n) a[i]=read (); rep (i,0 ,n-1 ) { int u=read ()-1 ; int v=read ()-1 ; g[u].push_back (v); g[v].push_back (u); } lcaST.init (); int s = getFarthest (0 ); int t = getFarthest (s); int m=read (); vector<int > lst (n, 0 ) ; rep (i,0 ,m) { int v=read ()-1 ; int x=read (); if (lst[v]<i) setOp (SEG_ROOT, lst[v], i, {v, a[v]}); lst[v] = i; a[v] = x; } rep (v,0 ,n) setOp (SEG_ROOT, lst[v], m, {v, a[v]}); ans.resize (m, -1 ); a.assign (n, 0 ); calcDiams (SEG_ROOT, {lcaST.getDist (s, t),s,t}); rep (i,0 ,m) printf ("%d\n" ,ans[i]); return 0 ; }
总结 F
基本的排列 环的感觉是有的, 最后推不下去了, 没想到反过来, 还是太菜了
G
能想到 去变成 (直径+1)/2, 但不会维护,
这个 跳点+线段树, 有点意思, 特别是这里从0 到 正的维护
树的直径维护, 感觉还是 完全没想到 x—y 为直径时, u增加长度会带来的变化
参考 官方