GRYZ10.19模拟赛解题报告
写在前面
今天洛谷打卡大凶。
“忌考试,忌装弱”,感觉很慌。
期望得分:\(100+100+100=300pts\)
实际得分:\(100+100+100=300pts\)
不要FST啊!!!!!
没有大样例差评。
CSP有四道题,为什么模拟赛只给三道?
下面来分享一下我做题过程:
14:10 下发题面
14:13 读完 T1,感觉可以秒了它。
14:15 T2 不会扔掉,然后发现 T3 是非常经典的次短路(原汁原味)。
14:23 T1 写完了,开始写 T3 (因为忘记怎么做了只能边写边yy)
14:34 T3 写完了,一遍过了样例,感觉这个样例很弱,可能自己又要挂分了
14:35 感觉 T2 很像一个背包,然后二分答案在外面套起来,这样复杂度是 \(\mathcal O(n^2 \log V)\) 的,极限在 \(1.2e8\) 左右,我感觉机房的评测机不一定能跑过去。其他地方感觉也不好优化了,于是开始写。
14:45 T2 写完了,稍微调了调过了样例
15:15 把 T1 的暴力和对拍写完开始拍。
15:26 感觉有点无聊开始写今天的解题报告
15:50 解题报告写完了
16:30 感觉有点饿,出去吃了条脆脆鲨,但是被 ycc 发现了
17:30 一个小插曲:距离结束还有 10min,zzg 的电脑蓝屏了哈哈哈哈,大杯。
17:40 另一个小插曲:结束了我第一个把用飞鸽把代码发过去,一会儿 斜揽残箫 过来跟我说,来来来我好不容易手造了一个数据,专门卡什么什么的,我给你试一试。然后呢我把 freopen 注释掉试了试,但是我大意了,这个时候老师还没有接收我的代码,作为第一个发过去的被压在了最下面,然后接收的时候已经把 freopen 注释掉了,然后我就十分乌龙的挂掉了 T2 /ll
17:45 听说 Chen_怡 半小时 AK 了这场比赛,我只能 Orz。
T1
肯定不能像他说的那样枚举子集啊。
然后你转化一下方向,从前到后确定每一位,把 \(n\) 个串的某一位放到一起考虑,然后你发现可以直接统计某一位 \(1\) 的个数或者 \(0\) 的个数,然后通过判断多少直接确定出填什么是最优的。每一位是相互独立的,所以这样做没有问题。
时间复杂度 \(\mathcal O(nL)\),瓶颈在读入。
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 1e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, L;
char s[MAXN];
int a[MAXN], b[MAXN];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
int main()
{
freopen("curse.in","r",stdin);
freopen("curse.out","w",stdout);
n = read();
for(int i = 1; i <= n; ++i) {
cin >> s + 1;
L = strlen(s + 1);
for(int j = 1; j <= L; ++j) {
a[j] += (s[j] == '0');
}
}
for(int i = 1; i <= L; ++i) {
if(a[i] >= n - a[i]) {
b[i] = 0;
} else {
b[i] = 1;
}
}
for(int i = 1; i <= L; ++i) {
printf("%d", b[i]);
}
puts("");
return 0;
}
T2
看完题面,就能感觉出来它需要二分这个 \(L\)。
因为你有两种法杖,你需要确定怎么去使用这些神光是最优的。然后你很自然的想到了 DP,而且他还很像背包 DP。
先对法坛的位置排序,方便后面的处理。
然后你发现 \(R,G\) 的范围很大,但是你想了想,发现它就是来吓唬你的。
当 \(R + G \ge n\) 的时候,直接输出 \(1\) 就行。
然后不能直接判断的情况就是 \(R + G \le 2000\) 的情况了。
这样的情况下足够支持你开一个二维数组。你想了想发现可以这样设状态:
设 \(f_{i,j}\) 表示已经消灭了前 \(i\) 个法坛,用了 \(j\) 次红色神光的情况下,最少用了多少次绿色神光。
转移方程:
我们让 \(a_i\) 作为光芒笼罩的右端点,因为每次神光可能会笼罩多个法坛,所以转移的时候要从上一个不能笼罩的法坛的位置转移。
其中 \(l1,l2\) 就分别表示红色神光和绿色神光上一个不能笼罩的位置。
你在把 \(a\) 数组排序后可以直接用 lower_bound
查一下这个位置,但是这会平白无故多一个 \(\log\),可能只有我这个傻逼会想到这么拉的做法吧。
然后你发现,每次 DP 时 \(L\) 是固定的,所以 \(l1,l2\) 的变化一定是单调递增的,然后你就发现你可以把他们当做指针,在不合法的时候暴力右移就可以了,很显然最多会移动 \(n\) 次。
然后就做完了。
时间复杂度 \(\mathcal O(nR \log V)\)
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2021;
const int INF = 1e9+7;
const int mod = 1e9+7;
int n, R, G;
int a[MAXN];
int f[2021][2021];
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
bool Check(int lim) {
for(int i = 1; i <= n; ++i) {
for(int j = 0; j <= R; ++j) {
f[i][j] = 0x3f3f3f3f;
}
}
// cout<<"new start !\n";
// cout<<lim<<" \n";
f[0][0] = 0;
int l1 = 0, l2 = 0;
for(int i = 1; i <= n; ++i) {
while(l1 < n && a[l1 + 1] <= a[i] - lim) l1++;
while(l2 < n && a[l2 + 1] <= a[i] - 2 * lim) l2++;
// cout<<l1<<" "<<l2<<" "<<i<<" \n";
for(int j = 0; j <= R; ++j) {
if(j) f[i][j] = min(f[i][j], f[l1][j - 1]);
f[i][j] = min(f[i][j], f[l2][j] + 1);
}
}
for(int i = 0; i <= R; ++i) {
if(f[n][i] <= G) return true;
}
return false;
}
int main()
{
freopen("light.in","r",stdin);
freopen("light.out","w",stdout);
n = read(), R = read(), G = read();
for(int i = 1; i <= n; ++i) {
a[i] = read();
}
sort(a + 1, a + n + 1);
if(R + G >= n) {
puts("1");
return 0;
}
int l = 1, r = 1000000000, ans = r;
while(l <= r) {
int mid = (l + r) >> 1;
// cout<<l<<" "<<mid<<" "<<r<<"\n";
if(Check(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}
T3
次短路板子题吧。
考虑在存最短路 \(dis\) 的同时记录一个次短路 \(Dis\)。
因为一个点可能更新多次,所以我选择 SPFA,并且点数和边数看上去不是很多,应该不好卡。(卡到 \(\mathcal O(nm)\) 也不是不可以?)
更新的时候需要分类讨论一下。
不管最短路还是次短路更新的时候都将其入队。
然后就做完了。
/*
Work by: Suzt_ilymics
Problem: 不知名屑题
Knowledge: 垃圾算法
Time: O(能过)
希望别假掉!!!
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#include<cmath>
#include<queue>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl
using namespace std;
const int MAXN = 2e5+5;
const int INF = 1e9+7;
const int mod = 1e9+7;
struct edge {
int to, w, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge = 1;
int n, m;
int dis[MAXN], Dis[MAXN];
bool vis[MAXN];
queue<int> q;
int read(){
int s = 0, f = 0;
char ch = getchar();
while(!isdigit(ch)) f |= (ch == '-'), ch = getchar();
while(isdigit(ch)) s = (s << 1) + (s << 3) + ch - '0' , ch = getchar();
return f ? -s : s;
}
void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }
void SPFA() {
memset(dis, 0x3f, sizeof dis);
memset(Dis, 0x3f, sizeof Dis);
dis[1] = 0, vis[1] = true, q.push(1);
while(!q.empty()) {
int u = q.front(); q.pop();
vis[u] = false;
for(int i = head[u]; i; i = e[i].nxt) {
int v = e[i].to;
int w1 = dis[u] + e[i].w, w2 = Dis[u] + e[i].w;
if(dis[v] > w1) {
dis[v] = w1;
if(!vis[v]) q.push(v);
} else if(dis[v] == w1) {
if(Dis[v] > w2) {
Dis[v] = w2;
if(!vis[v]) q.push(v);
}
} else {
if(Dis[v] > w1) {
Dis[v] = w1;
if(!vis[v]) q.push(v);
}
}
}
}
}
int main()
{
freopen("maze.in","r",stdin);
freopen("maze.out","w",stdout);
n = read(), m = read();
for(int i = 1, u, v, w; i <= m; ++i) {
u = read(), v = read(), w = read();
add_edge(u, v, w), add_edge(v, u, w);
}
SPFA();
printf("%d\n", Dis[n]);
return 0;
}
/*
in:
5 7
1 2 5
1 5 10
1 3 1
1 4 5
2 4 3
4 5 5
3 4 4
out:
12
1 -> 3 -> 1 -> 3 -> 4 -> 5
*/