DP
5196 Cow Poetry G
这个题,一开始做的时候一边写一边害怕要超时,但是最后竟然开了 $ O^2 $ 在最优解第三个,前面两个都是 $ unshown $ ,真是让我大吃一惊。
然后这题就是在一步步推的情况下一点点优化。第一步就是算出每一个长度所对应的种类有多少,然后就是算出来每一个韵律所对应的种类有多少,接下来就是记录一下有多少组需要押韵的,每组有多少个行。
最后就是计算啦!计算的时候用了一个快速幂,然后公式是显而易见的,所以就不说了。
然后要注意的是, $ res $ 要初始化,不然会寄。
贴点中心代码
for (int i = 1; i <= n; i ++){
len = fr(), a= fr();
dc[a].push_back(len);//记录每一个单词
c[len] ++;//每一个长度的数量
y.insert(a);
cd.insert(len);//记录长度
}
dp[0] = 1;
for (int i = 1; i <= k; i ++)
for(auto &len : cd){
if (len > i) break;
dp[i] = (dp[i] + c[len] * dp[i - len]) % mod;
//i的长度所对应的种类数
}
for (auto &a : y){
for (auto &len : dc[a]){
w[a] = (w[a] + dp[k - len]) % mod;
//a这个韵脚所对应的种类数
}
}
char str[2];
while (m --){
cin>>str;
cnt[str[0]-'A' + 1] ++;//这一组有多少行
}
ans = 1;
for (int i = 1; i <= 26; i ++){
if (!cnt[i]) continue;
int res = 0;//初始化!!!
for (auto &a : y){
res = (res + ksm(w[a],cnt[i])) % mod;
}
ans = (ans * res) % mod;
}
5202 Redistricting P
是一个紫题,但好像没有上面一个题难搞,感觉就是一个比较淳朴的dp然后再加一个单调队列优化,可能因为单调队列优化比较难?不太懂,建议和上面一题难度互换
思路就是先考虑前 $ i $ 个,然后再一个个往后考虑。因为在只考虑前 $ i $ 个的时候第 $ i $ 个一定是结尾,所以就很容易想到 $ dp $ 转移方程咯,然后根据 $ dp $ 方程就很容易得到这题可以单调队列优化
核心代码
tt++;
q[tt] = 0;
for (int i = 1; i <= n; i ++){
while (hh < tt && i - q[hh] > m)
hh ++;
dp[i] = dp[q[hh]] + check(q[hh],i);
while (hh < tt && (dp[i] < dp[q[tt]] || (dp[i] == dp[q[tt]] && sum[i] < sum[q[tt]]))){
tt --;
}
q[++ tt] = i;
}
5424 Snakes G
这个题和 $ ACwing $ 上面的股票买卖有点点像。
这一题可以转化为把这一段分为 $ k+1 $ 段,让每一段的贡献和最小。
然后这题我的做法是先预处理出每一段单独作为一段的时候,需要消耗的最小代价。显而易见,当这一段作为单独的一段的时候,用这一段中的最大值显然就是最优的策略
然后因为这题的数据范围也不大,所以直接 $ n^3 $ 跑一遍 $ dp $ 就可以了。
注意点
$ dp $ 里面转移的时候要注意标记的是起点还是终点,像是我这个代码标记的是终点的话那么下一次转移的时候就需要yong $ l - 1 $ ,要不然 $ l $ 这个点就会被用了两次
核心代码
for (int r = l; r <= n; r ++){
if(r)
h[l][r] = h[l][r - 1];
if(h[l][r].fi < w[r]){
h[l][r].se += (r - l) * (w[r] - h[l][r].fi);
h[l][r].fi = w[r];
} else {
h[l][r].se += h[l][r].fi - w[r];
}
}
}
memset(dp,0x3f,sizeof dp);
dp[0][0] = 0;
for (int i = 1; i <= n; i ++){
for (int j = 1; j <= k + 1; j ++){
for (int l = 1; l <= i; l ++)
//这里是l - 1,因为当区间l,i的时候,l这个点是算上的,所以要l - 1
if (dp[i][j] > dp[l - 1][j - 1] + h[l][i].se){
dp[i][j] = dp[l - 1][j - 1] + h[l][i].se;
}
}
}
6005 Time is Mooney G
一个图论题,不知道为什么是 $ dp $ 题,好像是题解里面是 $ dp $ ,但是我们几个都用的是图论 $ bfs $ 和 $ spfa $ 水过去的。
还没看题解的 $ dp $ 做法,也懒得看了,反正 $ spfa $ 还挺快。就是存 $ dis $ 和加到队列里面的时候存两个维度,一个是当前的点,然后第二个维度是到这个点的时候是第几天。然后存的时候是不需要算那个路上需要的价值的,因为对于同一天来说,需要的路上的代价都是相同的,所以就按照最长路存就可以啦!
最后因为要回到 $ 1 $ 点,所以再从第 $ 0 $ 天跑到第 $ 1000 $ 天(大概就是提供的贡献小于 $ 0 $ 的时候),然后取一个 $ max $ 。
最后就是由于他每到一次这个城市,就可以赚到钱,所以就可以直接把到这个点的边的权值设置为这个点的权值。
核心代码
void spfa(){
queue<pii> q;
dis[1][0] = 0;
q.push({1,0});
flag[1][0] = true;
while (q.size()){
auto t = q.front();
q.pop();
if (t.se > 1000) continue;
int u = t.fi,tim = t.se;
int dist = dis[u][tim];
for (auto &it :e[u]){
int v = it.v,w = it.w;
if (dis[v][tim + 1] < dist + w){
dis[v][tim + 1] = dist + w;
if (!flag[v][tim + 1]){
q.push({v,tim + 1});
flag[v][tim + 1] = true;
}
}
}
}
}
for (int i = 0; i <= 1000; i ++){
ans = max(ans,dis[1][i] - i * i * c);
}
6006 Farmer John Solves 3SUM G
这题是先预处理出两个固定端点的时候在中间寻找 $ k $ 有多少种方法,然后再计算出一个区间里面有多少种方法,最后再根据询问直接 $ O(1) $ 输出就可以了。
然后注意点
- 这题第一次预处理的时候不能新开一个数组,不然内存会爆掉
- 这一题不能用unordered_map,不然会被卡常卡掉,要用数组然后整体平移
- 这一题平移的时候还要判断一下这加了之后会不会超过定义的数组
核心代码
for (int i = 1; i <= n; i ++){
for (int j = i + 1; j <= n; j ++){
if (abs(0 - a[i] - a[j] + 1000001) < 2000005)
ans[i][j] = h[0 - a[i] - a[j] + 1000001];
h[a[j] + 1000001] ++;
}
for (int j = i + 1; j <= n; j ++){
h[a[j] + 1000001] --;
}
}
for (int i = n; i; -- i){
for (int j = i + 2; j <= n; j ++){
ans[i][j] += ans[i + 1][j] + ans[i][j - 1] - ans[i + 1][j - 1];
}
}
6146 Help Yourself G
这个题的意思就是说随意选取几条边,然后如果他们的区间有公共点,那么就没有多余的贡献,如果没有公共点的话,那么就算他多给了一个贡献
然后做法是先按照左端点排一个序,然后依次遍历,因为当有一条新的线段的时候,把这一条边和任何一个之前有过的线段集合相对应,就会有一个新的集合,然后新的集合里面如果这条线段和其中 $ i $ 条线段没有公共点的话(也就是说他的左端点小于那个点的右端点),那么这个边除了前一条边的时候的贡献,还有 $ 2^i $ 个贡献
然后快速找这条线段和其他多少条线段没有公共点的话,就用一个前缀和每个点的前面有多少个右端点,然后直接取用就可以了。
然后注意点就是:不要忘记改 $ N $ 的大小了!!!
核心代码
for (int i = 1; i <= n; i ++){
a = fr(), b = fr();
w[i] = {a, b};
cnt[b] ++ ;
}
for(int i = 1; i <= n * 2; i ++)
sum[i] = sum[i - 1] + cnt[i];
sort(w + 1, w + 1 + n);
lwl ans = 0;
for (int i = 1; i <= n; i ++){
ans = ((ans << 1) % mod + ksm(2,sum[w[i].l - 1])) % mod;
}
6150 Clock Tree S
是一道树形 $ dp $ 的题目,但是在题解里面有说这题是用染色法做的,还没有看,等会去看看。
$ dp $ 的话就是从每一个起点开始遍历,然后每一次遍历的时候用父亲来把儿子走成 $ 12 $ ,然后自己这个点再交给自己的父亲来改变。因为显而易见的是,当只在这一条走廊上面走的时候,父亲和儿子的相对差值是不变的,所以说可以直接加。然后在全部走完之后,看看当前遍历的起点的颜色是什么,如果是 $ 1 $ 或者 $ 0 $ 那就是可以的,因为 $ 1 $ 的话就可以直接停在自己的儿子节点不回来, $ 0 $ 的话就走回来。
核心代码
void dfs(int u,int fa){
for (auto v :e[u]){
if (v == fa) continue;
dfs(v,u);
w[u] = (w[u] + 12 - w[v]) % 12;
}
}
for (int i = 1; i <= n; i ++){
memcpy(w,tim,sizeof w);
dfs(i,-1);
if (w[i] == 0 || w[i] == 1)
ans ++;
}
7296 Uddered but not Herd G
状压 $ dp $ ,好久没有写状压 $ dp $ 了,上次写还是在上次。
这一题的意思是,要指定一个二十六个字母的顺序,然后让给定的字符串分成 $ k $ 个子序列,每一个子序列都可以对应到二十六个字母的顺序里面,让 $ k $ 最小。
然后先把字符串存到一个数组里面,然后 $ sort $ 一下,接着把这个数组排序一下,再去重。我感觉用 $ set $ 应该也可以?但是我 $ set $ 用的不是很熟,不知道能不能直接字符什么的,所以就用数组了。
然后用 $ b $ 数组存一下,接着用 $ sum $ 数组存一下顺序的相邻关系,最后再一个状压 $ dp $ ,先枚举最后一个数,然后再遍历一遍除了这个点以外其他在当前集合里面的点和这个点的逆序对有多少,用一个数去统计然后取一个 $ min $
- 注:因为这里要取 $ min $ ,所以要初始化为无穷大,要不然就没有办法更新咯
核心代码
for (int i = 1; i <= n ; i ++) {
a[i] = s[i] - 'a' + 1;
}
sort(a + 1,a + 1 + n);
int cnt = unique(a + 1,a + 1 + n) - (a + 1);
for (int i = 1; i <= n; i ++) {
b[i] = lower_bound(a + 1,a + cnt + 1,s[i] - 'a' + 1) - (a + 1);
}
for (int i = 1; i < n; i ++){
sum[b[i]][b[i + 1]] ++;
}
memset(dp,0x3f,sizeof dp);
dp[0] = 1;
for (int i = 1; i < (1 << cnt); i ++) {
for (int j = 0; j < cnt; j ++) {
if (i & 1 << j) {
int t = dp[i ^ 1 << j];
for (int k = 0; k < cnt; k ++) {
if (i & (1 << k))
t += sum[j][k];
}
dp[i] = min(dp[i],t);
}
}
}

浙公网安备 33010602011771号