写在前面

期望得分:\(100+100+100=300pts\)

实际得分:\(100+100+10=210pts\)

挂了一个 \(0\) /cy

理想很丰满,现实很骨感。

题挺没意思的,第一题进制,第二题二分+最短路,第三题原题,也是二分

下面来介绍一下我的做题过程:

14:00 开始。

14:15 读完题,感觉今天的题都有点奇怪。

14:15 写完 T1 并自己写了一个 checker 对拍。

15:00 写完 T2,然后发现 T3 是原题。

15:22 写完 T3,然后一直划水。

中间又造了几个极弱的样例,感觉 T2,T3 都没有问题。

17:30 考试结束,T3 挂了 90,原题啊原题!我挂了 90!我是不是菜?啊 我 是 不 是 菜 啊?

T1

你发现能填的数是 \(3^0,3^1,3^2...\),然后这个东西转化成三进制刚好对应着三进制的每一位。

因为只能放一次嘛,你在想如果你把 \(W\) 转化成三进制后是 \(11010011...\) 的形式(只有 \(1\) 和 \(0\))

你考虑把要处理的 \(W\) 来转化成你想要的形式,怎么办?加砝码!

假设 \(W\) 拆成三进制后是 \(112001212022...\) 之类的,那么你从低位向高位遍历,如果第 \(x\) 为是 \(2\) 就加一个 \(3^{x-1}\) 的砝码,然后进位。如果是 \(1\) 和 \(0\) 就不用管,遍历完就能得到我们想要的那个形式了。就可以直接得到左边要加的砝码。

/*
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 LL MAXN = 1e5+5;
const LL INF = 1e9+7;
const LL mod = 1e9+7;

LL W;
LL stc[200], sc = 0;
LL a[200], top1 = 0;
LL b[200], top2 = 0;

LL read(){
LL 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("entertain.in","r",stdin);
freopen("entertain.out","w",stdout);
b[1] = 1;
for(LL i = 2; i <= 35; ++i) b[i] = b[i - 1] * 3;
W = read();
LL x = W;
while(x) {
stc[++sc] = x % 3;
x /= 3;
}
// for(int i = 1; i <= sc; ++i) cout<<stc[i]<<" "; puts("");
for(LL i = 1; i <= sc; ++i) {
stc[i + 1] += stc[i] / 3;
stc[i] %= 3;
if(stc[i] == 2) {
a[i] = true;
stc[i] = 0, stc[i + 1] ++;
}
if(stc[i + 1] > 0) sc = max(sc, i + 1);
}
// for(int i = 1; i <= sc; ++i) cout<<stc[i]<<" "; puts("");
// for(int i = 1; i <= sc; ++i) cout<<a[i]<<" "; puts("");
// int cnt1 = 0, cnt2 = 0;
// for(int i = 1; i <= sc; ++i) cnt1 += (stc[i] == 1), cnt2 += a[i];
// cout<<cnt1<<" "<<cnt2 + 1<<"\n";
for(LL i = 1; i <= sc; ++i) {
if(stc[i] == 1) {
cout<<b[i]<<" ";
}
}
puts("");
cout<<W<<" ";
for(LL i = 1; i <= sc; ++i) {
if(a[i]) {
cout<<b[i]<<" ";
}
}
puts("");
return 0;
}


T2

分层图

这个题没给 \(k\) 的数据范围,\(k\) 大了应该能卡掉分层图做法。

每用几次免消耗体力的机会就是从上一层走到下一层。

然后就可以建 \(k\) 层图,跑 Dij 和 SPFA 都行,记录最大值即可。

二分答案

这个应该是正解。

二分答案 \(x\),把 \(w > x\) 的边的权值看做 \(1\),\(w \le x\) 的边的权值看做 \(0\),然后跑最短路。

如果 \(dis_n \le k\) 说明这个答案 \(x\) 合法,然后下调边界,否则表示不合法,上调边界。

代码是二分答案的做法:

/*
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;

struct edge {
int to, w, nxt;
}e[MAXN << 1];
int head[MAXN], num_edge = 1;

int n, m, K;
int dis[MAXN];
bool vis[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;
}

void add_edge(int from, int to, int w) { e[++num_edge] = (edge){to, w, head[from]}, head[from] = num_edge; }

bool SPFA(int lim) {
queue<int> q;
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;
if(dis[v] > dis[u] + (e[i].w > lim)) {
dis[v] = dis[u] + (e[i].w > lim);
if(!vis[v]) q.push(v), vis[v] = true;
}
}
}
return dis[n] <= K;
}

int main()
{
freopen("novel.in","r",stdin);
freopen("novel.out","w",stdout);
n = read(), m = read(), K = read();
int l = 0, r = 1000000, ans = 0;
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(r);
if(dis[n] == dis[0]) {
puts("-1");
return 0;
}
while(l <= r) {
int mid = (l + r) >> 1;
if(SPFA(mid)) ans = mid, r = mid - 1;
else l = mid + 1;
}
printf("%d\n", ans);
return 0;
}


T3

二分惩罚值,好像也叫什么 wqs二分,​​洛谷原题​​。

/*
Work by: Suzt_ilymics
Knowledge: ??
Time: O(??)
*/
#include<iostream>
#include<cstdio>
#include<cstring>
#include<algorithm>
#define LL long long
#define orz cout<<"lkp AK IOI!"<<endl

using namespace std;
const int MAXN = 1e5+10;
const int INF = 1;
const int mod = 1;

struct edge{
int from, to, w, hb;
}e[MAXN];
int num_edge = 0;

int n, m, b;
int ans = 0, cnt = 0, cnt0 = 0;
int fa[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;
}

bool cmp(edge x, edge y){ return x.w < y.w; }

void add_edge(int from, int to, int w, int hb){ e[++num_edge] = (edge){from, to, w, hb}; }

int find(int x){ return fa[x] == x ? x : fa[x] = find(fa[x]); }

void kruskal(){
for(int i = 0; i <= n; ++i) fa[i] = i;
for(int i = 1; i <= num_edge; ++i){
int uf = find(e[i].from), vf = find(e[i].to);
if(uf != vf){
fa[uf] = vf;
ans += e[i].w;
cnt++;
if(e[i].hb == 0) cnt0++;
if(cnt == n - 1) return ;
}
}
}

bool check(int mid){
for(int i = 1; i <= m; ++i) if(e[i].hb == 0) e[i].w -= mid;
cnt = 0, cnt0 = 0, ans = 0;
sort(e + 1, e + m + 1, cmp);
kruskal();
for(int i = 1; i <= m; ++i) if(e[i].hb == 0) e[i].w += mid;
return cnt0 >= b;
}

int main()
{
n = read(), m = read(), b = read();
int u, v, w, hb;
for(int i = 1; i <= m; ++i){
u = read() + 1, v = read() + 1, w = read(), hb = read();
add_edge(u, v, w, hb);
}
int l = -105, r = 105, last;
while(l <= r){
int mid = (l + r) >> 1;
if(check(mid)) {
r = mid - 1, last = mid;
}
else l = mid + 1;
}
// last -= 1;
// cout<<check(last)<<endl;
ans = 0, cnt = 0, cnt0 = 0;
check(last);
printf("%d", ans + last * b);
return 0;
}