codeforces div1 ABC彙總(長期更新)
Codeforces Round #310 (Div. 1)
Codeforces Round #309 (Div. 1)
Codeforces Round #305 (Div. 1)
B - Mike and Feet
給出n個數,這n個數在區間長度為i(1~n)的時候可以分割成一些區間,這每個區間都會有一個最小值,在同樣長度的這些區間的最小值中,輸出最大值
思路:用單調棧維護每個元素a[i]作為最小元素的區間。就可以知道對於size=x的區間的最小值是多少。顯然對於答案陣列ans[i],我們可以知道。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 2e5 + 5;
int a[maxn], lb[maxn], rb[maxn], ans[maxn];
int main()
{
int n;
scanf("%d", &n);
for(int i = 0; i < n; i++) scanf("%d", &a[i]);
{
stack<int >sta;
lb[0] = 0, sta.push(0);
for(int i = 1; i < n; i++)
{
while(sta.size() && a[sta.top()] >= a[i]) sta.pop();
lb[i] = sta.size() == 0 ? 0 : sta.top() + 1;
sta.push(i);
}
}
{
stack<int>sta;
rb[n-1 ] = n-1, sta.push(n-1);
for(int i = n-2; i >= 0; i--)
{
while(sta.size() && a[sta.top()] >= a[i]) sta.pop();
rb[i] = sta.size() == 0 ? n-1 : sta.top() - 1;
sta.push(i);
}
}
for(int i = 0; i < n; i++)
{
int len = rb[i] - lb[i] + 1;
ans[len] = max(ans[len], a[i]);
}
for(int i = n - 1; i >= 1; i--) ans[i] = max(ans[i], ans[i + 1]);
for(int i = 1; i <= n; i++) printf("%d%c", ans[i], i == n ? '\n' : ' ');
return 0;
}
Codeforces Round #302 (Div. 1)
A - Writing Code(類揹包dp)
問你n個人寫m行程式碼,出的bug的數量少於b個的方案數,每個人寫一行程式碼產生a[i]個bug。()。
dp[i][j][k]:前i個人寫了j行程式碼一共出了k個bug的方案數。
那麼容易寫出轉移。
常規套路化簡成dp[i][j][k] = dp[i-1][j][k] + dp[i][j-1][k-a[i]]即可。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 500 + 5;
long long dp[maxn][maxn];
long long sum[maxn][maxn];
int a[maxn];
int main()
{
int n, m, b, mod;
scanf("%d%d%d%d", &n, &m, &b, &mod);
for(int i = 1; i <= n; i++) scanf("%d", &a[i]);
dp[0][0] = 1;
for(int i = 1; i <= n; i++)
{
for(int j = 1; j <= m; j++)
{
for(int k = a[i]; k <= b; k++)
{
dp[j][k] = (dp[j][k] + dp[j-1][k-a[i]]) % mod;
}
}
}
long long ans = 0;
for(int i = 0; i <= b; i++)
ans = (ans + dp[m][i]) % mod;
printf("%lld\n", ans);
return 0;
}
B - Destroying Roads(列舉)
給你n個節點,m條邊(權值為1)的無向圖,問你滿足s1到t1經過的路程不超過l1,且s2到t2經過的路程不超過l2的條件下,最多可以刪除幾條邊。()
預處理出兩點間的最短距離,我們直接列舉公共部分,然後計算一下最少可以用幾個。輸出es-ans即可。注意一個可以反向的問題,注意一個ans的初始值應該設定成沒有公共部分的時候的最少使用的邊的數量。
#include <bits/stdc++.h>
using namespace std;
const int maxn = 3000 + 5;
const int INF = 0x3f3f3f3f;
int dp[maxn][maxn];
int vs, es;
int s1, t1, l1, s2, t2, l2;
vector<int>G[maxn];
void bfs()
{
for(int st = 1; st <= vs; st++)
{
queue<int>que;
for(int i = 1; i <= vs; i++) dp[st][i] = INF;
que.push(st);
dp[st][st] = 0;
while(que.size())
{
int v = que.front();que.pop();
for(auto to : G[v])
{
if(dp[st][to] == INF)
{
dp[st][to] = dp[st][v] + 1;
que.push(to);
}
}
}
}
}
int main()
{
scanf("%d%d", &vs, &es);
for(int i = 0; i < es; i++)
{
int u, v;
scanf("%d%d", &u, &v);
G[u].push_back(v);
G[v].push_back(u);
}
scanf("%d%d%d%d%d%d", &s1, &t1, &l1, &s2, &t2, &l2);
bfs();
if(dp[s1][t1] > l1 || dp[s2][t2] > l2)
{
puts("-1");
return 0;
}
int minn = dp[s1][t1] + dp[s2][t2];
for(int i = 1; i <= vs; i++)
{
for(int j = 1; j <= vs; j++)
{
int time11 = dp[s1][i] + dp[i][j] + dp[j][t1];
int time12 = dp[s1][j] + dp[i][j] + dp[i][t1];
int time21 = dp[s2][i] + dp[i][j] + dp[j][t2];
int time22 = dp[s2][j] + dp[i][j] + dp[i][t2];
// printf("dp[%d][%d] = %d\n", i, j, dp[i][j]);
// printf("%d %d %d %d\n", time11, time12, time21, time22);
if(time11 <= l1 && time21 <= l2) minn = min(minn, time11 + time21 - dp[i][j]);
if(time11 <= l1 && time22 <= l2) minn = min(minn, time11 + time22 - dp[i][j]);
if(time12 <= l1 && time21 <= l2) minn = min(minn, time12 + time21 - dp[i][j]);
if(time12 <= l1 && time22 <= l2) minn = min(minn, time12 + time22 - dp[i][j]);
}
}
printf("%d\n", es - minn);
return 0;
}
C - Remembering Strings (狀壓DP)☆
給了n個長度為m的僅有小寫字母組成的字串,要求把每一個字串變為好記的,定義為存在它的某一位字元在所有字串中那一位獨特,給定變換某個字串某一位的代價,不限制把它轉換成什麼小寫字母,問達到要求的最少的代價是多少()
由於n,m只有20,小於26。所以將其變換後,一定是滿足條件的,不會重複。那麼考慮
。
有兩種轉移方式,一種是把str[i][j]轉換掉,另一種是把第j列上和str[i][j]相同的x個字元中的x-1個轉換掉。然後預處理出這些需要的變數即可。複雜度。此時可以通過每次更新S中最低位0的方式,優化掉n的一層迴圈,因為dp[S]不需要從頭開始轉移,他直接從dp[first_zero_of_S]轉移過去即可,那麼和gym上有道掃雷的思路挺像的。因為對列的列舉,只會進行n次,所以幾乎可以看做是
#include <bits/stdc++.h>
using namespace std;
int dp[1 << 20 + 5];
char s[25][25];
int sameCost[25][25], same[25][25], a[25][25], n, m;
int main()
{
scanf("%d%d", &n, &m);
for(int i = 0; i < n; i++) scanf("%s", s[i]);
for(int i = 0; i < n; i++)
for(int j = 0; j < m; j++)
scanf("%d", &a[i][j]);
for(int i = 0; i < n; i++)
{
for(int j = 0; j < m; j++)
{
int mx = 0;
for(int k = 0; k < n; k++)
{
if(s[i][j] == s[k][j])
{
sameCost[i][j] += a[k][j];
mx = max(mx, a[k][j]);
same[i][j] |= (1 << k);//state[i][j]: 第j列上 所有和s[i][j]相同字母的 行的記錄。
}
}
sameCost[i][j] -= mx;//v[i][j]: 第j列上 所有和s[i][j]相同字母的 集體修改後使得s[i][j]在j列上唯一的
}
}
//dp[i][j] : 前i行 合法的行的狀態為j時 需要付出的最小花費。
memset(dp, 0x3f3f3f3f, sizeof(dp));
dp[0] = 0;
for(int S = 0; S < (1 <<n); S++)//列舉合法的行的狀態
{
for(int i = 0; i < n; i++)//列舉行。找到S最低位的0進行修改。
{
if((S & (1 << i)) == 0)// 當第i行並不合法時
{
for(int j = 0; j < m; j++)//列舉列
{
//將s[i][j]修改。
dp[S | (1 << i)] = min(dp[S | (1 << i)], dp[S] + a[i][j]);
//將和s[i][j]相同的修改
dp[S | same[i][j]] = min(dp[S | same[i][j]], dp[S] + sameCost[i][j]);
}
break;
}
}
}
printf("%d\n", dp[(1<<n) - 1]);
return 0;
}