2024睿抗省赛本科组DE题解
章鱼图的判断
题意:
对于无向图\(G = (V, E)\),我们将有且只有一个环的、大于2个顶点的无向连通图称之为章鱼图,因为其形状像是一个环(身体)带着若干个树(触手),故得名。
给定一个无向图,请你判断是不是只有一个章鱼子图存在。
注意:这里的章鱼子图指的是满足章鱼图性质的极大连通子图
思路:
①有几个联通快?dfs一遍即可
②如何判断是不是“章鱼图”?对于一个合法的章鱼图,\(点数=\frac{出度数 + 入度数}{4}\)
③如何求环上的点数?dfs,保留前驱,更新深度
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 1e5 + 10;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
int head[N];
int ne[N * 2];
int idx;
int w[N * 2];
int group[N];
int cnt = 0;
int num = 0;
int dep[N];
bool flag = false;
void add (int x, int y) {
ne[idx] = head[x];
w[idx] = y;
head[x] = idx ++;
}
void dfs (int u, int father) {
group[u] = cnt;
for (int i = head[u]; i != -1; i = ne[i]) {
int ww = w[i];
if (ww != father && !group[ww]) {
dfs (ww, u);
}
}
}
void dfs2 (int u, int deep, int pre) {
dep[u] = deep;
for (int i = head[u]; i != -1; i = ne[i]) {
int ww = w[i];
if (ww == pre) continue;
if (ww != pre && dep[ww]) {
num = abs(dep[u] - dep[ww]) + 1;
flag = 1;
return ;
}
dfs2 (ww, deep + 1, u);
if (flag) return ;
}
}
void solve() {
int n, m;
cin >> n >> m;
idx = 0;
cnt = 0;
memset (head, -1, sizeof (head));
for (int i = 1; i <= n; ++i) {
group[i] = 0;
}
vector<int> Size (n + 1, 0);
vector<int> In (n + 1, 0);
vector<int> Out (n + 1, 0);
vector<int> groupIn (n + 1, 0);
vector<int> groupOut (n + 1, 0);
for (int i = 1; i <= m; ++i) {
int x, y;
cin >> x >> y;
add (x, y);
add (y, x);
In[x] ++;
In[y] ++;
Out[x] ++;
Out[y] ++;
}
for (int i = 1; i <= n; ++i) {
if (!group[i]) {
cnt ++;
dfs (i, 0);
}
}
for (int i = 1; i <= n; ++i) {
Size[group[i]] ++;
groupIn[group[i]] += In[i];
groupOut[group[i]] += Out[i];
}
int res = 0;
int resid = 0;
for (int i = 1; i <= cnt; ++i) {
if (Size[i] >= 3 && Size[i] == (groupIn[i] + groupOut[i]) / 4) {
res ++;
resid = i;
}
}
if (res >= 2 || res == 0) {
cout << "No" << " " << res << "\n";
}else{
flag = false;
for (int i = 1; i <= n; ++i) {
dep[i] = 0;
}
for (int i = 1; i <= n; ++i) {
if (group[i] == resid) {
dfs2 (i, 1, -1);
break;
}
}
cout << "Yes" << " " << num << "\n";
}
}
int main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}
工作安排
题意:
小\(K\)有\(N\)项工作等待完成,第\(i\)项工作需要花\(t_i\)单位时间,必须在\(d_i\)时刻或之前完成,报酬为\(p_i\)。假设小\(K\)工作时刻从\(0\)开始,且同一时刻只能做一项工作、工作一旦开始则不可中断或切换至其他工作,请你帮小\(K\)规划一下如何选择合适的工作,使小\(K\)可以获得最多的报酬。
思路:
按截止时间排序 + 倒序背包
假设你没有按截止时间排序,而是随机顺序,结果你把一个截止时间很晚的任务放进去了,占据了一段时间。然后你遇到一个截止时间早、时间短、收益高的任务,结果你却安排不进去了 → 损失最优解。
而如果你按截止时间升序处理,就能保证:每次你在当前时刻前安排能做的任务,不会冲突掉未来那些“必须早做”的任务
TLE代码
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> nd[i].x >> nd[i].y >> nd[i].z;
}
sort (nd + 1, nd + 1 + n, cmp);
vector<int> dp (N, 0);
for (int i = 1; i <= n; ++i) {
for (int j = nd[i].y; j >= 0; --j) {
if (j - nd[i].x >= 0) dp[j] = max (dp[j], dp[j - nd[i].x] + nd[i].z);
}
}
int res = 0;
for (int i = 0; i < N; ++i) {
res = max (res, dp[i]);
}
cout << res << "\n";
}
问题
1、每组数据进 solve() 都会重新构造一个 vector
2、把 int j、int i、int n 全抬升成 long long 以后,编译器可以直接在 R 系列(64 bit)寄存器上循环,省掉每次从 32→64 的零扩展或者 64→32 的截断资源冲刺
代码
#include<bits/stdc++.h>
#define ll long long
#define ce cerr
#define ull unsigned long long
#define lll __int128
using namespace std;
#define int long long
const int inf = 0x3f3f3f3f;
const ll iinf = 1e18;
const int N = 5010;
//cin.ignore(std::numeric_limits< streamsize >::max(), '\n');
int t;
struct node {
int x, y, z;
};
node nd[N];
int dp[N];
bool cmp (node a, node b) {
return a.y < b.y;
}
void solve() {
int n;
cin >> n;
for (int i = 1; i <= n; ++i) {
cin >> nd[i].x >> nd[i].y >> nd[i].z;
}
sort (nd + 1, nd + 1 + n, cmp);
memset (dp, 0, sizeof (dp));
for (int i = 1; i <= n; ++i) {
for (int j = nd[i].y; j >= nd[i].x; --j) {
dp[j] = max (dp[j], dp[j - nd[i].x] + nd[i].z);
}
}
int res = 0;
for (int i = 0; i < N; ++i) {
res = max (res, dp[i]);
}
cout << res << "\n";
}
signed main() {
ios::sync_with_stdio (false);
cin.tie(NULL);
cout.tie(NULL);
t = 1;
cin >> t;
while (t --) {
solve();
}
return 0;
}