BZOJ 杂题选记(8.24 - 8.30)

P2546 SZK - Mirror trap

第一问是 \(n / 2\)。我们考虑一条从顶点射出的光线,因为射出点不同,所以光线之间不会相交。而一个从顶点射出的光线一定会终止于某个顶点,所以是一一对应的。
构造方案的话,考虑旋转 45°,这样光线就是垂直于坐标轴的。然后维护连通性,具体来说,\(x\) 坐标相同的点,按照 \(y\) 坐标排序后,第一个和第二个是互相可达的,第三个和第四个是互相可达的.....\(y\) 坐标同理。两个顶点在一个连通块内,就可以输出方案了。
注意特判一些跳点的情况,如果这个顶点射出的光线是沿 \(x\) 轴的,那么在连 \(y\) 轴光线就跳过它。类似的也可以讨论连 \(x\) 轴光线的情况。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 3e5 + 5;
struct node{
	int x, y, v, id;
}p[N];
int px[N], py[N], len[N], n, s[N], f[N];
int getf(int u){ return (u == s[u] ? u : s[u] = getf(s[u])); }
void merge(int u, int v){
	u = getf(u), v = getf(v);
	if(f[u] && f[v]) cout << f[u] << ' ' << f[v] << '\n';
	else if(f[u] || f[v]) f[u] += f[v];
	s[v] = u, f[v] = 0;
}
int nxt(int i){ return (i == n ? 1 : i + 1); }
int pre(int i){ return (i == 1 ? n : i - 1); }

int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n;
	for(int i = 1; i <= n; ++i){
		cin >> px[i] >> py[i];
		len[i] = px[i] + py[i];
	}
	for(int i = 1; i <= n; ++i) len[i] = px[nxt(i)] + py[nxt(i)] - len[i]; 
	int x = 0, y = 0, cnt = 0;
	for(int i = 1; i <= n; ++i){
		auto ins = [&](){
			p[++cnt] = node{x - y, x + y, -1, cnt};
			s[cnt] = cnt;
		};
		if(i & 1){
			if(len[i] > 0){
				for(int j = 0; j < len[i]; ++j) ins(), ++x;
				int tmp = cnt - len[i] + 1;
				p[tmp].v = len[pre(i)] < 0;
				f[tmp] = i;
			}
			else{
				for(int j = 0; j < -len[i]; ++j) ins(), --x;
				int tmp = cnt + len[i] + 1;
				p[tmp].v = len[pre(i)] > 0;
				f[tmp] = i;
			}
		}
		else{
			if(len[i] > 0){
				for(int j = 0; j < len[i]; ++j) ins(), ++y;
				int tmp = cnt - len[i] + 1;
				p[tmp].v = len[pre(i)] < 0;
				f[tmp] = i; 
			}
			else{
				for(int j = 0; j < -len[i]; ++j) ins(), --y;
				int tmp = cnt + len[i] + 1;
				p[tmp].v = len[pre(i)] > 0;
				f[tmp] = i;
			}
		}
	}
	cout << n / 2 << '\n';
	sort(p + 1, p + 1 + cnt, [](node i, node j){
		return i.x == j.x ? i.y < j.y : i.x < j.x;
	});
	int lst = 0;
	for(int i = 1; i <= cnt; ++i){
		if(p[i].x != p[i - 1].x) lst = 0;
		if(p[i].v == 0) continue;
		if(!lst) lst = p[i].id;
		else merge(p[i].id, lst), lst = 0; 
	}
	sort(p + 1, p + 1 + cnt, [](node i, node j){
		return i.y == j.y ? i.x < j.x : i.y < j.y; 
	});
	lst = 0;
	for(int i = 1; i <= cnt; ++i){
		if(p[i].y != p[i - 1].y) lst = 0;
		if(p[i].v == 1) continue;
		if(!lst) lst = p[i].id;
		else merge(p[i].id, lst), lst = 0;
	}
	return 0;
}

P3470 BBB-BBB

