zoukankan      html  css  js  c++  java
  • 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))

    代码好像有丶难写

  • 相关阅读:
    size_t类型
    sudo 安装 ——Debian 6
    Ubuntu10.10 上海交大及其他教育网更新源
    Linux 命令解决小问题
    VMware Player tools for linux 安装
    内核总结之内存管理api (转)
    volatile的使用
    二叉树层次遍历队列实现
    Ubuntu 11.04 下OpenCV安装
    Ubuntu 添加教育网更新源【转】
  • 原文地址:https://www.cnblogs.com/AThousandMoons/p/14757336.html
Copyright © 2011-2022 走看看