300iq Contest 2【杂题】

传送门:link

D Determinant

给定 \(n\) 个点 \(m\) 条边的简单连通无向图,求邻接矩阵行列式\(\bmod 998244353\)。

\(n\le 2.5\cdot 10^4\),\(m\le 5\cdot 10^5\),\(\exist k\in\{1,2,\cdots,25\}\),\(\forall A\subseteq V\land |A|=k+1\),\(\exist a,b\in A\),\(e\in E\),删去 \(e\) 之后 \(a,b\) 不连通。


我们可以从题目条件推出这个图的双连通分量大小 \(\le k\)。

于是想到可以把双连通分量缩点,对这棵树搞树形 dp。

但好像转移很难弄,因为要选择每个点事与连通分量内的点在同一个环还是与树边匹配。

实际上都可以加起来,用一个行列式可以表示,每次暴力算就可以了,时间复杂度 \(O(nk^2)\)。

#include<bits/stdc++.h>
#define PB emplace_back
using namespace std;
typedef long long LL;
const int N = 25003, M = 1e6 + 3, K = 26, mod = 998244353;
int read(){
	int ch = getchar(), x = 0;
	for(;ch < '0' || ch > '9';ch = getchar());
	for(;ch >= '0' && ch <= '9';ch = getchar()) x = x * 10 + ch - '0';
	return x;
} template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int ksm(int a, int b){
	int res = 1;
	for(;b;b >>= 1, a = (LL)a * a % mod)
		if(b & 1) res = (LL)res * a % mod;
	return res; 
} 
int n, m, dfn[N], low[N], stk[N], tim, tp, col[N], id[N], ccnt;
bool ins[N]; vector<int> G[N], E[N], bl[N];
void dfs0(int x, int fa){
	low[x] = dfn[x] = ++tim; stk[++tp] = x; ins[x] = true;
	for(int v : G[x]) if(v != fa){
		if(!dfn[v]){dfs0(v, x); chmin(low[x], low[v]);}
		else if(ins[v]) chmin(low[x], dfn[v]);
	} if(dfn[x] == low[x]){ ++ccnt;
		while(true){
			int u = stk[tp--];
			col[u] = ccnt; ins[u] = false;
			id[u] = bl[ccnt].size();
			bl[ccnt].PB(u); if(u == x) break;
		}
	}
}
int coe[K], val[K][K], dp[N][2];
int Gauss(int n){
	static int a[K][K]; int ans = 1;
	for(int i = 0;i < n;++ i)
		for(int j = 0;j < n;++ j)
			if(i != j) a[i][j] = (LL)val[i][j] * coe[i] % mod;
			else a[i][j] = val[i][j];
	for(int i = 0;i < n;++ i){
		if(!a[i][i]){
			int p = 0;
			for(int j = i+1;j < n;++ j)
				if(a[j][i]){p = j; break;}
			if(!p) return 0;
			swap(a[i], a[p]); ans = mod - ans;
		} ans = (LL)ans * a[i][i] % mod;
		int inv = ksm(a[i][i], mod-2);
		for(int j = i+1;j < n;++ j) if(a[j][i]){
			int tmp = mod - (LL)a[j][i] * inv % mod;
			for(int k = i;k < n;++ k)
				a[j][k] = (a[j][k] + (LL)tmp * a[i][k]) % mod;
		}
	} return ans;
}
void dfs(int x, int fa){
	for(int v : E[x]) if(v != fa) dfs(v, x);
	int _ = bl[x].size(); coe[_] = 1;
	for(int i = 0;i <= _;++ i) memset(val[i], 0, _+1<<2);
	int spe = 0;
	for(int u : bl[x]){
		int res0 = 1, res1 = 0;
		for(int v : G[u]){
			if(col[v] == x) ++val[id[u]][id[v]];
			else if(col[v] == fa) spe = id[u];
			else {
				res1 = ((LL)res1 * dp[col[v]][0] + (LL)res0 * dp[col[v]][1]) % mod;
				res0 = (LL)res0 * dp[col[v]][0] % mod;
			}
		} coe[id[u]] = res0; val[id[u]][id[u]] = res1;
	} dp[x][0] = Gauss(_);
	val[spe][_] = val[_][spe] = 1;
	dp[x][1] = Gauss(_ + 1);
}
int main(){
	n = read(); m = read(); read();
	for(int i = 0, u, v;i < m;++ i){
		u = read(); v = read();
		G[u].PB(v); G[v].PB(u);
	} dfs0(1, 0);
	for(int i = 1;i <= n;++ i)
		for(int j : G[i]){
			int x = col[i], y = col[j];
			if(x < y){E[x].PB(y); E[y].PB(x);}
		}
	dfs(1, 0); printf("%d", dp[1][0]);
}