好水的紫。发现操作一和操作二是相互独立的,可以分开考虑。二操作的代价很好求,一操作要做多少次取决于长度为 \(n\) 的区间内最小的前缀和是多少,然后就单调队列维护这个就行(好像遇到过了,这个很经典)。注意一些计算代价的细节。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e6 + 5;
ll a[2 * N], ori, tgt, n, x, y, tag, val[2 * N], cnt[2], k, ans = 1e18;
void chmin(ll &s, ll t){ s = min(s, t); }
deque<int> q;
ll ins(int idx){
    val[idx] = -tag;
    while(!q.empty() && q.front() >= idx + n) q.pop_front();
    tag += a[idx];
    while(!q.empty() && val[q.back()] > val[idx]) q.pop_back();
    q.push_back(idx);
    return val[q.front()] + tag;
}
int main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n >> ori >> tgt >> x >> y;
    for(int i = 1; i <= n; ++i){
        char x; cin >> x;
        if(x == '-') a[i] = -1, cnt[0]++;
        else a[i] = 1, cnt[1]++;
    }
    k = (cnt[1] - cnt[0] - tgt + ori) / 2;
    for(int i = n + 1; i <= 2 * n; ++i) a[i] = a[i - n]; 
    for(int i = 2 * n; i >= 1; --i){
        ll mn = ins(i) + (k > 0 ? 0 : -2ll * k), tmp = n + 1 - i;
        if(i <= n + 1){
            if(mn >= -ori) chmin(ans, tmp * y);
            else chmin(ans, 2 * ((abs(mn) - abs(ori) + 1) / 2) * x + tmp * y);
        }
    }
    cout << ans + abs(k) * x;
    return 0;
}

P3472 MAF-Mafia

感觉又评高了很多。最大点数的情况很简单,如果当前连通块是基环树,那么答案为非叶节点的个数在加上环长,如果是纯环,那么答案为环长减一。
最小点数的话先考虑树上的情况,这个可以 dp。 \(f_u\) 表示 \(u\) 死的最小伤害数,\(g_u\) 表示 \(u\) 活的最小伤害数,转移是 trival 的。环上的情况也可以先断成链类似 dp,只需要最后判断一下第一个点和最后一个点的死活状况合不合法就行。
本题最大的难点貌似在直接递归 dfs 会爆空间,要写非递归的 dfs,调了我好久。

Code
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 1e6 + 5, inf = N;
bitset<N> vis, on, in;
vector<int> e[N], ring[N];
int cnt, s[N], sta[N], n, rdfn[N]; 
ll f[N], g[N], siz[N], a[2][2], b[2][2];
struct mystack{
    int tp = 0;
    void push(int u){ in[u] = 1, sta[++tp] = u; }
    void pop(){ in[sta[tp]] = 0, --tp; }
    int top(){ return sta[tp]; }
    bool empty(){ return tp == 0; }
    bool check(int u){ return in[u]; }
}st;
 
void dfs(int rt) {
    st.push(rt);
    vis[rt] = 1;
    while(!st.empty()){
        int u = st.top();
        if(!vis[s[u]]){
            vis[s[u]] = 1;
            st.push(s[u]);
        } 
        else{
            if(st.check(s[u])){
                ++cnt;
                for (int i = st.tp; i; --i){
                    int idx = sta[i];
                    ring[cnt].push_back(idx);
                    on[idx] = 1;
                    if (idx == s[u]) break;
                }
            }
            st.pop();
        }
    }
}
 
void init(int rt) {
    st.push(rt);
    int tsp = 0;
    while (!st.empty()){
        int u = st.top();
        st.pop();
        rdfn[++tsp] = u;
        for (int v : e[u]){
            if (!on[v])
                st.push(v);
        }
    }
    for (int i = tsp; i; --i){
        int u = rdfn[i];
        bool fl = 1;
        for (int v : e[u]) {
            if (!on[v]){
                siz[u] += siz[v];
                f[u] = min(f[u] + min(g[v], f[v]), g[u] + g[v]);
                g[u] += f[v];
                fl = 0;
            }
        }
        if(fl) f[u] = inf, g[u] = 0;
        else siz[u]++, f[u]++;
    }
}
 
