XX Open Cup Grand Prix of Moscow【杂题】

传送门:link

*A Alice and Bob

给定 \(n\) 个点 \(m\) 条边的 DAG,每个点染红/蓝色,求在每个点放 0/1 个棋子使得以下游戏中小红赢的方案数\(\bmod 998244353\)

小红和小蓝轮流操作,每次选择一个与其对应颜色且有棋子的点,选择一个出边连向的点,移动一个棋子。

\(n\le 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_u\in[-n,n]\),所以直接搞个背包即可,时间复杂度 \(O(n^3)\)

B Brackets

对于长为 \(2n\) 的括号序列 \(S\),给定 \([1,2n]\cap\N\) 的一组匹配,要求每组匹配的对应位置的字符相同。

求满足条件且合法的字典序最小的 \(S\),需判断无解。

\(n\le 2\cdot 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\) 次操作:

  • \(\texttt{1 i y}\):令 \(x_i:=y\)
  • \(\texttt{2 l}\):求 \(\min\{d\mid l\le a<b<c<d\land x_a<x_b<x_c<x_d\}\),需判断空集。

\(n,q\le 5\cdot 10^5\)\(x_i,y\le 10^9\)


好像很恶心,咕了

E Easiest Sum

给定长为 \(n\) 的整数序列 \(a_1,\cdots,a_n\) 和正整数 \(k\)

每次操作你可以选择 \(p\in[1,n]\),令 \(a_p:=a_p-1\)

定义 \(g(i)\) 表示操作 \(i\) 次之后最大子段和的最小值。

\((\sum_{i=1}^kg(i))\bmod 998244353\)

\(n\le 10^5\)\(-10^8\le a_i\le 10^8\)\(k\le 10^{13}\)


容易发现 \(g\) 会有一堆一大段相同,所以转置一下,考虑求 \(c(x)\) 表示使所有子段和 \(\le x\) 的最小操作次数。

\(p_i\) 表示对 \(1,2,\cdots,i\) 的操作次数之和,则 \(p_i\ge p_{i-1}\)\(p_j-p_i\ge\sum_{k=i+1}^ja_i-x\pod{i<j}\),要让 \(p_n\) 尽量小。