E Easy Win

给定 \(n\) 个点 \(q\) 条边的无向图,边带两个整数权值 \(a,w\)。

\(\forall i\in[1,q]\),在前 \(i\) 条边中,求满足每个点度数为偶数的生成子图的 \(a\) 的异或和均不为 \(0\) 的生成子图的 \(w\) 之和的最大值。

\(n\le 64\),\(q\le 2\cdot 10^5\),\(0\le a_i<2^{60}\),\(1\le w_i\le 10^9\)。


每个点度数为偶数的限制可以跟 \(a\) 的限制合并。于是就是线性基模板了。

F Fast Spanning Tree

给定 \(n\) 个点的无向图和 \(m\) 个整数三元组 \((a_i,b_i,s_i)\),第 \(i\) 个点的点权为 \(w_i\)。进行以下操作尽量多次,求每次选取的 \(i\)。

  • 求出最小的 \(i\) 使得 \(a_i,b_i\) 在不同连通块,且这两个连通块的 \(w\) 之和 \(\ge s_i\),将 \(a_i,b_i\) 之间连边。

\(n,m\le 3\cdot 10^5\)。


404 Not Found.

#include<bits/stdc++.h>
#define MP make_pair
#define PB emplace_back
#define fi first
#define se second 
using namespace std;
typedef pair<int, int> pii;
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';
} const int N = 3e5+3, INF = 1e6;
int n, m, w[N], a[N], b[N], fa[N], s[N]; vector<int> ans;
priority_queue<pii, vector<pii>, greater<pii> > pq[N];
priority_queue<int, vector<int>, greater<int> > can;
int getf(int x){return x == fa[x] ? x : fa[x] = getf(fa[x]);}
void push(int _){
	int u = getf(a[_]), v = getf(b[_]);
	if(u == v) return;
	if(w[u] + w[v] >= s[_]){can.emplace(_); return;}
	int d = s[_]-w[u]-w[v]+1>>1;
	pq[u].emplace(w[u]+d, _);
	pq[v].emplace(w[v]+d, _);
}
void merge(int u, int v){
	if(pq[u].size() < pq[v].size()) swap(u, v);
	fa[v] = u; w[u] = min(w[u] + w[v], INF);
	while(!pq[u].empty()){
		pii _ = pq[u].top();
		if(_.fi > w[u]) break;
		pq[u].pop(); push(_.se);
	} while(!pq[v].empty()){
		pii _ = pq[v].top();
		if(_.fi > w[u]) break;
		pq[v].pop(); push(_.se);
	} while(!pq[v].empty()){pq[u].emplace(pq[v].top()); pq[v].pop();}
}
int main(){
	read(n); read(m);
	for(int i = 1;i <= n;++ i){fa[i] = i; read(w[i]);}
	for(int i = 1;i <= m;++ i){read(a[i]); read(b[i]); read(s[i]); push(i);}
	while(!can.empty()){
		int _ = can.top(); can.pop();
		int u = getf(a[_]), v = getf(b[_]);
		if(u != v){ans.PB(_); merge(u, v);}
	} printf("%u\n", ans.size());
	for(int u : ans) printf("%d ", u);
}

H Honorable Mention

给定长为 \(n\) 的整数序列 \(a_i\),\(q\) 次询问 \(l,r,k\) 表示求 \(a[l,r]\) 的 \(k\) 个不交子段和的最大值。

\(n,a_i,q\le 35000\)。


众所周知答案关于 \(k\) 是凸的。

