Srm 654 250pt SquareScores
题意:定义一个字符串的分数为有多少个子串,且子串中每个字符都相差,比如aaaba,得分是8。给出一个字符串,由小写英文字母和'?'组成,对于'?',给出它填上每个英文字母的概率。问给出字符串的期望分数
思路:DP,设dp[i][j][k]为处理到字符串的第i位,有连续j位以k这个字符为结尾的概率。
如果第i + 1位的字符不是问号,如果这个字符为k,则dp[i + 1][j + 1][k] += dp[i][j][k];
否则 dp[i + 1][1][s[i + 1]] += dp[i][j][k];
如果第i + 1位的字符是问号,枚举这个问号填哪个字符,然后类似上面的转移
对于每次求出dp[i][j][k]后,ans += dp[i][j][k] * j,表示新增加的分数的期望
#include <bits/stdc++.h>
using namespace std;
const int N = 1010;
double dp[2][1010][26];
struct SquareScores {
double calcexpectation(vector <int> p, string s) {
double pt[26];
int n = s.size();
int m = p.size();
double ans = 1;
for(int i = 0; i < m; i ++) pt[i] = 1.0 * p[i] / 100;
if(s[0] >= 'a' && s[0] <= 'z') dp[1][1][s[0] - 'a'] = 1;
else {
for(int j = 0; j < m; j ++)
dp[1][1][j] = pt[j];
}
for(int i = 2; i <= n; i ++) {
int flag = i & 1;
memset(dp[flag],0,sizeof(dp[flag]));
if(s[i - 1] == '?') {
for(int j = 1; j <= i; j ++)
for(int k = 0; k < m; k ++)
if(j == 1) {
double sum = 1;
for(int jj = 1; jj <= i; jj ++)
sum -= dp[flag ^ 1][jj][k];
dp[flag][j][k] = sum * pt[k];
}
else dp[flag][j][k] += pt[k] * dp[flag ^ 1][j - 1][k];
}
else {
int idx = s[i - 1] - 'a';
for(int len = 1; len <= i; len ++) {
if(len == 1) {
for(int j = 1; j <= i; j ++)
for(int k = 0; k < m; k ++)
if(k != idx) dp[flag][len][idx] += dp[flag ^ 1][j][k];
}
else dp[flag][len][idx] += dp[flag ^ 1][len - 1][idx];
}
}
for(int j = 1; j <= i; j ++)
for(int k = 0; k < m; k ++)
ans += dp[flag][j][k] * j;
}
return ans;
}
};Srm 654 450pt SuccessiveSubtraction2
题意:给出一个序列a,长度为N <= 2000,有<=2000个修改,每次修改是将a[i]的值变为x,然后输出这个数组经过操作后的最大值。 所以操作值的是,初始数组的价值是a[1] - a[2] - a[3] - ... - a[N],你可以插入不超过2对括号,使得这个数组的价值最大。比如1 - 3 - 5 - 7 - 10,可以变成 1 - (3 + 5 + 7 + 10)。
思路: 对于每个修改,因为数据范围<=2000,可以直接O(N)求出数组的价值。观察可以看出,对于一个数,如果其前面的左括号 - 右括号的次数为奇数,则其变号为+,否则符号不变。因为dp[i][j][k]表示前i项,有j个左括号,k个右括号即可。(j <= 2,k <= 2,k <= j)
#include <bits/stdc++.h>
using namespace std;
const int N = 2010;
int dp[N][3][3];
void update(int &x,int y)
{
x = max(x,y);
}
struct SuccessiveSubtraction2 {
vector <int> calc(vector <int> a, vector <int> p, vector <int> v) {
vector<int> ret;
for(int pos = 0; pos < p.size(); pos ++) {
a[p[pos]] = v[pos];
int n = a.size();
for(int i = 0; i <= n; i ++)
for(int j = 0; j < 3; j ++)
for(int k = 0; k < 3; k ++)
dp[i][j][k] = -1000000;
dp[1][0][0] = a[0];
for(int i = 1; i < n; i ++)
for(int j = 0; j < 3; j ++)
for(int k = 0; k <= j; k ++) {
if(dp[i][j][k] == -1000000) continue;
int sign = (j - k) % 2 ? (-1) : 1;
update(dp[i + 1][j][k],dp[i][j][k] - sign * a[i]);
if(j > k) update(dp[i + 1][j][k + 1],dp[i][j][k] - sign * a[i]);
if(j > k + 1) update(dp[i + 1][j][k + 2],dp[i][j][k] - sign * a[i]);
if(j + 1 < 3) update(dp[i + 1][j + 1][k],dp[i][j][k] - sign * a[i]);
if(j + 2 < 3) update(dp[i + 1][j + 2][k],dp[i][j][k] - sign * a[i]);
}
int ans = -10000000;
for(int j = 0; j < 3; j ++)
for(int k = 0; k < 3; k ++)
if(j == k) ans = max(ans,dp[n][j][k]);
ret.push_back(ans);
}
return ret;
}
};Srm 654 850pt TwoEntrances
题意,给出N <= 3000个点的树,给出两个点s1,s2(s1 != s2),s1,s2是两个出入口,你可以从s1或s2运送货物到树上的每个点,你能运送货物到点x,当且仅当存在一条路径s1->x,或者s2->x,满足路径上每个点都是没有货物的。 问存在多少种合法的运货顺序 % (10^9 + 7).
思路:如果只有一个出入口s1,合法序列的方法数是很容易算的,s1比如安是最后的点。同理,用fu]表示以u为根的子树合法的运货顺序。则根节点是最后一个运货的。f假设v是f[u]的儿子节点,则f[u] *= f[v],且因为各儿子运货的顺序是随意的,所以f[u] *= combination(u的子树中还有多少位置没被用,v的子树大小)
两个出入口s1,s2的情况。假设s1->s2的路径为s1->a1->a2->..->an->s2。 首先两个端点s1,s2必然有一个是最后运货的,假设是s1,则a2->a2->..->an->s2是类似的问题。因此可以用dp[x][y]表示(x,y)这条路经上的两个端点,只能转移到dp[x + 1][y]和dp[x][y - 1],同时,对于路径上的一个点ai,运货必然是经过它的,所以可以看成是以它为根,一个点的运货问题,即上一段。所以要乘上 f[ai] * combination(空余位置,ai的后代数目)
#include <bits/stdc++.h>
using namespace std;
const int N = 3010;
const int mod = 1000000007;
int c[N][N],dp[N][N],pre[N],visit[N],sz[N],f[N];
vector<int> edges[N],path;
void init()
{
for(int i = 0; i < N; i ++) {
c[i][0] = c[i][i] = 1;
for(int j = 1; j < i; j ++) {
c[i][j] = (c[i - 1][j] + c[i - 1][j - 1]) % mod;
}
}
}
void dfs(int u,int fa)
{
pre[u] = fa;
for(int i = 0; i < edges[u].size(); i ++)
if(edges[u][i] != fa) dfs(edges[u][i],u);
}
void get_size(int u,int pre)
{
sz[u] = 1;
for(int i = 0; i < edges[u].size(); i ++)
if(edges[u][i] == pre || visit[edges[u][i]]) continue;
else {
get_size(edges[u][i],u);
sz[u] += sz[edges[u][i]];
}
}
void dfs1(int u,int pre)
{
f[u] = 1;
int tot = sz[u] - 1;
for(int i = 0; i < edges[u].size(); i ++) {
int x = edges[u][i];
if(x == pre || visit[x]) continue;
dfs1(x,u);
f[u] = 1LL * f[u] * c[tot][sz[x]] % mod * f[x] % mod;
tot -= sz[x];
}
}
int dfs2(int x,int y,int tot)
{
if(dp[x][y] != -1) return dp[x][y];
if(x == y) return dp[x][y] = f[path[x]];
int tmp = dfs2(x + 1,y,tot - sz[path[x]]);
dp[x][y] = 1LL * tmp * c[tot - 1][sz[path[x]] - 1] % mod * f[path[x]] % mod;
tmp = dfs2(x,y - 1,tot - sz[path[y]]);
dp[x][y] = (dp[x][y] + 1LL * tmp * c[tot - 1][sz[path[y]] - 1] % mod * f[path[y]]) % mod;
return dp[x][y];
}
struct TwoEntrances {
int count(vector <int> a, vector <int> b, int s1, int s2) {
int n = a.size() + 1;
for(int i = 0; i < n - 1; i ++) {
edges[a[i]].push_back(b[i]);
edges[b[i]].push_back(a[i]);
}
dfs(s1,-1);
init();
int x = s2;
while(x != -1) {
visit[x] = 1;
path.push_back(x);
x = pre[x];
}
reverse(path.begin(),path.end());
for(int i = 0; i < path.size(); i ++) get_size(path[i],-1);
for(int i = 0; i < path.size(); i ++) dfs1(path[i],-1);
memset(dp,-1,sizeof(dp));
return dfs2(0,path.size() - 1,n);
}
};
本文深入探讨了算法与数据结构的核心概念,包括排序算法、动态规划、哈希算法、贪心算法等,并通过实例详细解释了每种算法的原理与应用。同时,文章还覆盖了数据结构的基本类型,如二叉树、队列、栈、数组、链表、B树、散列表等,旨在帮助读者构建坚实的计算机科学基础。
529

被折叠的 条评论
为什么被折叠?



