第一届梦羽杯题目(后六题)浅析

G 爱女装的社长

题目大意:

n个位置排成一列,每个位置可以填1,2,3,要求不能有连续三个位置填相同的数,求方案数
\(n<=10^5\)

算法思路

看完题,似乎是个数学题?不太会
不急,观察数据范围,或许可以想一种稍微慢一点的办法

考虑DP
先谈一谈个人对DP的理解:DP,其实利用的是无后效性的特点,把对之前处理完的所有数据,进行一定的归纳,再集中处理。

比如在此题中,若是没有“要求不能有连续三个位置相同”的限制,答案是什么?
毫无疑问\(3^n\)!!!,其实在这个过程中就运用到了无后效性的特点:
每个位置都有三种选择,若是没有限制,则每个位置之间互不影响,所以可以对每个位置进行相似的计算

那么我们再回到原题目,现在有了限制.
若还是从前往后填那么每个位置填数时的选择就会被前两个数影响,即:前两个数产生了后效性。
如何消除后效性呢?
我们采取枚举+记录的办法即可,也是动态规划题的通用套路:把后效性的部分记为状态
观察到1,2,3三种数字其实是等价的,不妨设:\(f_{i,1/2}\) 表示第i个位置末尾连续的数字已经有 \(1 or 2\)个连续的相同数字
那么递推式有:

\[f_{i,1}=f_{i-1,1}*2+f_{i-1,2}*2 \]

\[f_{i,2}=f_{i-1,1} \]

代码

/*********************************************************************
    WRITER:YZHX
    DATE:2023.9.12
*********************************************************************/
#include <bits/stdc++.h>
using namespace std;
#define re register
#define in inline
#define ll long long
#define get getchar()
#define db double
in int read() {
    int t = 0, x = 1;
    char ch = get;
    while ((ch < '0' || ch > '9') && ch != '-')
        ch = get;
    if (ch == '-')
        ch = get, x = -1;
    while (ch <= '9' && ch >= '0')
        t = t * 10 + ch - '0', ch = get;
    return t * x;
}
const int mod = 1e9 + 7;
ll f[100100][3];
in void pre() {
    int n = 100000;
    f[1][1] = 3;
    for (re int i = 2; i <= n; ++i)
    {
        f[i][1] = (f[i - 1][1] * 2 % mod + f[i - 1][2] * 2 % mod) % mod;
        f[i][2] = f[i - 1][1];
    }
}
int main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    int T = read();
    pre();
    while (T--) {
        int n = read();
        cout << (f[n][1] + f[n][2]) % mod << endl;
    }
    return 0;
}


H女装社长的威胁

题目大意

给定一个n个元素的集合,可以进行k次操作,每次操作可以对一个元素 \(+1\) or \(-1\) (可以重复对一个数进行操作),之后要求 最大化 众数个数
\(n<=10^5, k<=10^{12}, a_i<=10^9\)

算法解析

思路一:
通过简单思考发现,最后的众数一定是原先存在的数,那么从小到大排序后,直接枚举最后的众数,再通过二分,找到左右能够变成选中数的最大边界。最后在n个答案中取最值即可

思路二:
明显可以得出答案与操作次数k是单调相关的,即操作次数越多,答案一定不会更小,那么考虑二分答案:
二分最后的答案,再进行check,是否能在k次操作内做到。
关于check:双指针+前缀和即可

/*********************************************************************
    WRITER:YZHX
    DATE:2023.9.12
*********************************************************************/
#include <bits/stdc++.h>
using namespace std;
#define re register
#define in inline
#define ll long long
#define get getchar()
#define db double
in int read() {
    int t = 0, x = 1;
    char ch = get;
    while ((ch < '0' || ch > '9') && ch != '-')
        ch = get;
    if (ch == '-')
        ch = get, x = -1;
    while (ch <= '9' && ch >= '0')
        t = t * 10 + ch - '0', ch = get;
    return t * x;
}
const int _ = 1e5 + 23;
ll n, k, a[_], b[_];
in int check(int mid) {
    int l, r, t;
    for (re int i = 1; i <= n - mid + 1; ++i) {
        l = i, r = i + mid - 1, t = (l + r) >> 1;
        ll w = a[t] * (t - l + 1) - (b[t] - b[l - 1]) + (b[r] - b[t]) - a[t] * (r - t);
        if (w < k)
            return 1;
    }
    return 0;
}
int main() {
    // freopen(".in","r",stdin);
    // freopen(".out","w",stdout);
    n = read(), k = read();
    for (re int i = 1; i <= n; ++i)
        a[i] = read();
    sort(a + 1, a + n + 1);
    for (re int i = 1; i <= n; ++i)
        b[i] = b[i - 1] + a[i];
    ll sum = 0, l = 1, r = n, ans = 0;
    while (l <= r) {
        int mid = (l + r) >> 1;
        if (check(mid))
            l = mid + 1, ans = mid;
        else
            r = mid - 1;
    }
    printf("%lld\n", ans);
    return 0;
}

I教你如何签到的签到题

01背包模板题,使用任意搜索引擎输入01背包点击回车键即可获取详解

J模拟跑酷

题目描述

现在有一条宽度为n的跑道,跑道上有如下几种地形:
1、普通跑道,用'.'表示,通过一个单位的普通跑道需要1s
2、上坡,用‘&’表示,通过一个单位的上坡跑道需要2s
3、下坡,用‘%’表示,通过一个单位的下坡跑道需要0.5s
4、障碍跑道,用‘$’表示,通过障碍跑道需要5s
你可以在每个单位长度跑道尽头处切换跑道,但是切换到相邻的跑道需要2s,可以连续切换跑道但是切换跑道时不能前进。
最开始你处在1号跑道的跑道开始处。
请计算通过这条跑道需要的最短时间。

