「codeforces - 1592F2」Alice and Recoloring 2:妙妙题,显然只会操作 和 ,分别记作操作 1 和操作 2。我们希望单点操作,所以考虑差分(trick),只是这个题的差分非常高妙。如果你直接对前缀做二维差分会发现操作 1 要修改四个点,操作 2 修改一个点,而操作 1 花费 ,2 花费 ,所以每个点可能被操作最多两次(考虑一种情况,即 ,, 是黑点, 是白点)。xf 教我了一种高庙的差分,具体是 ,这样差分操作 2 就修改四个点,操作 1 修改一个点,这样一个点就最多修改一次了,建出二分图即可。
最后的做法是,假设我全部用操作 1,然后反悔看最多能用多少次操作 2(因为会用在 上用操作 2 的情况是 ,, 都是黑点)。
最后 是需要特判的。
各种意义上我不会的题,膜拜 xf。
char s[600];
int n, m, a[600][600];
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> m;
for (int i=1; i<=n; ++i) {
cin >> s;
for (int j=1; j<=m; ++j) {
a[i][j] = s[j-1] == 'B';
}
}
for (int i=1; i<=n; ++i) {
for (int j=1; j<=m; ++j) {
a[i][j] ^= a[i+1][j]^a[i][j+1]^a[i+1][j+1];
}
}
mf_graph<int> g(n+m+2);
const int src = n+m+1, ter = n+m+2;
for (int i=1; i<n; ++i) {
for (int j=1; j<m; ++j) {
if (a[i][j] && a[n][j] && a[i][m]) {
g.add_edge(i, j+n, 1);
}
}
}
for (int i=1; i<=n; ++i) g.add_edge(src, i, 1);
for (int i=1; i<=m; ++i) g.add_edge(i+n, ter, 1);
int ret = -g.flow(src, ter);
a[n][m] ^= (-ret)&1;
for (int i=1; i<=n; ++i) {
for (int j=1; j<=m; ++j) ret += a[i][j];
}
cout << ret << "\n";
}
「codeforces - 1146G」Zoning Restrictions:还行的题。想到把每个点拆成 个点,如果你定义状态 表示第 个点的高度为 会发现并不优秀,改成高度大于等于 会好一点。关于这题怎么想到 min-cut 的,可以这样考虑(which is also a common trick):全局最理想的答案是 ,然而不一定合法,我们以最小的代价去把这个答案调整到合法,得到的一定是最优的答案,因此是 min-cut。然而这道题我们不会这样去想,这只是一些题的思路。对于这个题我们的想法是把所有的贡献取负,这样就成了最小值()
这里涉及另一个思维上的 trick:最小割的源汇有什么意义?划分出的两个点集有什么意义?在这一题中我们不妨将点集 中的事件称为「成立」, 中的事件称为「不成立」,这样就赋予了最小割意义。
这里给出构造网络的方案:,,。
考察意义:割掉第一类边的意义即令房子 的高度为 ,这对答案的贡献为 ,我们对答案取了负,所以贡献为 ,如果第二类边有流量则意味着付罚金,因为一个区间只需要付一次罚金,所以我们不能直接割掉第二类边,而是通过第二类边使得割掉第三类边有意义(断掉连通性)。
当然流量不可能是负的,所以选择给 加上 ,因为 幢房子代表的每一条链必然割且仅割一条一类边,所以最后给答案加上 即可。
const int inf = 0x3f3f3f3f;
int n, h, m, id[60][60], qwq;
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> h >> m;
for (int i=1; i<=n; ++i) {
for (int j=0; j<=h+1; ++j) id[i][j] = ++qwq;
}
mf_graph<int> g(qwq+m+2);
const int src = qwq+m+1, ter = qwq+m+2;
for (int i=1; i<=n; ++i) {
g.add_edge(src, id[i][0], inf);
}
for (int i=1; i<=n; ++i) {
for (int j=0; j<=h; ++j) g.add_edge(id[i][j], id[i][j+1], h*h-j*j);
}
for (int i=1,l,r,x,c; i<=m; ++i) {
cin >> l >> r >> x >> c;
g.add_edge(i+qwq, ter, c);
for (int j=l; j<=r; ++j) {
g.add_edge(id[j][x+1], i+qwq, inf);
}
}
cout << h*h*n-g.flow(src, ter) << "\n";
}
「codeforces - 1061E」Politics:第一步是想到把 tree 1 放左部 tree 2 放右部。然后进入这个题的 key step:将对于子树的限制(被钦定的结点个数)转化为对一个结点的限制。这里大部分的做法是:对于一个有限制的结点 ,将 减去 ,从而我们的工作就变成了在「所有 的子树中的有限制的结点的上方中钦定一些结点」。于是给出这题的建图(费用流):,,,其中 v_1 v_2 是两棵树的点集,up_i 是编号 在 tree 1 中对应的结点的最近有限制祖先,up'_i 同理。
理解一下,一、二类边都对应每个结点的限制,第三类边就是反过来考虑(我们之前是对祖先 考虑,要在子树中所有有限制的结点的上面钦定结点),现在我们对一个点 考虑它对最近有限制祖先的贡献。如果钦定了编号 ,则会对答案造成 的贡献,占据 up_i 和 up'_i 的空余可钦定数。
mcf_graph<int, int> g;
int src, ter, qwq, qaq;
int n, rtx, rty, q1, q2, a[600], bstx[600], bsty[600], upx[600], upy[600], parx[600], pary[600];
bsi<int> adjx[600], adjy[600];
int dfsx(int x, int pa, int t) {
parx[x] = pa;
if (bstx[x]) {
t = x;
}
upx[x] = t;
int sum = 0;
for (auto y : adjx[x]) {
if (y != pa) {
sum += dfsx(y, x, t);
}
}
if (bstx[x]) {
if (bstx[x] < sum) fucked_up();
qwq += bstx[x]-sum;
g.add_edge(src, x, bstx[x]-sum, 0);
return bstx[x];
}
return sum;
}
int dfsy(int x, int pa, int t) {
pary[x] = pa;
if (bsty[x]) {
t = x;
}
upy[x] = t;
int sum = 0;
for (auto y : adjy[x]) {
if (y != pa) {
sum += dfsy(y, x, t);
}
}
if (bsty[x]) {
if (bsty[x] < sum) fucked_up();
qaq += bsty[x]-sum;
g.add_edge(x+n, ter, bsty[x]-sum, 0);
return bsty[x];
}
return sum;
}
int getx(int x, int pa) {
int res = 0;
for (auto y : adjx[x]) {
if (y != pa) res += getx(y, x);
}
if (bstx[x]) {
return bstx[x];
}
return res;
}
int gety(int x, int pa) {
int res = 0;
for (auto y : adjy[x]) {
if (y != pa) res += gety(y, x);
}
if (bsty[x]) {
return bsty[x];
}
return res;
}
signed main() {
ios::sync_with_stdio(0);
cin.tie(0);
cin >> n >> rtx >> rty;
for (int i=1; i<=n; ++i) {
cin >> a[i];
}
for (int i=1,x,y; i<n; ++i) {
cin >> x >> y;
adjx[x] += y, adjx[y] += x;
}
for (int i=1,x,y; i<n; ++i) {
cin >> x >> y;
adjy[x] += y, adjy[y] += x;
}
cin >> q1;
for (int i=0,x; i<q1; ++i) {
cin >> x >> bstx[x];
}
cin >> q2;
for (int i=0,x; i<q2; ++i) {
cin >> x >> bsty[x];
}
g = mcf_graph<int, int>(n*2+2);
src = n*2+1, ter = n*2+2;
dfsx(rtx, 0, 0), dfsy(rty, 0, 0);
if (qwq != qaq) fucked_up();
for (int i=1; i<=n; ++i) {
if (upx[i] && upy[i]) {
g.add_edge(upx[i], upy[i]+n, 1, -a[i]);
}
}
auto [X, Y] = g.flow(src, ter);
if (X != qwq) fucked_up();
cout << -Y << "\n";
}