题目链接
https://codeforces.com/gym/101908
参考题解
B - Marbles
简要题意:
给定 n n n 个弹珠,第 i i i 个坐标为 ( r i , c i ) (r_i, c_i) (ri,ci)。 A B AB AB 两人轮流行动, A A A 先手,每次选择一个弹珠和一个正整数 u u u,将其移动到 ( r i − u , c i ) (r_i - u, c_i) (ri−u,ci) 或 ( r i , c i − u ) (r_i, c_i - u) (ri,ci−u) 或 ( r i − u , c i − u ) (r_i - u, c_i - u) (ri−u,ci−u) 的位置,最先将一个弹珠移动到 ( 0 , 0 ) (0, 0) (0,0) 的玩家获胜,问是否先手必胜。
解题思路:
若最开始就有弹珠在 r = c r = c r=c 位置,则先手必胜。若弹珠都在 ( 1 , 2 ) (1, 2) (1,2) 或 ( 2 , 1 ) (2, 1) (2,1) 的位置,则为必败态,因为下一次行动不管移动哪个弹珠,就会让对方直接能获胜。故将一个弹珠获胜条件转化为全部弹珠不能移动则落败, s g sg sg 打表即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
const int xp[] = {0, -1, -1};
const int yp[] = {-1, 0, -1};
int sg[maxn][maxn];
int n;
void init(int n){
sg[1][2] = sg[2][1] = 0;
for(int i = 1; i <= n; ++i){
for(int j = 1; j <= n; ++j){
if(i == j) continue;
set<int> vis;
int lim = max(i, j);
for(int k = 1; k <= lim; ++k){
for(int o = 0; o < 3; ++o){
int x = i + k * xp[o], y = j + k * yp[o];
if(x < 1 || y < 1 || x == y) continue;
vis.emplace(sg[x][y]);
}
}
for(int k = 0; ; ++k){
if(!vis.count(k)) { sg[i][j] = k; break; }
}
}
}
// for(int i = 0; i <= 10; ++i){
// for(int j = 0; j <= 10; ++j){
// cout << sg[i][j] << " ";
// }
// cout << endl;
// }
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
init(100);
cin >> n;
int ret = 0, flg = 0;
for(int i = 1; i <= n; ++i){
int r, c; cin >> r >> c;
ret ^= sg[r][c];
flg |= r == c;
}
if(flg) ret = 1;
cout << (ret ? "Y" : "N") << endl;
return 0;
}
C - Pizza Cutter
简要题意:
给定一张纸,有 n n n 刀从水平剪过、 m m m 刀从竖直方向剪,剪切痕迹不需要为直线、任意两刀最多一个交点,给定 n + m n + m n+m 次剪切在纸边缘的坐标,求最多将纸剪成几份。
解题思路:
对于水平的剪切 ( y i 1 , y i 2 ) (y_{i1}, y_{i2}) (yi1,yi2) 和 ( y j 1 , y j 2 ) (y_{j1}, y_{j2}) (yj1,yj2),两者能相交仅当 y i 1 < y j 1 ∧ y i 2 > y j 2 y_{i1} \lt y_{j1} \land y_{i2} \gt y_{j2} yi1<yj1∧yi2>yj2,竖直方向同理,每交一次则多一块。树状数组维护即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
pii a[maxn], b[maxn];
int xi[maxn], cntX, yi[maxn], cntY;
int n, m;
struct BIT{
int c[maxn], n;
void init(int nn){
n = nn;
for(int i = 1; i <= n; ++i) c[i] = 0;
}
#define lowb(x) ((x)&(-(x)))
void add(int x, int v){
while(x <= n) c[x] += v, x += lowb(x);
}
int sum(int x){
int ret = 0;
while(x > 0) ret += c[x], x -= lowb(x);
return ret;
}
} bit;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int X, Y; cin >> X >> Y;
cin >> n >> m;
for(int i = 1; i <= n; ++i){
cin >> a[i].first >> a[i].second;
yi[++cntY] = a[i].second;
}
for(int i = 1; i <= m; ++i){
cin >> b[i].first >> b[i].second;
xi[++cntX] = b[i].second;
}
sort(a + 1, a + 1 + n, greater<pii>());
sort(b + 1, b + 1 + m, greater<pii>());
sort(yi + 1, yi + 1 + cntY);
sort(xi + 1, xi + 1 + cntX);
cntY = unique(yi + 1, yi + 1 + cntY) - yi - 1;
cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
ll ret = 1;
bit.init(cntY);
for(int i = 1; i <= n; ++i){
int y = lower_bound(yi + 1, yi + 1 + cntY, a[i].second) - yi;
ret += bit.sum(y - 1) + 1;
bit.add(y, 1);
}
bit.init(cntX);
for(int i = 1; i <= m; ++i){
int x = lower_bound(xi + 1, xi + 1 + cntX, b[i].second) - xi;
ret += bit.sum(x - 1) + n + 1;
bit.add(x, 1);
}
cout << ret << endl;
return 0;
}
D - Unraveling Monty Hall
简要题意:
签到题。
解题思路:
签到题。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
int n; cin >> n;
int ret = 0;
for(int i = 1; i <= n; ++i){
int x; cin >> x;
if(x != 1) ++ret;
}
cout << ret << endl;
return 0;
}
E - Enigma
简要题意:
签到题。
解题思路:
暴力即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
char s[maxn], t[maxn];
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> s + 1 >> t + 1;
n = strlen(s + 1);
m = strlen(t + 1);
int ret = 0;
for(int i = m; i <= n; ++i){
int j = 1;
while(j <= m && s[i - m + j] != t[j]) ++j;
ret += j > m;
}
cout << ret << endl;
return 0;
}
F - Music Festival
简要题意:
音乐节有 n n n 个阶段,每阶段有若干艺术家在时间段 [ i j , f j ] [i_j, f_j] [ij,fj] 演出,并有数据 o j o_j oj 表示用户听过该艺术节的 o j o_j oj 首歌曲,现在要为用户规划观看哪些艺术家的演出,满足每个阶段至少观看一个节目、节目时间不冲突的情况下,使得 ∑ o j \sum o_j ∑oj 最大。
解题思路:
状压 d p dp dp,先将时间离散化,让 d p [ i ] [ j ] dp[i][j] dp[i][j] 表示 j j j 状态下,前 i i i 时间内的最大权值,转移详见代码。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e3 + 25;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct Node{
int l, r, w, c;
} a[maxn];
vector<Node> G[maxn << 1];
int xi[maxn << 1], dp[maxn << 1][maxn];
int n, m, cntX;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n;
for(int i = 0; i < n; ++i){
int ki; cin >> ki;
while(ki--){
int l, r, w; cin >> l >> r >> w;
xi[++cntX] = l, xi[++cntX] = r;
a[++m] = Node{l, r, w, i};
}
}
sort(xi + 1, xi + 1 + cntX);
cntX = unique(xi + 1, xi + 1 + cntX) - xi - 1;
for(int i = 1; i <= m; ++i){
a[i].l = lower_bound(xi + 1, xi + 1 + cntX, a[i].l) - xi;
a[i].r = lower_bound(xi + 1, xi + 1 + cntX, a[i].r) - xi;
G[a[i].r].pb(a[i]);
}
int lim = (1 << n) - 1;
for(int i = 0; i <= cntX; ++i){
for(int j = 0; j <= lim; ++j) dp[i][j] = -inf;
}
dp[0][0] = 0;
for(int i = 1; i <= cntX; ++i){
memcpy(dp[i], dp[i - 1], sizeof dp[i - 1]);
for(auto &e : G[i]){
for(int j = 0; j <= lim; ++j){
dp[i][j | (1 << e.c)] = max(dp[i][j | (1 << e.c)], dp[e.l][j] + e.w);
}
}
}
int ret = -1;
for(int i = 1; i <= cntX; ++i){
ret = max(ret, dp[i][lim]);
}
cout << ret << endl;
return 0;
}
G - Gasoline
简要题意:
有 P P P 个加油站,每个需要 D i D_i Di 的燃料, R R R 个炼油厂,每个有 E i E_i Ei 的库存,已知有 C C C 个供应路径,最短时间已知,假设卡车无限多、容量无限大。问能否供应全部加油站,若能,求出最短时间。
解题思路:
二分答案,使用所有小于等于时间 t t t 的边建图,网络流经典建模。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
struct Edge{
int u, v, w;
} ei[maxn];
int D[maxn], E[maxn];
int P, R, C;
struct Dinic{
struct Edge{
int v, rev, cap;
};
vector<Edge> G[maxn];
int dis[maxn], cur[maxn], n, sp, tp;
void init(int nn){
n = nn;
for(int i = 1; i <= n; ++i) G[i].clear();
}
void add(int u, int v, int cap, int rcap = 0){
G[u].pb(Edge{v, sz(G[v]), cap});
G[v].pb(Edge{u, sz(G[u]) - 1, rcap});
}
int bfs(){
queue<int> q;
for(int i = 1; i <= n; ++i) dis[i] = 0;
dis[sp] = 1, q.push(sp);
while(!q.empty()){
int u = q.front(); q.pop();
for(auto &e : G[u]){
if(e.cap && !dis[e.v]){
dis[e.v] = dis[u] + 1;
if(e.v == tp) return 1;
q.push(e.v);
}
}
}
return 0;
}
int dfs(int u, int flow){
if(u == tp || !flow) return flow;
int ret = 0, tmp;
for(int &i = cur[u]; i < sz(G[u]); ++i){
auto &e = G[u][i];
if(dis[e.v] == dis[u] + 1 && (tmp = dfs(e.v, min(e.cap, flow - ret)))){
e.cap -= tmp, G[e.v][e.rev].cap += tmp, ret += tmp;
if(ret == flow) return ret;
}
}
if(!ret) dis[u] = 0;
return ret;
}
int solve(int s, int t){
sp = s, tp = t;
int ret = 0;
while(bfs()){
for(int i = 1; i <= n; ++i) cur[i] = 0;
ret += dfs(sp, inf);
}
return ret;
}
} dn;
int judge(int mid){
int S = P + R + 1, T = S + 1;
dn.init(T);
for(int i = 1; i <= R; ++i){
dn.add(S, i, E[i]);
}
int sum = 0;
for(int i = 1; i <= P; ++i){
dn.add(R + i, T, D[i]);
sum += D[i];
}
for(int i = 1; i <= C; ++i){
if(ei[i].w <= mid) dn.add(ei[i].u, R + ei[i].v, inf);
}
return dn.solve(S, T) == sum;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> P >> R >> C;
for(int i = 1; i <= P; ++i) cin >> D[i];
for(int i = 1; i <= R; ++i) cin >> E[i];
for(int i = 1; i <= C; ++i) cin >> ei[i].v >> ei[i].u >> ei[i].w;
int l = 1, r = 1000000, mid, ret = -1;
while(l <= r){
mid = gmid;
if(judge(mid)) ret = mid, r = mid - 1;
else l = mid + 1;
}
cout << ret << endl;
return 0;
}
I - Switches
简要题意:
有 m m m 盏灯, n n n 个开关,每个开关控制若干盏灯,拨动开关改变灯的明暗情况。最初有若干灯亮着,问依次拨动第 1 1 1 到 n n n 个开关,如此循环,最少需要多少次拨动能让灯全暗,无解输出 − 1 -1 −1。
解题思路:
拨动 2 n 2n 2n 次则回到初始状态,故枚举 1 1 1 到 2 n − 1 2n - 1 2n−1 次拨动,判断是否全暗即可。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
bitset<maxn> a[maxn], b;
int n, m;
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m;
int L; cin >> L;
for(int i = 1; i <= L; ++i){
int x; cin >> x;
b[x] = 1;
}
for(int i = 1; i <= n; ++i){
int L; cin >> L;
while(L--){
int x; cin >> x;
a[i][x] = 1;
}
}
int ret = -1;
for(int i = 1; i < 2 * n; ++i){
int k = i <= n ? i : i - n;
b ^= a[k];
if(!b.count()) { ret = i; break; }
}
cout << ret << endl;
return 0;
}
J - Joining Capitals
简要题意:
给定 n n n 个城市,其中有 k k k 个首都,城市间边权为欧氏距离,问在 k k k 个首都只有一个直接邻接城市的情况下,将 k k k 个首都连通的最小边权和。
解题思路:
最小斯坦纳树,改变转移方程使得 k k k 个首都不成为根。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e2 + 5;
const int mod = 1e9 + 7;
// const int inf = 0x3f3f3f3f;
const double inf = 1e20;
#define eps 1e-7
pii a[maxn];
double wi[maxn][maxn], f[1 << 10][maxn];
int vis[maxn];
int n, k;
void spfa(double dis[]){
queue<int> q;
for(int i = 0; i < n; ++i) if(dis[i] < inf + eps) q.push(i), vis[i] = 1;
while(!q.empty()){
int u = q.front(); q.pop(); vis[u] = 0;
for(int v = 0; v < n; ++v){
if(u == v) continue;
if(dis[v] > dis[u] + wi[u][v] + eps){
dis[v] = dis[u] + wi[u][v];
if(!vis[v]) q.push(v), vis[v] = 1;
}
}
}
}
#define sqr(x) ((x)*(x))
double solve(){
for(int i = 0; i < n; ++i){
for(int j = i + 1; j < n; ++j){
wi[i][j] = wi[j][i] = sqrt(sqr(a[i].first - a[j].first) + sqr(a[i].second - a[j].second));
}
}
int lim = (1 << k) - 1;
for(int i = 0; i <= lim; ++i){
for(int j = 0; j < n; ++j) f[i][j] = inf;
}
for(int i = 0; i < k; ++i) f[1 << i][i] = 0;
for(int i = 1; i <= lim; ++i){
for(int j = k; j < n; ++j){
for(int s = (i - 1) & i; s; s = (s - 1) & i){
if(f[s][j] >= inf - eps || f[i ^ s][j] >= inf - eps) continue;
if(f[i][j] > f[s][j] + f[i ^ s][j] + eps) f[i][j] = f[s][j] + f[i ^ s][j];
}
}
spfa(f[i]);
}
double ret = inf;
for(int i = k; i < n; ++i) ret = min(ret, f[lim][i]);
return ret;
}
int main(){
// ios::sync_with_stdio(0); cin.tie(0);
scanf("%d%d", &n, &k);
for(int i = 0; i < n; ++i){
scanf("%d%d", &a[i].first, &a[i].second);
}
double ret = solve();
printf("%.5f\n", ret);
return 0;
}
L - Subway Lines
简要题意:
给定一棵 n n n 个结点的树, q q q 次询问,每次给出 a , b , c , d a, b, c, d a,b,c,d,问 ( a , b ) (a, b) (a,b) 和 ( c , d ) (c, d) (c,d) 的公共结点个数。
解题思路:
分类讨论,利用 L C A LCA LCA 和深度信息可解。或者树剖做法,将 ( a , b ) (a, b) (a,b) 间点权 + 1 +1 +1,再查询 ( c , d ) (c, d) (c,d) 的点权和。或者虚树做法,将关键点提取,在虚树上暴力求解(需要特别注意深度最小的公共点的点权)。这里提供虚树做法。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn], P[maxn], pi;
int dfn[maxn], fa[maxn][21], dep[maxn], stk[maxn], par[maxn], dt[maxn];
int n, q, tim, top, ans;
void dfs(int u, int f){
dep[u] = dep[f] + 1, dfn[u] = ++tim;
for(auto &v : G[u]){
if(v == f) continue;
fa[v][0] = u;
for(int i = 1; i <= 20; ++i) fa[v][i] = fa[fa[v][i - 1]][i - 1];
dfs(v, u);
}
}
int lca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
for(int i = 20; i >= 0; --i){
if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
}
if(u == v) return u;
for(int i = 20; i >= 0; --i){
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
}
return fa[u][0];
}
void link(int u, int v){
P[u].pb(v), par[v] = u;
}
int build(){
sort(pi.begin(), pi.end(), [](int x, int y){ return dfn[x] < dfn[y]; });
pi.erase(unique(pi.begin(), pi.end()), pi.end());
stk[top = 1] = pi[0], pi.erase(pi.begin());
for(auto &v : pi){
int lc = lca(stk[top], v);
while(dep[lc] < dep[stk[top - 1]]) link(stk[top - 1], stk[top]), --top;
if(lc != stk[top]){
link(lc, stk[top]), --top;
if(lc != stk[top]) stk[++top] = lc;
}
stk[++top] = v;
}
while(--top) link(stk[top], stk[top + 1]);
return stk[1];
}
void cal(int u){
for(auto &v : P[u]){
cal(v);
dt[u] += dt[v];
}
ans += dt[u] == 2 ? dep[u] - dep[par[u]] : 0;
}
void clear(int u){
for(auto &v : P[u]){
clear(v);
}
dt[u] = 0, P[u].clear();
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for(int i = 1; i < n; ++i){
int u, v; cin >> u >> v;
G[u].pb(v), G[v].pb(u);
}
dfs(1, 0);
while(q--){
int u, v, x, y; cin >> u >> v >> x >> y;
pi.pb(1), pi.pb(u), pi.pb(v), pi.pb(x), pi.pb(y);
auto add = [](int u, int v){
int lc = lca(u, v); pi.pb(lc);
if(lc != 1) pi.pb(fa[lc][0]);
};
add(u, v), add(u, x), add(u, y);
add(v, x), add(v, y), add(x, y);
int rt = build();
int lc1 = lca(u, v), lc2 = lca(x, y);
// cout << lc1 << " xxx " << lc2 << endl;
++dt[u], ++dt[v], --dt[lc1], --dt[par[lc1]];
++dt[x], ++dt[y], --dt[lc2], --dt[par[lc2]];
ans = 0, cal(rt), clear(rt), pi.clear();
cout << ans << "\n";
}
return 0;
}
另一种构建虚树的方法,只加入关键点。最终交出来的链在虚树上的端点为深度最大的两个点,故不需要显式建树,顺便还能求出交集的两个端点。
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 1e5 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
vector<int> G[maxn];
int dfn[maxn], fa[maxn][21], dep[maxn];
int n, q, tim;
void dfs(int u, int f){
dep[u] = dep[f] + 1, dfn[u] = ++tim;
for(auto &v : G[u]){
if(v == f) continue;
fa[v][0] = u;
for(int i = 1; i <= 20; ++i) fa[v][i] = fa[fa[v][i - 1]][i - 1];
dfs(v, u);
}
}
int lca(int u, int v){
if(dep[u] < dep[v]) swap(u, v);
for(int i = 20; i >= 0; --i){
if(dep[fa[u][i]] >= dep[v]) u = fa[u][i];
}
if(u == v) return u;
for(int i = 20; i >= 0; --i){
if(fa[u][i] != fa[v][i]) u = fa[u][i], v = fa[v][i];
}
return fa[u][0];
}
int getDis(int u, int v){
return dep[u] + dep[v] - 2 * dep[lca(u, v)];
}
int onPath(int x, int u, int v){
return getDis(u, v) == getDis(u, x) + getDis(v, x);
}
struct Chain{
int u, v;
friend Chain operator & (const Chain &a, const Chain &b){
vector<int> vc = {a.u, a.v, b.u, b.v};
auto cmp = [](int x, int y){ return dfn[x] < dfn[y]; };
sort(vc.begin(), vc.end(), cmp);
vector<int> pi = vc;
for(int i = 1; i < sz(vc); ++i) pi.pb(lca(vc[i], vc[i - 1]));
sort(pi.begin(), pi.end(), cmp);
pi.erase(unique(pi.begin(), pi.end()), pi.end());
int x = 0, y = 0;
for(auto &v : pi){
if(!onPath(v, a.u, a.v) || !onPath(v, b.u, b.v)) continue;
if(dep[v] > dep[x]) y = x, x = v;
else if(dep[v] > dep[y]) y = v;
}
if(x && !y) y = x;
return Chain{x, y};
}
};
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> q;
for(int i = 1; i < n; ++i){
int u, v; cin >> u >> v;
G[u].pb(v), G[v].pb(u);
}
dfs(1, 0);
while(q--){
int u, v, x, y; cin >> u >> v >> x >> y;
Chain ret = Chain{u, v} & Chain{x, y};
if(!ret.u) cout << "0\n";
else cout << getDis(ret.u, ret.v) + 1 << "\n";
}
return 0;
}
M - Modifying SAT
简要题意:
给定 m m m 个二元变量,再给出 n n n 个形如 X 1 ∧ X 2 ∧ ⋯ ∧ X X_1 \land X_2 \land\cdots\land X X1∧X2∧⋯∧X 的合取式,其中每个子句 X i X_i Xi 有至多 3 3 3 个变量的,形如 x 1 ∨ x 2 ∨ ¬ x 3 x_1 \lor x_2 \lor \lnot x_3 x1∨x2∨¬x3 的析取式,每个子句 X i X_i Xi 中需要有一个或三个子项取值为真,问是否有解,有解则求字典序最大的解。
解题思路:
转化后即给定 n n n 个方程,每个方程形如 x 1 ⊕ x 2 ⊕ x 3 = 0 / 1 x_1 \oplus x_2 \oplus x_3 = 0/1 x1⊕x2⊕x3=0/1,异或高斯消元求解。若有解,由于字典序 T > F T \gt F T>F,令所有自由元取值为 T T T 得到一组特解,消元从第 m m m 列自右向左进行,可让主元尽可能靠后、自由元尽可能靠前,如此字典序最大。
参考代码:
#include<bits/stdc++.h>
using namespace std;
#define pb emplace_back
#define sz(a) ((int)a.size())
#define lson (rt << 1)
#define rson (rt << 1 | 1)
#define gmid (l + r >> 1)
typedef long long ll;
typedef pair<int, int> pii;
const int maxn = 2e3 + 5;
const int mod = 1e9 + 7;
const int inf = 0x3f3f3f3f;
bitset<maxn> A[maxn];
int vis[maxn], n, m;
struct Gauss{
int piv[maxn], fre[maxn], ans[maxn], tot;
int solve(bitset<maxn> A[], int n, int m){
int r = 0, c = 0; tot = 0;
for( ;c < m && r < n; ++c, ++r){
int mx = r;
for(int i = r; i < n; ++i){
if(A[i][c]) { mx = i; break; }
}
if(!A[mx][c]) { --r; fre[tot++] = c; continue; }
piv[r] = c;
swap(A[r], A[mx]);
for(int i = 0; i < n; ++i){
if(i == r || !A[i][c]) continue;
A[i] ^= A[r];
}
}
for(int i = r; i < n; ++i) if(A[i][m]) return -1;
while(c < m) fre[tot++] = c++;
for(int i = 0; i < tot; ++i) ans[fre[i]] = 1;
for(int i = 0; i < r; ++i){
for(int j = 0; j < tot; ++j) A[i][m] = A[i][m] ^ A[i][fre[j]];
ans[piv[i]] = A[i][m];
}
return r;
}
} gs;
vector<pii> read(){
string s; getline(cin, s);
vector<pii> vc;
int pos = s.find('x');
while(pos != string::npos){
int flg = pos >= 2 && s[pos - 2] == 't';
int val = 0; ++pos;
while(isdigit(s[pos])) val = val * 10 + (s[pos] - '0'), ++pos;
vc.pb(pii{flg, val});
pos = s.find('x', pos);
}
return vc;
}
int main(){
ios::sync_with_stdio(0); cin.tie(0);
cin >> n >> m; cin.ignore();
for(int i = 0; i < n; ++i){
auto vc = read();
A[i][m] = 1;
for(auto &e : vc){
A[i][m - e.second] = A[i][m - e.second] ^ 1;
A[i][m] = A[i][m] ^ e.first;
vis[m - e.second] = 1;
// cout << e.first << " " << e.second << endl;
}
}
int rk = gs.solve(A, n, m);
if(rk == -1) cout << "impossible" << endl;
else{
for(int i = m - 1; i >= 0; --i){
if(!vis[i]) cout << "T";
else cout << (gs.ans[i] ? "T" : "F");
}
cout << endl;
}
return 0;
}