上周的后面几天考试作业什么的导致没怎么训练
这周要回归,火力全开。
最近两周的训练任务就是队内训练赛和三个kuangbin专题
我现在的训练分两条线,一条是队内的比赛和任务,一条是自己学额外的知识点
队内的任务优先
周二 5.25(匹配)
昨天去打班级篮球赛去了,所以晚上没训练
队里布置了kuangbin的匹配问题,搞起
A - Fire Net(建图 + 最大匹配)
选最多的点,最大匹配,所以匹配对应点,所以一个点就是一条边
常规的思路是(x, y)是x向y连一条边,这样如果这个点选了,那么x和y都不能被其他边选,也就是不能被其他点选,是符合题意的
但是这道题有个x的存在,导致了一行可以选多个点
那很简单,比如中间有一个x,那就要区分一下
因此可以分配编号,当遇到x的时候就编号++来区分,每次选一个点占领的是对应编号的点
然后跑二分图最大匹配就好了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 10;
int x[N][N], y[N][N], vis[N * N], link[N * N], n;
vector<int> g[N * N];
char s[N][N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
return true;
}
}
return false;
}
int main()
{
while(scanf("%d", &n) && n)
{
memset(link, 0, sizeof(link));
_for(i, 1, n) scanf("%s", s[i] + 1);
int idx = 0;
_for(i, 1, n)
{
idx++;
_for(j, 1, n)
{
if(s[i][j] == 'X') idx++;
else x[i][j] = idx;
}
}
int idy = 0;
_for(j, 1, n)
{
idy++;
_for(i, 1, n)
{
if(s[i][j] == 'X') idy++;
else y[i][j] = idy;
}
}
_for(i, 1, idx) g[i].clear();
_for(i, 1, n)
_for(j, 1, n)
if(s[i][j] != 'X')
g[x[i][j]].push_back(y[i][j]);
int ans = 0;
_for(i, 1, idx)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
}
return 0;
}
B - The Accomodation of Students(二分图判定 + 最大匹配)
开局不错,顺利连A两题
一看是一个很裸的最大匹配
但是要线判断是否为二分图
这个用染色法就好了,一直染色,如果是二分图那么这么染色下去是不会冲突的
如果冲突了那就不是二分图了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 210;
int vis[N], link[N], color[N], n, m;
vector<int> g[N];
bool Dfs(int u)
{
for(int v: g[u])
{
if(vis[v])
{
if(color[u] == color[v]) return false;
continue;
}
vis[v] = 1;
color[v] = color[u] ^ 1;
if(!Dfs(v)) return false;
}
return true;
}
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
return true;
}
}
return false;
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
_for(i, 1, n) g[i].clear();
memset(color, 0, sizeof(color));
memset(link, 0, sizeof(link));
memset(vis, 0, sizeof(vis));
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
bool ok = 1;
_for(i, 1, n)
{
if(!vis[i] && !Dfs(i))
{
puts("No");
ok = 0;
break;
}
}
if(!ok) continue;
int ans = 0;
_for(i, 1, n)
if(color[i])
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
printf("%d\n", ans);
}
return 0;
}
C - Courses(二分图最大匹配)
其实非常裸
但是我被惯性思维框住了,我一直觉得二分图的节点是同一类的东西
我一直再考虑把所有课程看成点另一个看成边,或者所有学生看成点,课程看成边
实际上直接二分图左边是课程,右边是学生,直接跑二分图最大匹配就好了
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 310;
int vis[N], link[N], n, p;
vector<int> g[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
return true;
}
}
return false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(link, 0, sizeof(link));
scanf("%d%d", &p, &n);
_for(i, 1, p)
{
g[i].clear();
int t; scanf("%d", &t);
while(t--)
{
int x; scanf("%d", &x);
g[i].push_back(x);
}
}
int ans = 0;
_for(i, 1, p)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
puts(ans == p ? "YES" : "NO");
}
return 0;
}
周三 5.26
Gym-103049A(完全背包 + 贪心)
训练赛补题最后一道
天啊这个官方题解真的把这个题复杂了N多倍
想复杂了很多,把我带偏了……
就是一个完全背包的基础上拓展
给出体积为1到n的物品,价值为a[1] 到a[n]
1.当体积<=n的时候价值是固定的,大于n的时候可以由多个物品组成。
2.询问给出一个体积,求价值最小。这题的询问非常大,大到1e9
首先第一个<=n的时候价值是固定的
这个很好解决,直接初始化好1到n的dp值,然后转移就从n + 1开始就好
第二个问题是这个询问的体积非常大
如果比较小那么这道题就做完了
这么办呢,我自己想的时候就卡在这
这里利用到了部分的贪心
一开始写背包的时候,有一个错误的贪心,就是每次选性价比最高的物品
这个贪心只有在总体积刚好是此物品体积的倍数的时候才成立,而这道题就利用了这个性质
我们可以部分的贪,也就是说把询问的总体积分为两部分
一部分是上面说的贪心,一部分是完全背包
也就是说利用这个贪心使得总体积减少到预处理的dp的范围之内
就这样就ok了,官方题解还说了什么鸽巢定理什么东西的……
所以直接预处理出1e5(其实远远不用这么大)前的dp值,然后对于每个询问,先用那个贪心来缩小体积,然后再用上dp值就好了
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 110;
const int M = 1e5 + 10;
ll a[N], dp[M];
int n, q, m;
int main()
{
scanf("%d%d", &n, &q);
_for(i, 1, n) scanf("%lld", &a[i]);
double mi = 1e18;
_for(i, 1, n)
if((double)a[i] / i < mi)
{
mi = (double)a[i] / i;
m = i;
}
memset(dp, 0x3f, sizeof(dp));
_for(i, 1, n) dp[i] = a[i];
_for(i, n + 1, 1e5)
_for(j, 1, n)
dp[i] = min(dp[i], dp[i - j] + a[j]);
while(q--)
{
int x; scanf("%d", &x);
if(x <= 1e5) printf("%lld\n", dp[x]);
else
{
int k = (x - 1e5 + m - 1) / m;
printf("%lld\n", dp[x - k * m] + k * a[m]);
}
}
return 0;
}
HDU 1281
这题我弄错了一直以为暴力删边会超时
实际证明我想多了,是不会超时的
直接暴力删边就好
#include<bits/stdc++.h>
#define REP(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int vis[N], link[N], t[N], n, m, k, a, b;
vector<int> g[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v] || a == u && b == v) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
return true;
}
}
return false;
}
int main()
{
int kase = 0;
while(~scanf("%d%d%d", &n, &m, &k))
{
memset(link, 0, sizeof(link));
_for(i, 1, n) g[i].clear();
a = b = 0;
while(k--)
{
int x, y;
scanf("%d%d", &x, &y);
g[x].push_back(y);
}
int ans = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) ans++;
}
int ans2 = 0;
_for(i, 1, m) t[i] = link[i];
_for(v, 1, m)
if(link[v])
{
a = link[v], b = v;
int res = 0;
memset(link, 0, sizeof(link));
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) res++;
}
if(res != ans) ans2++;
_for(i, 1, m) link[i] = t[i];
}
printf("Board %d have %d important blanks for %d chessmen.\n", ++kase, ans2, ans);
}
return 0;
}
hdu 2819
这道题搞了我巨久
首先可以逆推,从对角线开始变换,会发现一个条件
但是我把这个条件描述为了每一行和每一列都至少有一个数
但其实这是必要条件而不是充分条件,这并不能保证最后可以为对角线
正确的应该是,如果每一个1占领一行和一列的话,可以放n个
这就联想到二分图匹配最大匹配为n了,因此可以用最大匹配来判断是否可以为对角线
这是这道题的第一部分
然后就是接下来怎么去构造的问题了
我自己的想法很简单,直接暴力一行一行的放,对于每一行,直接找到一个1然后通过交换达到对角线的位置
但是我又掉了一个坑,就是那些多余的点,也就是没有被匹配的点是不能算入的,不然会影响结果,这些点不能放到对角线哪里,只有匹配的点才能转移
你用那些多余点放到对角线的时候,就默认它是匹配的点了,但实际上它不是匹配的点
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int vis[N], link[N], a[N][N], n;
vector<int> g[N];
void Swap(int x, int y)
{
_for(i, 1, n)
swap(a[i][x], a[i][y]);
}
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
return true;
}
}
return false;
}
int main()
{
while(~scanf("%d", &n))
{
memset(link, 0, sizeof(link));
_for(i, 1, n) g[i].clear();
_for(i, 1, n)
_for(j, 1, n)
{
scanf("%d", &a[i][j]);
if(a[i][j]) g[i].push_back(j);
}
int res = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) res++;
}
if(res < n)
{
puts("-1");
continue;
}
memset(a, 0, sizeof(a));
_for(i, 1, n) a[link[i]][i] = 1;
vector<pair<int, int> > ans;
_for(i, 1, n)
{
int p;
_for(j, i, n)
if(a[i][j])
{
p = j;
break;
}
if(p == i) continue;
Swap(i, p);
ans.push_back(make_pair(i, p));
}
printf("%d\n", ans.size());
for(auto x: ans) printf("C %d %d\n", x.first, x.second);
}
return 0;
}
当然有更简洁的方法,就是直接在匹配数组上操作,而不是在输入的矩阵上操作,这样也避免了操作不匹配的点
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int vis[N], link[N], choose[N], n;
vector<int> g[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!link[v] || dfs(link[v]))
{
link[v] = u;
choose[u] = v;
return true;
}
}
return false;
}
int main()
{
while(~scanf("%d", &n))
{
memset(link, 0, sizeof(link));
memset(choose, 0, sizeof(choose));
_for(i, 1, n) g[i].clear();
_for(i, 1, n)
_for(j, 1, n)
{
int x; scanf("%d", &x);
if(x) g[i].push_back(j);
}
int res = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(dfs(i)) res++;
}
if(res < n)
{
puts("-1");
continue;
}
vector<pair<int, int> > ans;
_for(i, 1, n)
if(link[i] != i)
{
ans.push_back(make_pair(i, link[i]));
link[choose[i]] = link[i];
choose[link[i]] = choose[i];
link[i] = i; choose[i] = i;
}
printf("%d\n", ans.size());
for(auto x: ans) printf("R %d %d\n", x.first, x.second);
}
return 0;
}
周四 5.27(匹配)
HDU 2389(HK算法)
写了匈牙利T了
匈牙利时间复杂度是O(nm)
而HK算法复杂度是O(sqrt(n) * m)
自己还没有完全理解,理解了百分之80吧
不过也只是模板,如果要用直接抄纸质模板
我的写法是左右编号存在了一起
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 3000 + 10;
struct node
{
int x, y;
}a[N], b[N];
int v[N], dep[N << 1], match[N << 1], n, m, t; //我这里是左右两部分点都用一个数组,这样写简洁一些
vector<int> g[N];
bool bfs()
{
bool res = false;
memset(dep, 0, sizeof(dep));
queue<int> q;
_for(i, 1, n)
if(!match[i]) //把未匹配的点加入
q.push(i);
while(!q.empty())
{
int u = q.front(); q.pop();
for(int v: g[u])
{
if(dep[v]) continue;
dep[v] = dep[u] + 1;
if(!match[v]) res = true; //只要找到增广路就返回true
else
{
dep[match[v]] = dep[v] + 1;
q.push(match[v]);
}
}
}
return res;
}
bool dfs(int u)
{
for(int v: g[u])
{
if(dep[v] != dep[u] + 1) continue; //按照增广路dfs
dep[v] = 0;
if(!match[v] || dfs(match[v]))
{
match[v] = u; match[u] = v;
return true;
}
}
return false;
}
bool check(int i, int j)
{
return (a[i].x - b[j].x) * (a[i].x - b[j].x) + (a[i].y - b[j].y) * (a[i].y - b[j].y) <= t * t * v[i] * v[i];
}
int main()
{
int T, kase = 0;
scanf("%d", &T);
while(T--)
{
scanf("%d%d", &t, &n);
_for(i, 1, n) scanf("%d%d%d", &a[i].x, &a[i].y, &v[i]);
scanf("%d", &m);
_for(i, 1, m) scanf("%d%d", &b[i].x, &b[i].y);
_for(i, 1, n) g[i].clear();
_for(i, 1, n)
_for(j, 1, m)
if(check(i, j))
g[i].push_back(j + n); //右编号+n
int ans = 0;
memset(match, 0, sizeof(match));
while(bfs()) //预处理出最短增广路
{
_for(i, 1, n)
if(!match[i] && dfs(i)) //未匹配的去寻找增广路
ans++;
}
printf("Scenario #%d:\n%d\n\n", ++kase, ans);
}
return 0;
}
hdu 4185(建图)
这题我在建图这里卡了一下
之前是一个点看作一条边
这道题不一样,是一个点就是一个点,然后题目说的1x2的矩形看作一条边
边和点的定义不同
其实有提示了,题目问最多有多少个矩形
联系最大匹配,匹配的是边
所以矩形就是边
做的时候都加倍,点加倍,边加倍,然后答案除以2就好
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;
const int N = 610;
int match[N * N], vis[N * N], id[N][N], n, cnt;
vector<int> g[N];
char s[N][N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
int T, kase = 0;
scanf("%d", &T);
while(T--)
{
cnt = 0;
memset(match, 0, sizeof(match));
memset(s, 0, sizeof(s));
scanf("%d", &n);
_for(i, 1, n)
{
scanf("%s", s[i] + 1);
_for(j, 1, n)
if(s[i][j] == '#')
id[i][j] = ++cnt;
}
_for(i, 1, cnt) g[i].clear();
_for(i, 1, n)
_for(j, 1, n)
{
if(s[i][j] == '#' && s[i][j + 1] == '#')
{
g[id[i][j]].push_back(id[i][j + 1]);
g[id[i][j + 1]].push_back(id[i][j]);
}
if(s[i][j] == '#' && s[i + 1][j] == '#')
{
g[id[i][j]].push_back(id[i + 1][j]);
g[id[i + 1][j]].push_back(id[i][j]);
}
}
int ans = 0;
_for(i, 1, cnt)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("Case %d: %d\n", ++kase, ans / 2);
}
return 0;
}
poj 3020(最小路径覆盖)
这道题和上一道题非常像,只是变成了覆盖所有最少需要多少矩形
从最小可以想到最小点覆盖,我按照这个思路发现很复杂
其实最小也有最小路径覆盖,也刚好是这道题,最少的矩形覆盖
最小路径覆盖 = 总节点数 - 最大匹配
所以求出最小路径覆盖,最后把答案除以2就行了,因为这个图是翻倍的
#include <cstdio>
#include <vector>
#include <cstring>
#define rep(i, a, b) for(int i = (a) ; i < (b); i++)
#define _for(i, a, b) for(int i = (a) ; i <= (b); i++)
using namespace std;
const int N = 400;
int match[N], vis[N], id[N][N], n, m, cnt;
vector<int> g[N];
char s[N][N];
bool dfs(int u)
{
rep(i, 0, g[u].size())
{
int v = g[u][i];
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
int T, kase = 0;
scanf("%d", &T);
while(T--)
{
cnt = 0;
memset(match, 0, sizeof(match));
memset(s, 0, sizeof(s));
scanf("%d%d", &n, &m);
_for(i, 1, n)
{
scanf("%s", s[i] + 1);
_for(j, 1, m)
if(s[i][j] == '*')
id[i][j] = ++cnt;
}
_for(i, 1, cnt) g[i].clear();
_for(i, 1, n)
_for(j, 1, m)
{
if(s[i][j] == '*' && s[i][j + 1] == '*')
{
g[id[i][j]].push_back(id[i][j + 1]);
g[id[i][j + 1]].push_back(id[i][j]);
}
if(s[i][j] == '*' && s[i + 1][j] == '*')
{
g[id[i][j]].push_back(id[i + 1][j]);
g[id[i + 1][j]].push_back(id[i][j]);
}
}
int ans = 0;
_for(i, 1, cnt)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("%d\n", (cnt * 2 - ans) / 2);
}
return 0;
}
周五 5.28 (匹配)
I - Strategic Game(最小点覆盖)
点看作点,边看作边,建立二分图
最小点覆盖 = 最大匹配就好
点加倍,边加倍
结果除以2
#include<bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2000;
int match[N], vis[N], n;
vector<int> g[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
while(~scanf("%d", &n))
{
_for(i, 1, n) g[i].clear();
memset(match, 0, sizeof(match));
_for(i, 1, n)
{
int u, m;
scanf("%d:(%d)", &u, &m);
u++;
while(m--)
{
int v; scanf("%d", &v);
v++;
g[u].push_back(v);
g[v].push_back(u);
}
}
int ans = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("%d\n", ans / 2);
}
return 0;
}
poj 1975(传递闭包)
刷到一道题有关于传递闭包的,搞一波
其实就是用有向图中用floyed判断出从这个点是否可以到达那个点
这道题就求个传递闭包
然后看有多少比其重和比其轻就好了
#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int g[N][N], n, m;
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(g, 0, sizeof(g));
scanf("%d%d", &n, &m);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = 1;
}
_for(k, 1, n)
_for(i, 1, n)
_for(j, 1, n)
g[i][j] |= g[i][k] && g[k][j];
int ans = 0;
_for(i, 1, n)
{
int cnt1 = 0, cnt2 = 0;
_for(j, 1, n) cnt1 += g[i][j], cnt2 += g[j][i];
if(cnt1 > n / 2 || cnt2 > n / 2) ans++;
}
printf("%d\n", ans);
}
return 0;
}
poj 3660(传递闭包)
和上一题很像
只有知道了它和其他所有牛的关系才可确定其排名
#include <cstdio>
#include <cstring>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 110;
int g[N][N], n, m;
int main()
{
scanf("%d%d", &n, &m);
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = 1;
}
_for(k, 1, n)
_for(i, 1, n)
_for(j, 1, n)
g[i][j] |= g[i][k] && g[k][j];
int ans = 0;
_for(i, 1, n)
{
int cnt = 0;
_for(j, 1, n) cnt += g[i][j] + g[j][i];
ans += cnt == n - 1;
}
printf("%d\n", ans);
return 0;
}
hdu 1151(结论)
这道题每想出来
原来是有一个结论……
有向图的最小路径覆盖 = 节点数 - 最大匹配数
这里的最小路径覆盖指的是最少的路径覆盖整个图,每个点只在一条路径上
在网上有看到一个证明,很巧妙
首先没有边的时候答案是n条路径
如果u和v有一条边,那么匹配数+ 1需要的路径数 - 1
最多能匹配多少,路径就减去多少
很秀
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 200;
int match[N], vis[N], n, m;
vector<int> g[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(match, 0, sizeof(match));
scanf("%d%d", &n, &m);
_for(i, 1, n) g[i].clear();
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
}
int ans = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("%d\n", n - ans);
}
return 0;
}
poj 2594(传递闭包+ 结论)
这道题和上一道题的区别就是路径可以重复覆盖一个点
怎么解决这个问题呢
用传递闭包
不仅是直接连边的可以连边
可以到达的也连边,这样就可以重复
#include <cstdio>
#include <cstring>
#include <vector>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 510;
int g[N][N], match[N], vis[N], n, m;
bool dfs(int u)
{
_for(v, 1, n)
{
if(vis[v] || !g[u][v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
while(scanf("%d%d", &n, &m) && n)
{
memset(match, 0, sizeof(match));
memset(g, 0, sizeof(g));
while(m--)
{
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = 1;
}
_for(k, 1, n)
_for(i, 1, n)
_for(j, 1, n)
g[i][j] |= g[i][k] && g[k][j];
int ans = 0;
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("%d\n", n - ans);
}
return 0;
}
L - Cat VS Dog(最大独立集)
首先如果每个人讨厌的都移除的话,就有一些会冲突
而最大独立集就可以处理这样的有冲突的情况下最多取多少的问题
所以就以人为节点,把矛盾连边
然后求最大独立集就好了。结果再除以2
每次左右是相同类型的节点的话,边要连两条,最后结果除以2
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 510;
int match[N], vis[N], n, m, p;
vector<int> g[N];
string a[N], b[N];
bool dfs(int u)
{
for(int v: g[u])
{
if(vis[v]) continue;
vis[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
return false;
}
int main()
{
while(~scanf("%d%d%d", &n, &m, &p))
{
memset(match, 0, sizeof(match));
_for(i, 1, p) cin >> a[i] >> b[i], g[i].clear();
_for(i, 1, p)
_for(j, i + 1, p)
if(a[i] == b[j] || a[j] == b[i])
{
g[i].push_back(j);
g[j].push_back(i);
}
int ans = 0;
_for(i, 1, p)
{
memset(vis, 0, sizeof(vis));
ans += dfs(i);
}
printf("%d\n", (p * 2 - ans) / 2);
}
return 0;
}
M - Jamie's Contact Groups(二分图多重匹配)
一读完题就知道二分答案
但是一个组可以多个人,就不符合之前最大匹配的定义
然后就卡了很久
看了题解发现是二分图多重匹配,尼玛不知道这个知识点
有时候卡题是因为有些知识点不知道,就不要死卡在那里了
二分图多重匹配就是右侧的点可以连多条边,但是有限制的连边个数
思路和匈牙利是一样的,也是每次去寻找增广路,只是可以连多次
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e3 + 10;
int vis[N], n, m;
vector<int> g[N], match[N];
bool dfs(int u, int key)
{
rep(i, 0, g[u].size())
{
int v = g[u][i];
if(vis[v]) continue;
vis[v] = 1;
if(match[v].size() < key)
{
match[v].push_back(u); //如果还可以匹配就加入新的边
return true;
}
else
{
rep(j, 0, match[v].size())
if(dfs(match[v][j], key)) //类似最大匹配
{
match[v][j] = u; //找到了就替换
return true;
}
}
}
return false;
}
bool check(int key)
{
_for(i, 1, m) match[i].clear();
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(!dfs(i, key)) return false;
}
return true;
}
int main()
{
while(scanf("%d%d", &n, &m) && n)
{
_for(i, 1, n) g[i].clear();
_for(i, 1, n)
{
int x;
string name;
cin >> name;
while(1)
{
cin >> x;
g[i].push_back(++x);
if(getchar() == '\n') break;
}
}
int l = 0, r = n;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) r = m;
else l = m;
}
printf("%d\n", r);
}
return 0;
}
O - Steady Cow Assignment(二分图多重匹配)
二分图多重匹配不是求最优,而是验证是否可以匹配
所以经常用来验证答案,比如二分答案,枚举答案
这道题b很小只有20,不用二分答案直接枚举也可以
判断每一个答案的时候,知道了差值,那就直接枚举区间就好了
#include <cstdio>
#include <vector>
#include <cstring>
#include <iostream>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 1e3 + 10;
int g[N][25], vis[N], lim[N], n, b;
vector<int> match[N];
bool dfs(int u, int l, int r)
{
_for(i, l, r)
{
int v = g[u][i];
if(vis[v]) continue;
vis[v] = 1;
if(match[v].size() < lim[v])
{
match[v].push_back(u);
return true;
}
else
{
rep(j, 0, match[v].size())
if(dfs(match[v][j], l, r))
{
match[v][j] = u;
return true;
}
}
}
return false;
}
bool pd(int l, int r)
{
_for(i, 1, b) match[i].clear();
_for(i, 1, n)
{
memset(vis, 0, sizeof(vis));
if(!dfs(i, l, r)) return false;
}
return true;
}
bool check(int len)
{
_for(l, 1, b)
{
int r = l + len - 1;
if(r > b) break;
if(pd(l, r)) return true;
}
return false;
}
int main()
{
scanf("%d%d", &n, &b);
_for(i, 1, n)
_for(j, 1, b)
scanf("%d", &g[i][j]);
_for(i, 1, b) scanf("%d", &lim[i]);
int l = 0, r = b;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) r = m;
else l = m;
}
printf("%d\n", r);
return 0;
}
周六 5.29
N - Optimal Milking(二分图多重匹配)
二分答案+多重匹配
用Floyed预处理一下
注意0是代表无穷大(题目没理解情况卡了很久)
#include <cstdio>
#include <cstring>
#include <vector>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 250;
int g[N][N], vis[N], k, c, m;
vector<int> match[N];
bool dfs(int u, int key)
{
_for(v, 1, k)
{
if(vis[v] || g[u][v] > key) continue;
vis[v] = 1;
if(match[v].size() < m)
{
match[v].push_back(u);
return true;
}
else
{
rep(j, 0, match[v].size())
if(dfs(match[v][j], key))
{
match[v][j] = u;
return true;
}
}
}
return false;
}
bool check(int key)
{
_for(i, 1, k) match[i].clear();
_for(i, k + 1, k + c)
{
memset(vis, 0, sizeof(vis));
if(!dfs(i, key)) return false;
}
return true;
}
int main()
{
scanf("%d%d%d", &k, &c, &m);
_for(i, 1, k + c)
_for(j, 1, k + c)
{
scanf("%d", &g[i][j]);
if(i != j && !g[i][j]) g[i][j] = 1e9;
}
_for(K, 1, k + c)
_for(i, 1, k + c)
_for(j, 1, k + c)
g[i][j] = min(g[i][j], g[i][K] + g[K][j]);
int l = -1, r = 50000;
while(l + 1 < r)
{
int m = l + r >> 1;
if(check(m)) r = m;
else l = m;
}
printf("%d\n", r);
return 0;
}
P - 奔小康赚大钱(二分图最优匹配)
最优匹配就是指权值最大的完备匹配
完备匹配指x中每一个顶点都匹配成功或者y中每一个顶点都匹配成功
解法是KM算法
其实就是匈牙利 + 贪心
一开始先是没有边
然后把x中每个点的最大的边权的边加入图中
然后对于每一个x的点去匹配,用匈牙利算法
因为这时边是很少的,所以会匹配不成功
那怎么办呢,那接下来把点的第二大的边加入图中,再去匹配
总之就是每次匹配不成功就加入一条目前最优的边进去,然后再匹配
这个加边的过程挺麻烦,KM算法用了一个非常非常巧妙的方法很方便地实现了这个过程
给左右的点赋一个权值lx ly
当lx[i] + ly[j] == g[i][j]时这条边才存在
初始化时左边的lx赋值为这个点的连的边的最大权值,右边为0
根据边的存在条件,这个时候就只有每个点的最大权值边
然后去匹配,关键是这个加边操作
每次要加入次大的边,也就是g[i][j]没那么大的边
也就是差值就是lx[i] + ly[j] - g[i][j] 显然这个差值越小,那么这个边的权值最大
所以就可以在dfs的过程中维护这小权值
得出这个权值后,对于所有访问过的点,左边lx[i] -= d 右边ly[i] += d
这样的话,原来符合lx[i] + ly[j] == g[i][j]的依然符合,不同的是原来lx[i] + ly[j] > g[i][j]的
这时lx[i] -= d 而ly[j]是不变的,因为这条边根本不存在(看边的存在条件),所以不会去访问
lx[i]减去了一个d使得次小边lx[i] + ly[j] == g[i][j] 刚好符合,也就是实现了加入了一条次小边
多加了一条边就更容易匹配成功了,就这样一直贪心地加入新的边,一直这样做下去答案就是最优匹配
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 310;
int match[N], g[N][N], lx[N], ly[N], n;
int visx[N], visy[N], d;
bool dfs(int u)
{
visx[u] = 1;
_for(v, 1, n)
{
if(visy[v]) continue;
int t = lx[u] + ly[v] - g[u][v];
if(t == 0)
{
visy[v] = 1; //注意这里是这条边存在才visy[v] = 1
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
else if(t > 0) d = min(d, t); //寻找权值次小的边,注意要t > 0
}
return false;
}
int main()
{
while(~scanf("%d", &n))
{
memset(match, 0, sizeof match);
memset(lx, 0, sizeof lx);
memset(ly, 0, sizeof ly);
_for(i, 1, n)
_for(j, 1, n)
{
scanf("%d", &g[i][j]);
lx[i] = max(lx[i], g[i][j]); //初始化为最大边权
}
_for(i, 1, n)
{
while(1) //一直添加新的边,直到找到为止
{
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
d = 1e9;
if(dfs(i)) break;
_for(j, 1, n)
{
if(visx[j]) lx[j] -= d; //把所有访问过的边都改变
if(visy[j]) ly[j] += d; //相当于添加新边
}
}
}
int ans = 0;
_for(i, 1, n)
ans += g[match[i]][i];
printf("%d\n", ans);
}
return 0;
}
Q - Tour(二分图最优匹配)
把这个有向图转化成二分图
题目说环不能相交,其实就意味着不能有两条以上的有向边指向一个节点
这不就刚好是匹配吗
而且说每个点经过一次,这就刚好是完备匹配了
但是是求最小权值,那就取负值然后跑最优匹配就好了
用领接矩阵存图,不存在的边看作负无穷
注意有个大坑就是有重边,我因为这个wa了几发
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 210;
int lx[N], ly[N], match[N], visx[N], visy[N];
int g[N][N], d, n, m;
bool dfs(int u)
{
visx[u] = 1;
_for(v, 1, n)
{
if(visy[v]) continue;
int t = lx[u] + ly[v] - g[u][v];
if(!t)
{
visy[v] = 1;
if(!match[v] || dfs(match[v]))
{
match[v] = u;
return true;
}
}
else d = min(d, t); //这里可以不写t > 0 因为t不可能小于0
}
return false;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
memset(match, 0, sizeof match);
memset(lx, 0, sizeof lx);
memset(ly, 0, sizeof ly);
memset(g, 128, sizeof g);
scanf("%d%d", &n, &m);
while(m--)
{
int u, v, w;
scanf("%d%d%d", &u, &v, &w);
g[u][v] = max(g[u][v], -w);
}
_for(i, 1, n)
{
lx[i] = g[i][1];
_for(j, 2, n)
lx[i] = max(lx[i], g[i][j]);
}
_for(i, 1, n)
{
while(1)
{
memset(visx, 0, sizeof visx);
memset(visy, 0, sizeof visy);
d = 1e9;
if(dfs(i)) break;
_for(j, 1, n)
{
if(visx[j]) lx[j] -= d;
if(visy[j]) ly[j] += d;
}
}
}
int ans = 0;
_for(i, 1, n)
ans += g[match[i]][i];
printf("%d\n", -ans);
}
return 0;
}
R - Work Scheduling(一般图最大匹配)
又是新算法
而且这个算法还挺复杂
我直接抄了模板,比赛时带模板就好了
看了这位同学的代码
https://blog.csdn.net/qq_49494204/article/details/115705600
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 300;
int g[N][N], match[N], pre[N], fa[N], vis[N], inblossom[N], n;
deque<int> q;
int lca(int u, int v)
{
bool inpath[N] = {0};
while(1)
{
u = fa[u];
inpath[u] = true;
if(match[u] == -1) break;
u = pre[match[u]];
}
while(1)
{
v = fa[v];
if(inpath[v]) return v;
v = pre[match[v]];
}
return v;
}
void reset(int u, int anc)
{
while(u != anc)
{
int v = match[u];
inblossom[fa[u]] = 1;
inblossom[fa[v]] = 1;
v = pre[v];
if(fa[v] != anc) pre[v] = match[u];
u = v;
}
}
void contract(int u, int v)
{
int anc = lca(u, v);
memset(inblossom, 0, sizeof inblossom);
reset(u, anc); reset(v, anc);
if(fa[u] != anc) pre[u] = v;
if(fa[v] != anc) pre[v] = u;
_for(i, 1, n)
if(inblossom[fa[i]])
{
fa[i] = anc;
if(!vis[i])
{
q.push_back(i);
vis[i] = 1;
}
}
}
bool dfs(int s)
{
_for(i, 0, n) pre[i] = -1, vis[i] = 0, fa[i] = i;
q.clear();
q.push_back(s); vis[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop_front();
_for(v, 1, n)
if(g[u][v] && fa[v] != fa[u] && match[u] != v)
{
if(v == s || match[v] != -1 && pre[match[v]] != -1) contract(u, v);
else if(pre[v] == -1)
{
pre[v] = u;
if(match[v] != -1) q.push_back(match[v]), vis[match[v]] = 1;
else
{
u = v;
while(u != -1)
{
v = pre[u];
int w = match[v];
match[u] = v;
match[v] = u;
u = w;
}
return true;
}
}
}
}
return false;
}
int main()
{
int u, v;
memset(match, -1, sizeof match);
scanf("%d", &n);
while(~scanf("%d%d", &u, &v)) g[u][v] = g[v][u] = 1;
int ans = 0;
_for(i, 1, n)
if(match[i] == -1)
ans += dfs(i);
printf("%d\n", ans * 2);
_for(i, 1, n)
if(match[i] != -1 && match[i] < i)
printf("%d %d\n", match[i], i);
return 0;
}
周日 5.30
S - Boke and Tsukkomi(一般图最大匹配)
n个点,给你一些边。问在形成最大匹配的情况下,哪些边是多余的
(1)删除边的时候,根据题意,是相当于删除这两个点,所以与这两个点相连的所有边都要删除
(2)如果一条边没了,不能实现最大匹配,那其就不是多余的,否则是多余的
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 50;
int g[N][N], match[N], pre[N], fa[N], vis[N], inblossom[N], n, m;
deque<int> q;
int lca(int u, int v)
{
bool inpath[N] = {0};
while(1)
{
u = fa[u];
inpath[u] = true;
if(match[u] == -1) break;
u = pre[match[u]];
}
while(1)
{
v = fa[v];
if(inpath[v]) return v;
v = pre[match[v]];
}
return v;
}
void reset(int u, int anc)
{
while(u != anc)
{
int v = match[u];
inblossom[fa[u]] = 1;
inblossom[fa[v]] = 1;
v = pre[v];
if(fa[v] != anc) pre[v] = match[u];
u = v;
}
}
void contract(int u, int v)
{
int anc = lca(u, v);
memset(inblossom, 0, sizeof inblossom);
reset(u, anc); reset(v, anc);
if(fa[u] != anc) pre[u] = v;
if(fa[v] != anc) pre[v] = u;
_for(i, 1, n)
if(inblossom[fa[i]])
{
fa[i] = anc;
if(!vis[i])
{
q.push_back(i);
vis[i] = 1;
}
}
}
bool dfs(int s)
{
_for(i, 0, n) pre[i] = -1, vis[i] = 0, fa[i] = i;
q.clear();
q.push_back(s); vis[s] = 1;
while(!q.empty())
{
int u = q.front(); q.pop_front();
_for(v, 1, n)
if(g[u][v] && fa[v] != fa[u] && match[u] != v)
{
if(v == s || match[v] != -1 && pre[match[v]] != -1) contract(u, v);
else if(pre[v] == -1)
{
pre[v] = u;
if(match[v] != -1) q.push_back(match[v]), vis[match[v]] = 1;
else
{
u = v;
while(u != -1)
{
v = pre[u];
int w = match[v];
match[u] = v;
match[v] = u;
u = w;
}
return true;
}
}
}
}
return false;
}
int solve()
{
memset(match, -1, sizeof match);
int res = 0;
_for(i, 1, n)
if(match[i] == -1)
res += dfs(i);
return res;
}
int main()
{
while(~scanf("%d%d", &n, &m))
{
vector<pair<int, int> > Edge;
vector<int> ans;
memset(g, 0, sizeof g);
_for(i, 1, m)
{
int u, v;
scanf("%d%d", &u, &v);
g[u][v] = g[v][u] = 1;
Edge.push_back(make_pair(u, v));
}
int mx = solve();
rep(i, 0, Edge.size())
{
int u = Edge[i].first, v = Edge[i].second;
memset(g, 0, sizeof g);
rep(j, 0, Edge.size())
{
int x = Edge[j].first, y = Edge[j].second;
if(x == u || y == u || x == v || y == v) continue;
g[x][y] = g[y][x] = 1;
}
if(solve() + 1 != mx) ans.push_back(i + 1);
}
printf("%d\n", ans.size());
rep(i, 0, ans.size())
{
printf("%d", ans[i]);
if(i != ans.size() - 1) putchar(' ');
}
puts("");
}
return 0;
}
CodeForces - 1501C(时间复杂度)
这道题出得出其不意
比赛的时候只想到了n方的算法,看数据范围肯定超时
结果看题解后发现,实际上数值大小只在1到5e6
所以最多跑到5e6,是不会跑到n方那么大的,到5e6之前肯定已经找到了
太秀了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
const int M = 5e6 + 10;
pair<int, int> k[M];
int vis[M], a[N], n;
int main()
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
_for(i, 1, n)
_for(j, i + 1, n)
{
int now = a[i] + a[j];
if(vis[now] && i != k[now].first && j != k[now].second && i != k[now].second && j != k[now].first)
{
puts("YES");
printf("%d %d %d %d\n", i, j, k[now].first, k[now].second);
return 0;
}
vis[now] = 1;
k[now] = make_pair(i, j);
}
puts("NO");
return 0;
}
CodeForces - 1328D(思维)
比赛时想到了正解的思路,然而有个小地方想错了没A掉
首先可以121212,发现只要是偶数就可以这样放。
如果是奇数的话,这样放就必然有一个11或者22 这时要求类型相同
所以可以遍历一遍,找到相邻类型相同的设置为1,然后往两侧121212这样就好了
如果找不到就需要3,这时在结尾的时候放一个3就行了
我比赛时脑抽了搞成放123123123123,这样时有可能1231,首尾都为1要求类型相同,就是错误的
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 2e5 + 10;
int a[N], ans[N], n;
void change(int &x)
{
if(x == 2) x = 1;
else x = 2;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]);
a[n + 1] = a[1]; a[0] = a[n];
bool ok = false;
_for(i, 1, n)
if(a[i - 1] != a[i])
{
ok = true;
break;
}
if(!ok)
{
printf("1\n");
_for(i, 1, n) printf("1 "); puts("");
continue;
}
int k = 2;
if(n % 2 == 0) _for(i, 1, n) ans[i] = i % 2 + 1;
else
{
int p;
ok = false;
_for(i, 1, n)
if(a[i] == a[i - 1])
{
p = i;
ok = true;
break;
}
if(!ok)
{
k++;
_for(i, 1, n - 1) ans[i] = i % 2 + 1;
ans[n] = 3;
}
else
{
int t;
if(p == 1)
{
ans[1] = ans[n] = 1;
t = 1;
_for(i, 2, n - 1)
{
change(t);
ans[i] = t;
}
}
else
{
ans[p] = ans[p - 1] = 1;
t = 1;
_for(i, p + 1, n)
{
change(t);
ans[i] = t;
}
t = 1;
for(int i = p - 2; i >= 1; i--)
{
change(t);
ans[i] = t;
}
}
}
}
printf("%d\n", k);
_for(i, 1, n) printf("%d ", ans[i]);
puts("");
}
return 0;
}
Gym - 102028D(三角函数)
这题考试时静下心来做应该可以想出,可惜太着急
可以发现一条边长度固定,形成一个圆,推一推公式就好
注意角度和弧度的转换,求角度
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const double pi = acos(-1.0);
int dcmp(double x)
{
if(fabs(x) < 1e-8) return 0;
return x > 0 ? 1 : -1;
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
double a, b, r, d;
scanf("%lf%lf%lf%lf", &a, &b, &r, &d);
d = (d * pi / 180); //一开始就把角度转成弧度
double thi = atan(b / (a + r)); //用atan acos等等
double maxr = sqrt(pow(a + r, 2) + pow(b, 2));
if(dcmp(d - thi) >= 0)
{
printf("%.12f\n", maxr - r);
continue;
}
double t = thi - d;
double L = maxr * sqrt(2 * (1 - cos(t)));
double tt = (pi / 2) - (pi - t) / 2;
printf("%.12f\n", maxr - r - L * sin(tt));
}
return 0;
}
Parsa's Humongous Tree(观察+ 树形dp)
这道题我没想出来
有一个结论,就是取端点一定时最优秀的,我想到这个
其实自己想的时候也可以猜一下取端点一定是最优的
如果知道这个那就很水了,直接树形dp一波就好了
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
typedef long long ll;
const int N = 1e5 + 10;
int l[N], r[N], n;
vector<int> g[N];
ll dp[N][2]; //0 l 1 r
void dfs(int u, int fa)
{
dp[u][0] = dp[u][1] = 0;
for(int v: g[u])
{
if(v == fa) continue;
dfs(v, u);
dp[u][0] += max(dp[v][0] + abs(l[u] - l[v]), dp[v][1] + abs(l[u] - r[v]));
dp[u][1] += max(dp[v][0] + abs(r[u] - l[v]), dp[v][1] + abs(r[u] - r[v]));
}
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d%d", &l[i], &r[i]), g[i].clear();
_for(i, 1, n - 1)
{
int u, v;
scanf("%d%d", &u, &v);
g[u].push_back(v);
g[v].push_back(u);
}
dfs(1, 0);
printf("%lld\n", max(dp[1][0], dp[1][1]));
}
return 0;
}
CodeForces - 1433D(并查集)
这就是一道大水题,也是我比赛中唯一没看的一道题……
以后个人赛中做到把所有的题目意思都理解好,并且有初步的思考
不然就会出现这种情况,漏掉大水题……
#include <bits/stdc++.h>
#define rep(i, a, b) for(int i = (a); i < (b); i++)
#define _for(i, a, b) for(int i = (a); i <= (b); i++)
using namespace std;
const int N = 5e3 + 10;
int a[N], f[N], n;
int Find(int x)
{
if(f[x] == x)
return x;
return f[x] = Find(f[x]);
}
int main()
{
int T; scanf("%d", &T);
while(T--)
{
scanf("%d", &n);
_for(i, 1, n) scanf("%d", &a[i]), f[i] = i;
vector<pair<int, int> > ans;
_for(i, 1, n)
_for(j, 1, n)
if(a[i]!= a[j] && Find(i) != Find(j))
{
ans.push_back(make_pair(i, j));
f[Find(i)] = Find(j);
}
if(ans.size() != n - 1) puts("NO");
else
{
puts("YES");
for(auto x: ans) printf("%d %d\n", x.first, x.second);
}
}
return 0;
}