signed main(){
    cin.tie(nullptr)->sync_with_stdio(0);
    cin >> n;
    for(int i = 1; i <= n; ++i){
        cin >> s[i];
        e[s[i]].push_back(i);
    }
    ll mn = 0, mx = 0;
    for(int i = 1; i <= n; ++i){
        if(!vis[i]) dfs(i);
    }
    for(int i = 1; i <= cnt; ++i){
        ll sum = 0, len = ring[i].size();
        bool fl = 1;
        for(int u : ring[i]){
            init(u);
            if(siz[u]){
                fl = 0;
                sum += siz[u] - 1;
            }
            else f[u] = 1;
        }
        if(fl) mx += (len == 1 ? 1 : len - 1);
        else mx += sum + len;
        int nw = 0;
        a[0][1] = b[0][0] = inf;
        a[0][0] = f[ring[i].front()], b[0][1] = g[ring[i].front()];
        for(int j = 1; j < len; ++j){
            int u = ring[i][j];
            nw ^= 1;
            for(int t : {0, 1}){
                a[nw][t] = min(b[nw ^ 1][t], a[nw ^ 1][t]) + f[u];
                b[nw][t] = a[nw ^ 1][t] + g[u]; 
            }
        }
        mn += min({a[nw][0], a[nw][1], b[nw][0]}); 
    }
    cout << mn << ' ' << mx << '\n';
    return 0;
}

P3471 POC-Trains

STL 可以直接水过......
优化的方法基于一个观察,实际上我们只需要在一个火车被改变时更新它的答案就行。
具体如何更新呢,题解给的方法是:对于每一种字符串,我们维护一条链。当一个字符串 \(s\) 被改变为 \(s'\) 时,我们记录此时的 \(s'\) 的这条链有几个元素 \(l\)。并查询此时有多少个字符串为 \(s'\),将 \(num_{s'}\) 加入链尾。当这个字符串又被改变时,我们查询一下从 \(l + 1\) 到链的末端的最大值,更新该字符串的答案即可。
那么就变成了,加入链尾,查询后缀最大值的问题。然后这个可以用类似路径压缩的方法维护,均摊 \(O(1)\)
当然我肯定写 STL 的。

Code
#include <bits/stdc++.h>
using namespace std;
using ull = unsigned long long;
constexpr int N = 1e3 + 5, L = 1e2 + 5;
constexpr ull P = 133;
string s[N];
ull hsh[N][L];
map<ull, unordered_set<int> > mp;
int n, l, m, ans[N];
void get(int idx, int id){
	for(int i = id; i <= l; ++i){
		hsh[idx][i] = hsh[idx][i - 1] * P + (s[idx][i] - 'a');
	}
}
void upd(unordered_set<int> &s){
	int tmp = s.size();
	for(int x : s) ans[x] = max(ans[x], tmp);
}
void swap(int i, int x, int j, int y){
	if(i == j){
		mp[hsh[i][l]].erase(i);
		swap(s[i][x], s[j][y]);
		get(i, min(x, y));
		mp[hsh[i][l]].insert(i);
		upd(mp[hsh[i][l]]);
	} 
	else{
		mp[hsh[i][l]].erase(i), mp[hsh[j][l]].erase(j);
		swap(s[i][x], s[j][y]);
		get(i, x), get(j, y);
		mp[hsh[i][l]].insert(i), mp[hsh[j][l]].insert(j);
		upd(mp[hsh[i][l]]), upd(mp[hsh[j][l]]);
	}
	
}
int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n >> l >> m;
	for(int i = 1; i <= n; ++i){
		cin >> s[i];
		s[i] = "#" + s[i];
		get(i, 1);
		mp[hsh[i][l]].insert(i);
	}
	for(auto &t : mp) upd(t.second);
	for(int i = 1; i <= m; ++i){
		int a, b, c, d;
		cin >> a >> b >> c >> d;
		swap(a, b, c, d);
	}
	for(int i = 1; i <= n; ++i){
		cout << ans[i] << '\n';
	}
	return 0;
}

P3473 UCI-The Great Escape