把区间左右端点是否选上记到状态里,线段树维护对应区间的答案,询问的时候平行 wqs 二分,因为斜率单调的时候对应最优位置也单调,可以少一个 log。

时间复杂度是大常数的 \(O((n+q)\log n\log V)\)。

#include<bits/stdc++.h>
#define PB emplace_back
#define MP make_pair
#define fi first
#define se second
using namespace std;
typedef long long LL;
typedef vector<int> VI;
typedef pair<LL, int> pli;
const int N = 35003, M = 1<<17, INF = 1.3e9;
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;
} template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
int n, Q, a[N], ps[M][2][2], l[N], r[N], kk[N], ans[N]; VI hul[M][2][2];
void build(int x, int L, int R){
	if(L == R){ int _; read(_);
		hul[x][0][0].PB(0); hul[x][0][0].PB(_);
		hul[x][0][1].PB(_); hul[x][1][0].PB(_);
		hul[x][1][1].PB(_); return;
	} int mid = L+R>>1; build(x<<1, L, mid); build(x<<1|1, mid+1, R);
	for(int a = 0;a < 2;++ a)
		for(int b = 0;b < 2;++ b)
			for(int c = 0;c < 2;++ c)
				for(int d = 0;d < 2;++ d){
					VI res((a||b)+(c||d)-(a||d)-(b&&c), -INF);
					VI &A = hul[x<<1][a][b], &B = hul[x<<1|1][c][d], &C = hul[x][a][d];
					int px = 0, py = 0;
					while(true){
						res.PB(A[px] + B[py]);
						if(px+1 == A.size() && py+1 == B.size()) break;
						if(px+1 < A.size() && (py+1 == B.size() || A[px+1] - A[px] > B[py+1] - B[py])) ++ px;
						else ++ py;
					} while(C.size() < res.size()) C.PB(-INF);
					for(int i = 0;i < res.size();++ i) chmax(C[i], res[i]);
				}
} pli res[2];
void calc(int x, int L, int R, int l, int r, int slp){ // qws!
	if(l <= L && R <= r){
		pli num[2] = {MP(-INF, 0), MP(-INF, 0)};
		for(int a = 0;a < 2;++ a)
			for(int b = 0;b < 2;++ b){
				VI &C = hul[x][a][b]; int &p = ps[x][a][b];
				while(p && C[p-1] > C[p] - slp) -- p;
				for(int c = 0;c < 2;++ c){
					int cnt = p + (a||b) - (c&&a);
					chmax(num[b], MP(res[c].fi + C[p] - (LL)slp * cnt, res[c].se + cnt));
				}
			}
		res[0] = num[0]; res[1] = num[1]; return;
	} int mid = L+R>>1;
	if(l <= mid) calc(x<<1, L, mid, l, r, slp);
	if(mid < r) calc(x<<1|1, mid+1, R, l, r, slp);
}
struct Node {
	int l, r, id;
	Node(int _l = 0, int _r = 0, int _i = 0): l(_l), r(_r), id(_i){}
	bool operator < (const Node &o) const {return l < o.l || l == o.l && id < o.id;}
} q[N];
int main(){
	read(n); read(Q); build(1, 1, n);
	for(int i = 1;i <= Q;++ i){
		read(l[i]); read(r[i]); read(kk[i]);
		q[i] = Node(-INF, INF, i);
	} bool flg = true;
	while(flg){
		flg = false;
		for(int i = 1;i < M;++ i)
			for(int a = 0;a < 2;++ a)
				for(int b = 0;b < 2;++ b)
					ps[i][a][b] = (int)hul[i][a][b].size() - 1;
		sort(q + 1, q + Q + 1);
		for(int i = 1;i <= Q;++ i) if(q[i].l < q[i].r){
			int mid = (1ll + q[i].l + q[i].r) >> 1;
			int _ = q[i].id; res[0] = MP(0, 0); res[1] = MP(-INF, 0);
			calc(1, 1, n, l[_], r[_], mid);
			if(kk[_] <= max(res[0], res[1]).se) q[i].l = mid;
			else q[i].r = mid-1;
			flg = true;
		}
	}
	for(int i = 1;i <= Q;++ i){
		int _ = q[i].id; res[0] = MP(0, 0); res[1] = MP(-INF, 0);
		calc(1, 1, n, l[_], r[_], q[i].l);
		ans[_] = max(res[0], res[1]).fi + (LL)q[i].l * kk[_];
	} for(int i = 1;i <= Q;++ i) printf("%d\n", ans[i]);
}