输入格式

第一行一个整数,n
接下来n行,每行一个字符串代表第i道的跑道情况

输出格式

一个浮点数,代表跑到跑到尽头需要的时间(每一道跑道尽头都是跑道头)。保留两位小数

数据范围

n不大于5
跑道长度不大于10000

解题思路

先观察数据范围,发现n非常小,说明在考虑时间复杂度时n几乎可以忽略
直接猜出拿n作为DP中的一维状态
因为我们无法直接想出一个通解找到走线方案,不妨考虑动态规划。
我们在任何时候能对之后决策造成影响的只有我们所处的位置(横纵坐标),而与我们走过了什么障碍物并无关联。那么直接设\(f_{i,j}\)表示当前在第i个位置,第j条赛道,花费的最少的时间,接下来直接递推转移即可,相邻赛道的切换,多跑几次循环就可以啦,反正n是常数

(因为yzhx太饿了,代码被吃了奥)

K拔萝卜

题目描述

数轴上有n个机器,第i个机器位于 \(dis_i\) 每秒会产生 \(a_i\) 的开销,初始时你在第c个机器旁,你移动的速度是 \(1m/s\),走到一个机器旁可以瞬间关闭它,要求最小化关闭所有机器的代价
\(1<=c<=n<=100\) \(x_i,a_i<=10^9\)

解题思路

做题先看数据范围
n<100???这种数据通常是很暧昧的,既不会要求太高的数据结构和算法板子,也不会是暴力能过的,所以,继续考虑动态规划(DP)。
DP题最关键几点就是:
1.怎样记录状态
2.怎样通过已知状态合理转移 (考虑复杂度) 到未知状态
3.如何通过记录的状态获取答案

考虑第一个问题:
此题中,每个机器只有两个状态,打开和关闭,但是如何用尽量少的空间记录所有状态呢?
我们从c机器开始左右横跳时,因为关机器不需要时间,所以肯定路过的任何一台机器都会被顺手关闭。那么,被关闭的机器肯定是连续的,不妨记录已关闭的机器的区间是\([l,r]\)
当然,除了机器的状态,我们自身的位置在DP中也是至关重要的。
显然,在两个机器中间掉头换方向,绝对不是最优的,因此我们只可能在有机器的位置切换方向
状态已经呼之欲出啦:
\(f_{l,r,0/1}\)表示当前已关闭的机器区间是[l,r],我们自身的位置是l或者r(用0/1表示)。

第二个问题:如何转移状态呢
既然产生代价的机器区间是开头和结尾两段连续的机器,那么一次次求和不太现实,直接上前缀和
即: \(sum_i\) 表示 \(\sum_{i=1}^n a_i\)
然后转移式:

dp[i][j][1]=min( dp[i][j-1][0]+(dis[j]-dis[i])*(sum[n]+sum[i-1]-sum[j-1]),dp[i][j-1][1]+(dis[j]-dis[j-1])*(sum[n]+sum[i-1]-sum[j-1]) );
dp[i][j][0]=min( dp[i+1][j][0]+(dis[i+1]-dis[i])*(sum[n]+sum[i]-sum[j]),dp[i+1][j][1]+(dis[j]-dis[i])*((sum[n]+sum[i]-sum[j]) ) ) ;

最后一个问题:怎样计算答案
这点其实就无需多言了,直接在 \(dp_{i,j,0}\)\(dp_{i,j,1}\)中取min值就好

代码

/*********************************************************************
    WRITER:YZHX
    DATE:2023.9.12
*********************************************************************/
#include<bits/stdc++.h>
using namespace std;
#define re register
#define ll long long
#define in inline
#define get getchar()
in int read()
{
    int t=0; char ch=get;
    while(ch<'0' || ch>'9') ch=get;
    while(ch<='9' && ch>='0') t=t*10+ch-'0', ch=get;
    return t;
}
int n,c;
const int _=101;
ll sum[_],dp[_][_][2],dis[_],v[_];
int main()
{
    n=read(),c=read();
    for(re int i=1;i<=n;i++)
        dis[i]=read(),v[i]=read(),sum[i]=sum[i-1]+v[i];
    memset(dp,0x3f,sizeof(dp));
    dp[c][c][1]=0,dp[c][c][0]=0;
    for(re int j=c;j<=n;j++)
        for(re int i=j-1;i;i--)
        {
            dp[i][j][1]=min( dp[i][j-1][0]+(dis[j]-dis[i])*
			      (sum[n]+sum[i-1]-sum[j-1]),
               dp[i][j-1][1]+(dis[j]-dis[j-1])*(sum[n]+sum[i-1]-sum[j-1]) );
            dp[i][j][0]=min( dp[i+1][j][0]+(dis[i+1]-dis[i])*
			      (sum[n]+sum[i]-sum[j]),
               dp[i+1][j][1]+(dis[j]-dis[i])*((sum[n]+sum[i]-sum[j]) ) ) ;
        }
    cout<<min(dp[1][n][0],dp[1][n][1]);
}

L养一群龙吧

题目描述

自己去DPOJ官网找吧

题解

(没写不想动)
自己yy的,应该没问题,听嘉佑姐姐说有简单做法。。。但我不会/kk
先离散化
思路一: 用线段树模拟
思路二: 用莫队模拟
思路三: 暴力模拟+卡常

posted @ 2023-10-01 01:27  yzhx  阅读(15)  评论(0编辑  收藏  举报