第一次遇到这种 dp,自己想了一会完全没思路,还是我太菜了。
实际上难点在于怎么处理不能重复走的限制,姑且给这道题的 dp 方法叫螺旋线 dp。状态是:\(up_{xl, yl, xr, yr}\) 表示这一步向上走,并且一定沿着 \(y = yl\) 向上射入,且只能在左上角为 \((xl, yl)\), 右下角为 \((xr, yr)\) 的矩形内走螺旋线的方案数。其余方向同理。也就是说,对于向上的情况,\(y = yl\) 这条线一定会被取到, 而其余三条边界线不一定会被取到。
然后直接就能写出 \(O(1)\) 的转移了:\(up_{xl, yl, xr, yr} = [\text{路径上没有障碍}]right_{xl, yl + 1, xr, yr} + up_{xl + 1, yl, xr, yr}\)。其余同理。
只能说这个状态的设计非常巧妙,最后变成了一个区间 dp, 再滚一下就可以过了。

Code
#include <bits/stdc++.h>
using namespace std;
const int N = 1e2 + 5;
int mod, hor[N][N], col[N][N];
int add(int a, int b){ return (a + b >= mod ? a + b - mod : a + b); }
int f[2][N][N][N][4], n, m, sx, sy;
int main(){
	cin.tie(nullptr)->sync_with_stdio(0);
	cin >> n >> m >> mod >> sy >> sx;
	for(int i = 1; i <= n; ++i){
		for(int j = 1; j <= m; ++j){
			char c; cin >> c;
			int x = (c == '*');
			hor[i][j] = hor[i][j - 1] + x;
			col[i][j] = col[i - 1][j] + x;
		}
	}
	int nw = 0;
	for(int lenx = 1; lenx <= n; ++lenx){
		nw ^= 1;
		for(int leny = 1; leny <= m; ++leny){
			for(int x = n - lenx + 1; x >= 1; --x){
				for(int y = m - leny + 1; y >= 1; --y){
					int xr = x + lenx - 1, yr = y + leny - 1, d[4] = {x == sx && y == sy, x == sx && yr == sy, xr == sx && yr == sy, xr == sx && y == sy};
					if(xr < x || yr < y) continue;
					f[nw][leny][x][y][0] = add(f[nw ^ 1][leny][x + 1][y][0], (f[nw][leny - 1][x][y + 1][1] + d[0]) * (col[xr][y] - col[x - 1][y] == 0));
					f[nw][leny][x][y][1] = add(f[nw][leny - 1][x][y][1], (f[nw ^ 1][leny][x + 1][y][2] + d[1]) * (hor[x][yr] - hor[x][y - 1] == 0));
					f[nw][leny][x][y][2] = add(f[nw ^ 1][leny][x][y][2], (f[nw][leny - 1][x][y][3] + d[2]) * (col[xr][yr] - col[x - 1][yr] == 0));
					f[nw][leny][x][y][3] = add(f[nw][leny - 1][x][y + 1][3], (f[nw ^ 1][leny][x][y][0] + d[3]) * (hor[xr][yr] - hor[xr][y - 1] == 0));
				}
			}
		}
	}
	cout << f[nw][m][1][1][0];
	return 0;
}

P3474 KUP-Plot purchase

做一个悬线法的小结。
悬线法是一种用来求解关于极大子矩形问题的 trick。通常需要三个数组 \(l_{i,j}\)\(r_{i, j}\), \(up_{i, j}\)
模板:P1169 棋盘制作
这道题有点脑筋急转弯的感觉,一股 ad-hoc 的臭味。
如果 \(a_{i, j} > 2 \times k\) 那肯定不选,如果 \(k \le a_{i, j} \le 2 \times k\) 就直接输出。
那么可以选的就是 \(a_{i, j} < k\) 的。在这个情况下,如果有一个矩形的和 \(\ge k\),那么原题一定有答案。考虑构造解,如果原来矩形的和 $ \le 2 \times k$,直接输出。否则,考虑这个矩形的第一行,满足要求直接输出。不满足的话,如果这一行 $ > 2 \times k$ 那么就在这一行中从前往后加数,直到和 \(\ge k\),由于 \(a_{i, j} < k\) ,一定有这样的一个前缀。反之,就直接删掉这一行,然后继续判断矩形的和是否满足要求。
因此我们只用找到和最大的矩形即可,这个矩形显然是极大的,可以悬线法解决。

