寫在前面
今天洛谷打卡大凶。
“忌考試,忌裝弱”,感覺很慌。
期望得分:\(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
*/