传送门:link。
*A Alice and Bob
给定 (n) 个点 (m) 条边的 DAG,每个点染红/蓝色,求在每个点放 0/1 个棋子使得以下游戏中小红赢的方案数(mod 998244353)。
小红和小蓝轮流操作,每次选择一个与其对应颜色且有棋子的点,选择一个出边连向的点,移动一个棋子。
(nle 300)。
这是一个不平等博弈,套板子即可。设放在点 (u) 上的棋子所对应的游戏值为 (d_u),
则当 (u) 为红色时 (d_u=max({0}cup{d_v+1:(u,v)in E})),否则 (d_u=min({0}cup{d_v-1:(u,v)in E}))。
总共的和就是 (sum d_u),因为 (d_uin[-n,n]),所以直接搞个背包即可,时间复杂度 (O(n^3))。
B Brackets
对于长为 (2n) 的括号序列 (S),给定 ([1,2n]capN) 的一组匹配,要求每组匹配的对应位置的字符相同。
求满足条件且合法的字典序最小的 (S),需判断无解。
(nle 2cdot 10^5)。
猜结论+贪心。
对每组匹配按较小位置排序,按顺序考虑能不能填 (
。
给 (
赋权值 (1),)
赋权值 (-1)。
结论:若还没填的位置全都填 )
的时候最大后缀和非正且原问题有解,那么就是合法的。
证明就是调整法,看不太懂,自闭了。
#include<bits/stdc++.h>
using namespace std;
const int N = 200003, M = 1<<20;
template<typename T>
void read(T &x){
int ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
int n, p[N<<1], sec[N], sum[M], suf[M];
bool vis[N], ans[N];
void build(int x = 1, int L = 1, int R = n){
sum[x] = L-R-1; if(L == R) return;
int mid = L+R>>1;
build(x<<1, L, mid);
build(x<<1|1, mid+1, R);
}
void upd(int p, int v, int x = 1, int L = 1, int R = n){
if(L == R){sum[x] = v; suf[x] = max(v, 0); return;}
int mid = L+R>>1, ls = x<<1, rs = x<<1|1;
if(p <= mid) upd(p, v, ls, L, mid);
else upd(p, v, rs, mid+1, R);
sum[x] = sum[ls] + sum[rs];
suf[x] = max(suf[rs], suf[ls] + sum[rs]);
}
int main(){
read(n); n <<= 1; build();
for(int i = 1;i <= n;++ i){read(p[i]); sec[p[i]] = i;}
for(int i = 1;i <= n;++ i) if(!vis[p[i]]){
int j = sec[p[i]]; vis[p[i]] = true;
upd(i, 1); upd(j, 1);
if(suf[1] > 0){
ans[p[i]] = true;
upd(i, -1); upd(j, -1);
}
} int tmp = 0;
for(int i = 1;i <= n;++ i){
tmp += ans[p[i]] ? -1 : 1;
if(tmp < 0){puts("("); return 0;}
} if(tmp){puts("("); return 0;}
for(int i = 1;i <= n;++ i) putchar(ans[p[i]] ? ')' : '(');
}
-D Deja Vu
给定长为 (n) 的正整数序列 (x_1,x_2,cdots,x_n),要求支持 (q) 次操作:
- ( exttt{1 i y}):令 (x_i:=y)。
- ( exttt{2 l}):求 (min{dmid lle a<b<c<dland x_a<x_b<x_c<x_d}),需判断空集。
(n,qle 5cdot 10^5),(x_i,yle 10^9)。
好像很恶心,咕了
E Easiest Sum
给定长为 (n) 的整数序列 (a_1,cdots,a_n) 和正整数 (k)。
每次操作你可以选择 (pin[1,n]),令 (a_p:=a_p-1)。
定义 (g(i)) 表示操作 (i) 次之后最大子段和的最小值。
求 ((sum_{i=1}^kg(i))mod 998244353)。
(nle 10^5),(-10^8le a_ile 10^8),(kle 10^{13})。
容易发现 (g) 会有一堆一大段相同,所以转置一下,考虑求 (c(x)) 表示使所有子段和 (le x) 的最小操作次数。
设 (p_i) 表示对 (1,2,cdots,i) 的操作次数之和,则 (p_ige p_{i-1}),(p_j-p_igesum_{k=i+1}^ja_i-xpod{i<j}),要让 (p_n) 尽量小。
这是一个差分约束形式,最暴力的方法就是造一个有向图跑最长路:
- (i ightarrow j) 连权值为 (sum_{k=i}^{j-1}a_k-x) 的边((1le i<jle n+1))
- (i ightarrow i+1) 连权值为 (0) 的边((1le ile n))
然后就可以看出来 (c(x)=max_k(t_k-xk)),其中 (t_k) 表示 (k) 个互不相交的段的和最大值,这个是经典问题。
随着 (x) 减少取到最大值的 (k) 也是增加的,所以拿这一堆直线切出的就是一个凸包。
具体实现有点麻烦,分界点就是 (t_{k+1}-t_k),左开右闭的每一段是一堆线段形成等差数列的形式,最后剩下的散块要特判。
然后发现 pushup 写漏了,pushdown 忘调用了
#include<bits/stdc++.h>
#define MP make_pair
#define PB emplace_back
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef pair<LL, int> pli;
typedef pair<LL, pair<int, int> > plii;
pli operator - (const pli &a){return MP(-a.fi, a.se);}
plii operator - (const plii &a){return MP(-a.fi, a.se);}
pli operator + (const pli &a, const LL &b){return MP(a.fi + b, a.se);}
plii operator + (const pli &a, const pli &b){return MP(a.fi + b.fi, MP(a.se, b.se));}
const int N = 100010, M = 1<<18, mod = 998244353, inv2 = mod+1>>1;
LL qmo(LL x){return (x % mod + mod) % mod;}
template<typename T>
void read(T &x){
int ch = getchar(); x = 0; bool f = false;
for(;ch < '0' || ch > '9';ch = getchar()) f |= ch == '-';
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
if(f) x = -x;
}
int n, tot; LL k, sum[M], f[M]; bool tag[M];
pli mnp[M], mxp[M], mns[M], mxs[M]; plii mn[M], mx[M];
void prev(int x){
swap(mnp[x], mxp[x]); swap(mns[x], mxs[x]); swap(mn[x], mx[x]);
mnp[x] = -mnp[x]; mxp[x] = -mxp[x]; mns[x] = -mns[x]; sum[x] = -sum[x];
mxs[x] = -mxs[x]; mn[x] = -mn[x]; mx[x] = -mx[x]; tag[x] ^= 1;
}
void pdown(int x){if(tag[x]){prev(x<<1); prev(x<<1|1); tag[x] = false;}}
void pup(int x){
mnp[x] = min(mnp[x<<1], mnp[x<<1|1] + sum[x<<1]);
mxp[x] = max(mxp[x<<1], mxp[x<<1|1] + sum[x<<1]);
mns[x] = min(mns[x<<1|1], mns[x<<1] + sum[x<<1|1]);
mxs[x] = max(mxs[x<<1|1], mxs[x<<1] + sum[x<<1|1]);
mn[x] = min(min(mn[x<<1], mn[x<<1|1]), mns[x<<1] + mnp[x<<1|1]);
mx[x] = max(max(mx[x<<1], mx[x<<1|1]), mxs[x<<1] + mxp[x<<1|1]);
sum[x] = sum[x<<1] + sum[x<<1|1];
}
void build(int x, int L, int R){
if(L == R){
read(sum[x]); if(sum[x] < 0) f[tot--] = sum[x];
mn[x] = mx[x] = MP(sum[x], MP(L, L));
mnp[x] = mxp[x] = mns[x] = mxs[x] = MP(sum[x], L);
return;
} int mid = L+R>>1; build(x<<1, L, mid);
build(x<<1|1, mid+1, R); pup(x);
}
void upd(int x, int L, int R, int l, int r){
if(l <= L && R <= r){prev(x); return;}
int mid = L+R>>1; pdown(x);
if(l <= mid) upd(x<<1, L, mid, l, r);
if(mid < r) upd(x<<1|1, mid+1, R, l, r);
pup(x);
}
int main(){
read(n); tot = n; build(1, 1, n); read(k);
sort(f+tot+1, f+n+1, greater<LL>());
for(int i = 1;i <= n && mx[1].fi > 0;++ i){
f[i] = mx[1].fi; upd(1, 1, n, mx[1].se.fi, mx[1].se.se);
} f[n+1] = -2e13; LL used = 0, cur = f[1], ans = qmo(-cur);
for(int i = 1;i <= n;++ i){
LL nxt = f[i+1];
if(used + (cur - nxt) * i >= k){
nxt = cur - (k - used) / i; used += (cur - nxt) * i;
ans = (ans + qmo(cur+nxt+1) * qmo(cur-nxt) % mod * inv2 % mod * i + qmo(nxt) * qmo(k-used+1)) % mod;
break;
} used += (cur - nxt) * i;
ans = (ans + qmo(cur+nxt+1) * qmo(cur-nxt) % mod * inv2 % mod * i) % mod;
cur = nxt;
} printf("%lld
", ans);
}
F Funny Salesman
给定 (n) 个点的树,边带权,设 (d(u,v)) 表示 (u) 到 (v) 的简单路径上边权的最大值,求在所有长为 (n) 的排列 (p) 中,(sum_{i=2}^n2^{d(p_i,p_{i-1})}) 的最大值。
(nle 10^5),(win[0,30]capN)。
这个边权是 (2^w) 的性质好像 p 用没有,建 Kruskal 重构树然后贪心就完事了!
#include<bits/stdc++.h>
using namespace std;
typedef unsigned long long LL;
const int N = 100003, M = N<<1;
template<typename T>
void read(T &x){
int ch = getchar(); x = 0;
for(;ch < '0' || ch > '9';ch = getchar());
for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
}
int n, fa[M], val[M], siz[M], ls[M], rs[M]; LL ans;
struct Edge {
int u, v, w;
bool operator < (const Edge &o) const {return w < o.w;}
} e[N];
int getf(int x){return x == fa[x] ? x : fa[x] = getf(fa[x]);}
int main(){
read(n);
for(int i = 1;i <= n;++ i){fa[i] = i; siz[i] = 1;}
for(int i = 1;i < n;++ i){read(e[i].u); read(e[i].v); read(e[i].w);}
sort(e + 1, e + n);
for(int i = n+1;i < (n<<1);++ i){
ls[i] = getf(e[i-n].u); rs[i] = getf(e[i-n].v);
if(siz[ls[i]] > siz[rs[i]]) swap(ls[i], rs[i]);
fa[ls[i]] = fa[rs[i]] = fa[i] = i; val[i] = e[i-n].w;
siz[i] = siz[ls[i]] + siz[rs[i]];
} int u = (n<<1)-1, k = n-1;
while(u > n){
if(k <= (siz[ls[u]]<<1)){ans += (LL)k<<val[u]; break;}
ans += (LL)siz[ls[u]]<<val[u]+1; k -= siz[ls[u]]<<1; u = rs[u];
} printf("%llu
", ans);
}
G Graph Coloring
给定 (n) 个点的竞赛图,给边 (14) 染色,满足 (forall i ightarrow j ightarrow k),两条边的颜色不同。
(3le nle 3000)。
看到 (inom{14}7>3000) 想到....忘了叫啥名字了。
给每个点 (u) 赋颜色集合的不同 (7) 元子集 (S_u),(i
ightarrow j) 的颜色是 (S_iackslash S_j) 里的一个颜色。然后发现跟出题人心灵相通了
#include<bits/stdc++.h>
using namespace std;
const int N = 3003;
int n, m, a[N]; char str[N];
int main(){
scanf("%d", &n);
for(int i = 0;m < n;++ i) if(__builtin_popcount(i) == 7) a[m++] = i;
for(int i = 1;i < n;++ i){
scanf("%s", str);
for(int j = 0;j < i;++ j)
putchar(__builtin_ctz(str[j] == '1' ? (a[i] & ~a[j]) : (a[j] & ~a[i])) + 'a');
putchar('
');
}
}
*H Hidden Graph
这是一道交互题
给定正整数 (n),交互库有正整数 (k) 和一张 (n) 个点的无向图,满足所有导出子图的最小度数 (le k)。
你可以向交互库询问至多 (2nk+n) 次,每次询问给出点集 (S),交互库会返回给你 (S) 的导出子图中的一条边或告诉你 (S) 是独立集。求这张图。
(k<n),(nkle 2000)。
我也好喜欢贺题解啊
关键 trick:所有导出子图的最小度数 (le kRightarrow) 可以 (k+1) 染色。
考虑增量计算,维护 (k+1) 个独立集,若当前加入点 (i),那么将 (i) 跟每个独立集拼一拼,询问出所有边。
至多额外花费 (n(k+1)) 次询问,于是总询问次数 (le n(2k+1))。
*I Insects
初始时给定平面上 (n) 个黑点 ((a_i,b_i))。
定义第 (i) 个白点与第 (j) 个黑点之间有连边当且仅当 (x_ige a_jland y_ige b_j)。
要求支持 (m) 次询问,每次加一个白点 ((x_i,y_i)),求最小点覆盖。
(n,mle 10^5),(0le a_i,b_i,x_i,y_ile 10^5)。
我也好喜欢贺题解啊
这应该也是数竞题吧(
不妨设白点是左部点,黑点是右部点。
定理 1 (Konig Theorem) 二分图的最大匹配与最小点覆盖大小相同。
proves
显然最小点覆盖大小 (ge) 最大匹配大小。于是只需构造一组解即可。
对于一组最大匹配 (M),定义匹配边的方向从右向左,非匹配边的方向从左向右。
在这个有向图上,从左边未被匹配覆盖的点开始 DFS,设访问到的点集是 (Z),左右部点的集合是 (L,R)。
定义 (L_+=Lcap Z),(L_-=Lcap(V-Z)),(R_+=Rcap Z),(R_-=Rcap(V-Z))。
则 (L_-cup R_+) 是点覆盖,因为 (L_+) 与 (R_-) 之间没有边。
考虑证明 (|L_-cup R_+|=|M|)。由定义可得 (L_-subseteq V(M)),可以证明 (R_+subseteq V(M)),因为若 (vin R_+) 但 (v otin V(M)),则出发点到 (v) 的路径是一条增广路,与 (|M|) 的最大性相矛盾。且每条匹配边的两端点同时 (in Z),得证。
推论 2 (L_+cup R_-) 是最大独立集。
引理 3 (L_+cup R_+) 的最大匹配覆盖 (R_+)。
引理 4 (L_-cup R_-) 的最大匹配覆盖 (L_-)。
定义最优匹配是覆盖左部点编号字典序最小的最大匹配。
引理 5 第 (i) 次询问的答案即为 ([1,i]) 与最优匹配的交的大小。
引理 6 设 (A_k) 是左部的前 (k) 个点,考虑 (A_kcup R) 的导出子图,设 (A_-,A_+,R_-,R_+) 如上定义,(M_+) 是 (A_+cup R_+) 的最优匹配,则存在一组 (A_kcup R) 的最优匹配是 (M_+) 的超集。
proves
根据引理 4,(A_-cup R_-) 的最大匹配都是最优的。
所以将 (A_-cup R_-) 与 (A_+cup R_+) 的最优匹配直接合并就可以得到最大匹配,并且找不到更优的,所以是最优匹配。得证。
考虑分治,设函数 (f(L,R,x)) 表示当前在求 (Lcup R) 的最优匹配,但 (L) 的前 (|L|-x) 个点已经考虑过了,只需要考虑后面的 (x) 个点。
先考虑求出这 (x) 个点中前 (lfloor x/2 floor) 个点的最优匹配。设 (A) 是 (L) 的前 (|L|-lceil x/2 ceil) 个点,如上求出 (A_-,A_+,R_-,R_+)。
只需分别求出 (A_+cup R_+) 和 (A_-cup R_-) 的最优匹配,根据引理 3,(R_+) 会被 (A_+) 匹配完,根据引理 6,(A_+) 中未被匹配的点不会再被匹配,所以调用 (f(A_+,R_+,t)) 之后就可以把 (A_+,R_+) 删掉,其中 (t) 是 (A_+) 与 (L) 的后 (x) 个点的交大小。显然 (tlelfloor x/2 floor)。
然而 (R_-) 中剩下的点可能还有用处,所以不能删。
对于剩下的点,如上求出 (L_-,L_+,R_-,R_+),同理可得调用 (f(L_+,R_+,t)),其中 (t) 是 (L_+) 与 (L) 的后 (lceil x/2 ceil) 个点的交的大小。显然 (tlelceil x/2 ceil)。
根据引理 4,(L_-) 中的所有点都可以被匹配上。
最后 (x=1) 的时候可以直接用暴力方法跑两次最大匹配,看看 (L) 的最后一个点是不是必要的。
于是我们只需要更快地做最大匹配和 dfs 增广树。
前者可以直接贪心,将黑点和白点都按水平序排序,按顺序枚举白点,选择能选的黑点中纵坐标最大的。可以搞一个 set 维护。
后者同样排序,用线段树维护黑点的纵坐标的区间最小值,每次询问的时候就可以直接查了。
因为分治不超过 (log n) 层,并且每一层的 (L,R) 集合不交,所以总复杂度 (O(nlog^2n))。
代码好像有丶难写