Code
#include <bits/stdc++.h>
typedef long long ll;
using namespace std;
const int N = 2e3 + 5;
int n, a[N][N], l[2][N], r[2][N], up[2][N], lx, ly, rx, ry;
ll k, sum[N][N];
ll qry(int lx, int ly, int rx, int ry){
    if(lx == 0) return 0;
    return sum[rx][ry] - sum[rx][ly - 1] - sum[lx - 1][ry] + sum[lx - 1][ly - 1];
}
void upd(int i, int j, int x, int y){
    if(qry(lx, ly, rx, ry) < qry(i, j, x, y)) lx = i, ly = j, rx = x, ry = y;
}
signed main(){
    ios::sync_with_stdio(0);
    cin.tie(0), cout.tie(0);
    cin >> k >> n;
    for(int i = 1; i <= n; ++i){
        for(int j = 1; j <= n; ++j){
            cin >> a[i][j];
            sum[i][j] = sum[i][j - 1] + sum[i - 1][j] - sum[i - 1][j - 1] + a[i][j];
            if(a[i][j] >= k && a[i][j] <= 2 * k)
                return cout << j << ' ' << i << ' ' << j << ' ' << i, 0;
        }
    }
    for(int i = 1; i <= n; ++i){
        l[i & 1][0] = 1e9;
        for(int j = 1; j <= n; ++j){
            l[i & 1][j] = 1e9;
            if(a[i][j] > 2 * k) continue;
            l[i & 1][j] = min(j, l[i & 1][j - 1]);
        }
        for(int j = n; j >= 1; --j){
            r[i & 1][j] = 0;
            if(a[i][j] > 2 * k) continue;
            r[i & 1][j] = max(j, r[i & 1][j + 1]);
        }
        for(int j = 1; j <= n; ++j){
            if(a[i][j] > 2 * k) continue;
            up[i & 1][j] = 1;
            if(i != 1 && a[i - 1][j] < k){
                l[i & 1][j] = max(l[(i - 1) & 1][j], l[i & 1][j]);
                r[i & 1][j] = min(r[(i - 1) & 1][j], r[i & 1][j]);
                up[i & 1][j] = up[(i - 1) & 1][j] + 1;
            }
            upd(i - up[i & 1][j] + 1, l[i & 1][j], i, r[i & 1][j]);
        }
    }
    ll sum = qry(lx, ly, rx, ry);
    if(sum < k) cout << "NIE";
    else{
        for(int i = lx; i <= rx; ++i){
            if(sum <= 2 * k){
                cout << ly << ' ' << i << ' ' << ry << ' ' << rx;
                return 0;
            }
            ll tmp = qry(i, ly, i, ry);
            if(tmp < k) sum -= tmp;
            else if(tmp >= k && tmp <= 2 * k) return cout << ly << ' ' << i << ' ' << ry << ' ' << i, 0;
            else{
                ll tot = 0;
                for(int j = ly; j <= ry; ++j){
                    tot += a[i][j];
                    if(tot >= k) return cout << ly << ' ' << i << ' ' << j << ' ' << i, 0;
                }
            }
        }
    }
    return 0;
}

P1477 假面舞会

之前漏掉的一道,还挺重要的。
经典模型:求有向图所有环长的 gcd。

我们对于每一条边 \(u \to v\),从 \(u\)\(v\) 连一条边权为 \(1\) 的边,从 \(v\)\(u\) 连一条边权为 \(−1\) 的边。然后每一次都从任意一个未被搜索过的点开始搜,并记录每一个点的距离 \(dis\),若走到一个已走过的点 \(i\),就取 \(|d−dis_i|\) 作为一个新找到的环的长度。