这是一个差分约束形式,最暴力的方法就是造一个有向图跑最长路:

  • \(i\rightarrow j\) 连权值为 \(\sum_{k=i}^{j-1}a_k-x\) 的边(\(1\le i<j\le n+1\)
  • \(i\rightarrow i+1\) 连权值为 \(0\) 的边(\(1\le i\le 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\n", ans);
}

F Funny Salesman

给定 \(n\) 个点的树,边带权,设 \(d(u,v)\) 表示 \(u\)\(v\) 的简单路径上边权的最大值,求在所有长为 \(n\) 的排列 \(p\) 中,\(\sum_{i=2}^n2^{d(p_i,p_{i-1})}\) 的最大值。

\(n\le 10^5\)\(w\in[0,30]\cap\N\)


这个边权是 \(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\n", ans);
}

G Graph Coloring

给定 \(n\) 个点的竞赛图,给边 \(14\) 染色,满足 \(\forall i\rightarrow j\rightarrow k\),两条边的颜色不同。

\(3\le n\le 3000\)


看到 \(\binom{14}7>3000\) 想到....忘了叫啥名字了。

给每个点 \(u\) 赋颜色集合的不同 \(7\) 元子集 \(S_u\)\(i\rightarrow j\) 的颜色是 \(S_i\backslash 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('\n');
	}
}

*H Hidden Graph

这是一道交互题

给定正整数 \(n\),交互库有正整数 \(k\) 和一张 \(n\) 个点的无向图,满足所有导出子图的最小度数 \(\le k\)

你可以向交互库询问至多 \(2nk+n\) 次,每次询问给出点集 \(S\),交互库会返回给你 \(S\) 的导出子图中的一条边或告诉你 \(S\) 是独立集。求这张图。

\(k<n\)\(nk\le 2000\)


关键 trick:所有导出子图的最小度数 \(\le k\Rightarrow\) 可以 \(k+1\) 染色。

考虑增量计算,维护 \(k+1\) 个独立集,若当前加入点 \(i\),那么将 \(i\) 跟每个独立集拼一拼,询问出所有边。

至多额外花费 \(n(k+1)\) 次询问,于是总询问次数 \(\le n(2k+1)\)

*I Insects

初始时给定平面上 \(n\) 个黑点 \((a_i,b_i)\)

定义第 \(i\) 个白点与第 \(j\) 个黑点之间有连边当且仅当 \(x_i\ge a_j\land y_i\ge b_j\)

要求支持 \(m\) 次询问,每次加一个白点 \((x_i,y_i)\),求最小点覆盖。

\(n,m\le 10^5\)\(0\le a_i,b_i,x_i,y_i\le 10^5\)


不妨设白点是左部点,黑点是右部点。

定理 1 (Konig Theorem) 二分图的最大匹配与最小点覆盖大小相同。

proves

显然最小点覆盖大小 \(\ge\) 最大匹配大小。于是只需构造一组解即可。

对于一组最大匹配 \(M\),定义匹配边的方向从右向左,非匹配边的方向从左向右。

在这个有向图上,从左边未被匹配覆盖的点开始 DFS,设访问到的点集是 \(Z\),左右部点的集合是 \(L,R\)

定义 \(L_+=L\cap Z\)\(L_-=L\cap(V-Z)\)\(R_+=R\cap Z\)\(R_-=R\cap(V-Z)\)

\(L_-\cup R_+\) 是点覆盖,因为 \(L_+\)\(R_-\) 之间没有边。

考虑证明 \(|L_-\cup R_+|=|M|\)。由定义可得 \(L_-\subseteq V(M)\),可以证明 \(R_+\subseteq V(M)\),因为若 \(v\in R_+\)\(v\notin 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_k\cup R\) 的导出子图,设 \(A_-,A_+,R_-,R_+\) 如上定义,\(M_+\)\(A_+\cup R_+\) 的最优匹配,则存在一组 \(A_k\cup R\) 的最优匹配是 \(M_+\) 的超集。

proves
因为 $A_+\cup R_-$ 是独立集,所以 $A_+\cup R$ 的最优匹配就是 $M_+$。

根据引理 4,\(A_-\cup R_-\) 的最大匹配都是最优的。

所以将 \(A_-\cup R_-\)\(A_+\cup R_+\) 的最优匹配直接合并就可以得到最大匹配,并且找不到更优的,所以是最优匹配。得证。

考虑分治,设函数 \(f(L,R,x)\) 表示当前在求 \(L\cup R\) 的最优匹配,但 \(L\) 的前 \(|L|-x\) 个点已经考虑过了,只需要考虑后面的 \(x\) 个点。

先考虑求出这 \(x\) 个点中前 \(\lfloor x/2\rfloor\) 个点的最优匹配。设 \(A\)\(L\) 的前 \(|L|-\lceil x/2\rceil\) 个点,如上求出 \(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\) 个点的交大小。显然 \(t\le\lfloor x/2\rfloor\)

然而 \(R_-\) 中剩下的点可能还有用处,所以不能删。

对于剩下的点,如上求出 \(L_-,L_+,R_-,R_+\),同理可得调用 \(f(L_+,R_+,t)\),其中 \(t\)\(L_+\)\(L\) 的后 \(\lceil x/2\rceil\) 个点的交的大小。显然 \(t\le\lceil x/2\rceil\)

根据引理 4,\(L_-\) 中的所有点都可以被匹配上。

最后 \(x=1\) 的时候可以直接用暴力方法跑两次最大匹配,看看 \(L\) 的最后一个点是不是必要的。

于是我们只需要更快地做最大匹配和 dfs 增广树。

前者可以直接贪心,将黑点和白点都按水平序排序,按顺序枚举白点,选择能选的黑点中纵坐标最大的。可以搞一个 set 维护。

后者同样排序,用线段树维护黑点的纵坐标的区间最小值,每次询问的时候就可以直接查了。

因为分治不超过 \(\log n\) 层,并且每一层的 \(L,R\) 集合不交,所以总复杂度 \(O(n\log^2n)\)

代码好像有丶难写

posted @ 2021-05-11 21:06  mizu164  阅读(336)  评论(0编辑  收藏  举报