I Interactive Vertex

这是一道交互题

给定 \(n\) 个点的树,交互库有一个点 \(u\),你可以询问至多 \(4\lceil\log_2n\rceil\) 次来求出 \(u\)。

每次询问给出 \(x,v_1,\cdots,v_k\),交互库告诉你 \([\forall i\in[1,k]\cap\N,\text{dis}(u,v_i)\ge\text{dis}(u,x)]\)。

\(n\le 2\cdot 10^5\)。


树上求点当然要点分治。

若 \(v_1,\cdots,v_k\) 都与 \(x\) 相邻,那么返回 \(1\) 当且仅当 \(u\) 不在 \(v_1,\cdots,v_k\) 的子树内。于是就可以对子树二分了。

询问次数是 \(O(\log n)\) 的,但是不会算常数。

#include<bits/stdc++.h>
#define PB emplace_back
#define fi first
#define se second
using namespace std;
const int N = 200003;
typedef pair<int, int> pii;
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';
} template<typename T>
bool chmax(T &a, const T &b){if(a < b) return a = b, 1; return 0;}
template<typename T>
bool chmin(T &a, const T &b){if(a > b) return a = b, 1; return 0;}
int n, cnt, head[N], to[N<<1], nxt[N<<1];
void add(int u, int v){
	to[++cnt] = v; nxt[cnt] = head[u]; head[u] = cnt;
}
int siz[N], rt, mx; bool vis[N]; vector<pii> V;
void dfs(int x, int f, int S){
	int tmp = 0; siz[x] = 1;
	for(int i = head[x];i;i = nxt[i]) if(to[i] != f && !vis[to[i]]){
		dfs(to[i], x, S); siz[x] += siz[to[i]];
		chmax(tmp, siz[to[i]]);
	} chmax(tmp, S - siz[x]);
	if(chmin(mx, tmp)) rt = x;
}
int ask(int x, int L, int R){
	printf("? %d %d", R-L+1, x);
	for(int i = L;i <= R;++ i) printf(" %d", V[i].se);
	putchar('\n'); fflush(stdout); read(x); return x;
}
void solve(int x, int S){
	if(S == 1){printf("! %d\n", x); exit(0);}
	mx = N; dfs(x, 0, S); vis[x = rt] = true; V.resize(0);
	for(int i = head[x];i;i = nxt[i]) if(!vis[to[i]])
		V.PB(siz[to[i]] > siz[x] ? S - siz[x] : siz[to[i]], to[i]);
	sort(V.begin(), V.end()); int L = 0, R = (int)V.size()-1;
	if(ask(x, L, R)){printf("! %d\n", x); exit(0);}
	while(L < R){
		int sum = 0, mn = N, tmp = 0, mid = 0;
		for(int i = L;i <= R;++ i) sum += V[i].fi;
		for(int i = L;i <= R;++ i){
			if(chmin(mn, max(tmp, sum - tmp))) mid = i-1; tmp += V[i].fi;
		} ask(x, L, mid) ? L = mid+1 : R = mid;
	} solve(V[L].se, V[L].fi);
}
int main(){ read(n);
	for(int i = 1, u, v;i < n;++ i){
		read(u); read(v); add(u, v); add(v, u);
	} solve(1, n);
}

K K-pop Strings

给定自然数 \(n,k\),求有多少个长为 \(n\) 的字符串不存在长度 \(\ge n-k\) 的平方串。

\(n\le 100\),\(k\le 16\),\(|\Sigma|=35\)。


神必题。暴力容斥,如果当前考虑的段加不加都一样那么直接 return,就可以过了。

上一篇:2020 China Collegiate Programming Contest, Weihai Site H. Message Bomb(差分/思维/好题)


下一篇:C#路径/文件/目录/I/O常见操作汇总