能这样做主要是因为 \(gcd(a, b) = gcd(a, |a - b|) = gcd(a, a + b)\),我们只需要找到其中任意一些环就可以求出所有环的 gcd。这个题解证明的还不错。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
const int N = 1e5 + 5;
vector<pair<int, int> > e[N];
int n, m, dis[N], ans, maxx, minx, vis[N];
void chmax(int &a, int b){ a = max(a, b); }
void chmin(int &a, int b){ a = min(a, b); }
void dfs(int u, int d){
    if(vis[u]){
        ans = __gcd(ans, abs(dis[u] - d));
        return ;
    }
    dis[u] = d, vis[u] = 1;
    chmax(maxx, dis[u]), chmin(minx, dis[u]);
    for(auto [v, w] : e[u])
        dfs(v, d + w);
}
int main(){
    cin.tie(0)->sync_with_stdio(0);
    cin >> n >> m;
    for(int i = 1; i <= m; ++i){
        int u, v;
        cin >> u >> v;
        e[u].emplace_back(v, 1);
        e[v].emplace_back(u, -1);
    }
    int res = 0;
    for(int i = 1; i <= n; ++i){
        if(!vis[i]){
            maxx = -1e9, minx = 1e9;
            dfs(i, 0);
            res += maxx - minx + 1;
        }
    }
    if(ans){
        if(ans < 3) cout << "-1 -1";
        else{
            for(int i = 3; i <= ans; ++i){
                if(ans % i == 0)
                    return cout << ans << ' ' << i, 0;
            }
        }
    }
    else {
        if(res < 3) cout << "-1 -1";
        else cout << res << " 3" ;
    }
    return 0;
}

P3488 LYZ-Ice Skates

Hall 定理:对于一个二分图,假设其左部点集 \(V\),右部点集 \(V'\),记 \(f(S) = \{v | u \in S \land v \in V' \land (u, v) \in E\}\)。称左边的点全部匹配上的一个方案叫做完美匹配,那么存在完美匹配的充要条件是 \(\forall S \subseteq V,|f(S)| \ge |S|\)
稍微证明一下:如果原图存在完美匹配,那么显然 \(|f(S)| \ge |S|\)。反过来的证明麻烦一些。考虑最大流最小割定理,那么最大匹配就是原图的最小割。如果存在 \(S\),使得 \(|f(S)| < |S|\),那么割掉 \(V - S\) 中所有点和原点的连接,并且割掉 \(f(S)\) 中的点和汇点的连接,这样割掉的边数是 \(|V| - |S| + |f(S)| < |V|\)。因此,原图的最大流 \(\le |V|\),也就不存在完美匹配了。
这道题可以直接套 Hall 定理,贪心做即可。

Code
#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
#define ls(p) p << 1
#define rs(p) p << 1 | 1
const int N = 2e5 + 5;
int n, m;
ll a[N], k, d, lsum[N << 2], rsum[N << 2], msum[N << 2], sum[N << 2];
void pushup(int p){
    lsum[p] = max(lsum[ls(p)], sum[ls(p)] + lsum[rs(p)]); 
    rsum[p] = max(rsum[rs(p)], sum[rs(p)] + rsum[ls(p)]);
    msum[p] = max({msum[ls(p)], msum[rs(p)], rsum[ls(p)] + lsum[rs(p)]});    
    sum[p] = sum[ls(p)] + sum[rs(p)];
}
void update(int p, int pl, int pr, int x, ll k){
    if(pl == pr) return lsum[p] = rsum[p] = sum[p] = msum[p] = k, void();
    int mid = (pl + pr) >> 1;
    if(x <= mid) update(ls(p), pl, mid, x, k);
    else update(rs(p), mid + 1, pr, x, k);
    pushup(p);
}
int main(){
    cin.tie(0)->sync_with_stdio(0);
    cin >> n >> m >> k >> d;
    for(int i = 1; i <= n; ++i) update(1, 1, n - d, i, -k);
    for(int i = 1; i <= m; ++i){
        ll r, x;
        cin >> r >> x;
        a[r] += x;
        update(1, 1, n - d, r, a[r] - k);
        cout << (msum[1] > d * k ? "NIE" : "TAK") << '\n';
    }
    return 0;
}
posted @ 2025-08-24 21:49  Hengsber  阅读(17)  评论(0)    收藏  举报