2021.10.17CSP模拟赛 赛后总结
这一场个人感觉考得还是很不错的,至少能拿的分都拿了。
A. [SCOI2009]生日快乐
乍一看以为是一道数学题,然后看到下面的数据范围:\(1 \leq N \leq 10\)。
突然发现写个爆搜就可以了(其实还是想了很长时间 QwQ)。
我们存 3 个变量 \(dfs(x, y, k)\) 表示在 \(x * y\) 的矩形中切 \(k\) 刀,长和宽最小比例最大是多少。
我们枚举切几刀,然后上下左右都要搜一遍,求个最大值即可。
代码十分简洁。
#include <iostream>
#include <cstdio>
#include <algorithm>
using namespace std;
double n, m;
int k;
inline double dfs(double x, double y, int k){
if(k == 1) return max(x / y, y / x);
double res = 1e9;
for(int i = 1; i <= (k >> 1); i++){
double tx = x * i / k, ty = y * i / k;
res = min(res, max(dfs(tx, y, i), dfs(x - tx, y, k - i)));
res = min(res, max(dfs(x, ty, i), dfs(x, y - ty, k - i)));
}
return res;
}
int main(){
scanf("%lf%lf%d", &n, &m, &k);
printf("%.6lf\n", dfs(n, m, k));
return 0;
}
B.「POI2011 R2 Day1」垃圾运输 Garbage
emm……这道题考场上看了一眼,觉得有点困难,所以就先弃了,去看 \(T3\) 了,最后也没时间写了,后来发现这道题可能比 \(T3\) 好想一点,但是不好写……
而且内部 \(oj\) 的 \(spj\) 有锅,在洛谷上能 \(AC\) 的代码交上去只有 52 分(还好没写)。
下面进入正文:
我们发现对于当前颜色和目标颜色一样的边不需要考虑,所以我们只把需要改变颜色的边加入到图里。
然后其实就是找欧拉回路,也就是找环,然后就没别的了。
提前判断一下如果有度数为奇数的点,直接输出 NIE
。
(洛谷AC代码)
#include <iostream>
#include <cstdio>
using namespace std;
const int N = 1e5 + 10;
const int M = 1e6 + 10;
int n, m;
struct node{
int v, nxt;
}edge[M << 1];
int head[N], tot = 1;
int du[N], vis[M << 1];
int stk[M], top;
int cnt;
inline void add(int x, int y){
edge[++tot] = (node){y, head[x]};
head[x] = tot;
}
inline void dfs(int x, int root){
stk[++top] = x;
du[x] -= 2;
for(int i = head[x]; i; i = edge[i].nxt){
head[x] = i;
if(vis[i]) continue;
vis[i] = vis[i ^ 1] = 1;
int y = edge[i].v;
if(y == root && x != root){
cnt++;
stk[++top] = y;
return;
}
dfs(y, root);
return;
}
}
int main(){
scanf("%d%d", &n, &m);
for(int i = 1; i <= m; i++){
int u, v, p, q;
scanf("%d%d%d%d", &u, &v, &p, &q);
if(p ^ q){
add(u, v), add(v, u);
du[u]++, du[v]++;
}
}
for(int i = 1; i <= n; i++)
if(du[i] & 1){
puts("NIE");
return 0;
}
for(int i = 1; i <= n; i++){
if(!du[i]) continue;
while(du[i]) dfs(i, i);
}
printf("%d\n", cnt);
for(int i = 1; i <= top; i++){
int u = stk[i], k = 1;
i++;
while(stk[i] != u && i <= top)
k++, i++;
printf("%d ", k);
for(int j = i - k; j <= i; j++)
printf("%d ", stk[j]);
puts("");
}
return 0;
}
C. [JSOI2010]快递服务
一道非常巧妙的 \(dp\),考场上写了 3h,还好最后调出来了,心态没炸。
考虑朴素的状态 \(f[i][a][b][c]\),应该很容易看出来含义。
即,第 \(i\) 的个询问,三辆车分别在 \(a,b,c\) 三点时的最小油费。
但是这样转移的话时间,空间都会爆炸,所以考虑优化。
我们发现,对于上述状态,\(a,b,c\) 中必定有一个等于 \(p[i]\) 即当前询问的节点编号。
所以我们可以压掉这一维,只用剩下两维进行转移,那么:
设:\(x\) 为上一次询问的节点,\(y\) 为当前询问节点。
\(f[i][a][b] = min(f[i - 1][a]][b] + dis(x, y))\) (上一次在 \(x\) 点的车走到 \(y\))
\(f[i][a][b] = min(f[i - 1][x][b] + dis(a, y))\) (上一次在 \(a\) 点的车走到 \(y\))
\(f[i][a][b] = min(f[i - 1][a][x] + dis(b, y))\) (上一次在 \(b\) 点的车走到 \(y\))
然后本题应该就可以 \(A\) 了,因为题目给了 \(1G\) 的空间……
但其实还可以滚动一下。
我的代码强制 \(f\) 的第二维 \(<\) 第三维,其实可以不用。
#include <iostream>
#include <cstdio>
#include <algorithm>
#include <cstring>
using namespace std;
inline int read(){
int y = 0;
char ch = getchar();
while(ch < '0' || ch > '9') ch = getchar();
while(ch >= '0' && ch <= '9') y = (y << 3) + (y << 1) + ch - '0', ch = getchar();
return y;
}
const int N = 210;
const int M = 1010;
int dis[N][N], f[2][N][N];//f[i][x][y] (x <= y)
int n, y, cnt, ans = 1e9;
int x, l;
int main(){
// freopen("C.in", "r", stdin);
// freopen("C.out", "w", stdout);
n = read();
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
dis[i][j] = read();
memset(f, 0x3f, sizeof(f));
x = 1;
f[0][2][3] = 0;
while(scanf("%d", &y) != EOF){
l ^= 1;
memset(f[l], 0x3f, sizeof(f[l]));
cnt++;
// cout << cnt << " " << y << endl;
for(int i = 1; i <= n; i++)
for(int j = i; j <= n; j++){
f[l][i][j] = min(f[l][i][j], f[l ^ 1][i][j] + dis[x][y]);
if(x < j) f[l][x][j] = min(f[l][x][j], f[l ^ 1][i][j] + dis[i][y]);
else f[l][j][x] = min(f[l][j][x], f[l ^ 1][i][j] + dis[i][y]);
if(x < i) f[l][x][i] = min(f[l][x][i], f[l ^ 1][i][j] + dis[j][y]);
else f[l][i][x] = min(f[l][i][x], f[l ^ 1][i][j] + dis[j][y]);
}
x = y;
}
for(int i = 1; i <= n; i++)
for(int j = 1; j <= n; j++)
ans = min(ans, f[l][i][j]);
printf("%d\n", ans);
return 0;
}
D. [HNOI2010]物品调度
emm……还不会。
考场上输出 \(n - 1\) 得了 10pts。