XX Open Cup Grand Prix of Moscow【杂题】

传送门: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
因为 $A_+cup R_-$ 是独立集,所以 $A_+cup R$ 的最优匹配就是 $M_+$。

根据引理 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))

代码好像有丶难写

原文地址:https://www.cnblogs.com/AThousandMoons/p/14757336.html