【套题】qbxt国庆刷题班D1

时间:2024-01-18 09:33:32

Day1

事实上D1的题目还是比较简单的= =然而D1T2爆炸了就十分尴尬……错失一波键盘

看题

T1

传送门

Description

现在你手里有一个计算器,上面显示了一个数\(S\),这个计算器十分的牛逼,他只有两个按钮,分别可以把屏幕上显示的数加上\(1\)或者减去\(1\)。并且,如果计算器屏幕上的数变成了负数,那么计算器就会损坏。现在你想要在\(K\)次操作之内吧屏幕上的数字变成\(T\),而且不让计算器损坏,求一共有多少种方案。

两种方案不同当且仅当按钮被按下的序列不同

Input

一行三个整数\(S,T,K\)

Output

一行一个正整数,表示答案

Sample Input

0 1 3

Sample Output

3

Hint

\(For~All:\)

\(0~\leq~S,T,K~\leq~100000\)

\(For~30~percents:\)

\(S,T,K~\leq~10\)

\(For~60~percents:\)

\(S,T,K~\leq~1000\)

Solution

前30分枚举加和减

60分做法:DP。

设\(f_{i,j}\)为,第\(i\)次操作将计算器变成\(j\)的方案数。转移显然,枚举第\(i\)次加一还是减一。

\[f_{i,j}=f_{i-1,j-1}+f_{i-1}{j+1}$$。当$j=0$时不能从$j-1$转移。答案显然就是$\sum_{i=0}^{k}~f_{k,T}$,边界为$f_{0,S}=1$

满分做法:数学推导。

由于$S$到$T$的方案数严格等价于$T$到$S$的方案数,故不妨设$S~\leq~T$

考虑题目事实上等价于在一个平面直角坐标系中,有一个点$(S,0)$,求这个点到$(T,k)$的方案数。其中$0~\leq~k~\leq~K$

每次移动只能从$(x,y)$移动到$(x+1,y+1)$或$(x-1)(y+1)$。其中不允许越过$x=0$这条线。考虑这么做的方案数,等价于将坐标轴旋转$45°$,以原先的$(S,0)$为原点,不允许越过$y=x+S$这条直线,到达$(T-S,k)$的方案数。先考虑不存在不许越过直线的限制,那么方案数即为$C_{T-S+k}^{k}$。考虑不合法的方案。发现将不合法的方案在直线下方的部分关于直线对称后与他关于直线的对称点到目标的方案一一对应。于是显然另一个方案数可以求出。做差即为答案。

### Code

~~我懒得写了所以就把std放上来了~~

~~这std写的真丑~~

```cpp
#include <cstdio>
#include <cstring>
#include <iostream>
#include <algorithm>
using namespace std;

const int N = (int)2e5, mod = (int)1e9 + 7;
typedef long long ll;

int S, T, K;
int fac[N + 10], ifac[N + 10];

int pow(int x, int y) {
ll t = x, r = 1;
for ( ; y; y >>= 1, t = t * t % mod)
if (y & 1) r = r * t % mod;
return r;
}

void preprocessing() {
fac[0] = 1;
for (int i = 1; i <= N; ++i) fac[i] = (ll)fac[i - 1] * (ll)i % mod;
ifac[N] = pow(fac[N], mod - 2);
for (int i = N - 1; i >= 0; --i) ifac[i] = (ll)ifac[i + 1] * (ll)(i + 1) % mod;
}

int c(int n, int r) {
return (ll)fac[n] * (ll)ifac[r] % mod * (ll)ifac[n - r] % mod;
}

int main() {

preprocessing();
scanf("%d %d %d", &S, &T, &K);
if (S > T) swap(S, T);

int ans = 0;
for (int i = 0; i <= K; ++i) {
if ((i + T - S) & 1) continue;
int x = (i - T + S) >> 1, y = (i + T - S) >> 1;
if (x < 0) continue;
(ans += c(x + y, x)) %= mod;
if (x > S) (ans += (mod - c(x + y, x - S - 1))) %= mod;
}

printf("%d\n", ans);
return 0;
}
```

## T2

[传送门](https://www.luogu.org/problemnew/show/T50010)

### Description

有一个工厂一共有$n$个排成一排的机器,其中第$i$台机器的效率是$e_i$。

机器有开或关两种状态,显然当所有机器都开着时工作效率可以达到最大。但是由于工厂的供电系统出现了故障,不能够同时开启任意连续$k$台机器,否则工厂就会爆炸。

求工厂在不发生爆炸的前提下能达到的最大效率

### input

第一行两个整数$n$和$k$

接下来$n$行,每行一个整数代表$e_i$

### Output

一行一个数代表答案

### Sample Input

```
5 2
1
2
3
4
5
```

### Sample Output

```
12
```

### Hint

$For~All:$

$1~\leq~n,k~\leq~10^5~,~1~\leq~e_i~\leq~10^9$

$For~30~percents:$

$1~\leq~n~\leq~100$

### Solution

DP。

考虑设$f_i$为前$i$个的答案,其中一定不选第$i$个。显然可以通过枚举哪一个不选进行转移。$f_i=$$\max\{f_j+e_{j+1}+e_{j+2}+...+e_{i}\}$,其中满足$i-k~<~j~<~i$。$e_i$显然可以前缀和处理。另外发现因为$k$恒定,所以转移的左端点时单调不降的。于是可以单调队列把整个复杂度降低到$O(n)$。事实上使用线段树维护区间最大值也是可以的。

## Code

```cpp
#include<cstdio>
#define rg register
#define ci const int
#define cl const long long int

namespace IO {
char buf[110];
}

typedef long long int ll;

template <typename T>
inline void qr(T &x) {
char ch=getchar(),lst=' ';
while((ch > '9') || (ch < '0')) lst=ch,ch=getchar();
while((ch >= '0') && (ch <= '9')) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(lst == '-') x=-x;
}

template <typename T>
inline void write(T x,const char aft,const bool pt) {
if(x < 0) {putchar('-');x=-x;}
rg int top=0;
do {
IO::buf[++top]=x%10+'0';x/=10;
} while(x);
while(top) putchar(IO::buf[top--]);
if(pt) putchar(aft);
}

template <typename T>
inline T mmax(const T a,const T b) {return a > b ? a : b;}
template <typename T>
inline T mmin(const T a,const T b) {return a < b ? a : b;}
template <typename T>
inline T mabs(const T x) {return x < 0 ? -x : x;}

template <typename T>
inline void mswap(T &a,T &b) {
T temp=a;a=b;b=temp;
}

const int maxn = 100010;

int n,k,front,end;
ll frog[maxn],que[maxn],sum[maxn];

int main() {
qr(n);qr(k);
for(rg int i=1;i<=n;++i) {qr(sum[i]);sum[i]+=sum[i-1];}
front=end=1;
for(rg int i=1;i<=n;++i) {
if(i-que[front] > k) ++front;
while((front <= end) && ((frog[que[end]]-sum[que[end]]) <= (frog[i]-sum[i]))) --end;
que[++end]=i;
frog[i+1]=frog[que[front]]-sum[que[front]]+sum[i];
}
write(frog[n+1],'\n',true);
return 0;
}
```

## T3

[传送门](https://www.luogu.org/problemnew/show/T50011)

### Description

![qwq](https://cdn.luogu.org/upload/pic/35123.png )

### Input

![qwq](https://cdn.luogu.org/upload/pic/35124.png)

### Output

对于每组询问输出一行代表答案

### Sample Input

```
6
M 1 6
C 1
M 2 4
M 2 6
C 3
C 4
```

### Sample Output

```
1
0
2
```

### Solution

这不是没穿衣服的带权并查集嘛

### Code

```cpp
// i forgot to reset the seed of the rand
// maybe i will get zero pts
// upd: i ain't get WA for this task
// i feel very happy
#include<cstdio>
#define rg register
#define ci const int
#define cl const long long int

namespace IO {
char buf[110];
}

template <typename T>
inline void qr(T &x) {
char ch=getchar(),lst=' ';
while((ch > '9') || (ch < '0')) lst=ch,ch=getchar();
while((ch >= '0') && (ch <= '9')) x=(x<<1)+(x<<3)+(ch^48),ch=getchar();
if(lst == '-') x=-x;
}

template <typename T>
inline void write(T x,const char aft,const bool pt) {
if(x < 0) {putchar('-');x=-x;}
rg int top=0;
do {
IO::buf[++top]=x%10+'0';x/=10;
} while(x);
while(top) putchar(IO::buf[top--]);
if(pt) putchar(aft);
}

template <typename T>
inline T mmax(const T a,const T b) {return a > b ? a : b;}
template <typename T>
inline T mmin(const T a,const T b) {return a < b ? a : b;}
template <typename T>
inline T mabs(const T x) {return x < 0 ? -x : x;}

template <typename T>
inline void mswap(T &a,T &b) {
T temp=a;a=b;b=temp;
}

const int maxn = 30010;

int up[maxn],down[maxn],dist[maxn];

int findup(ci);
int finddown(ci);

int main() {
freopen("cubes.in","r",stdin);
freopen("cubes.out","w",stdout);
rg int m=0;qr(m);
for(rg int i=1;i<=maxn;++i) up[i]=i,down[i]=i,dist[i]=0;
rg int a,b;rg char ch;
while(m--) {
ch=getchar();
while((ch != 'M') && (ch != 'C')) ch=getchar();
if(ch == 'M') {
a=b=0;qr(a);qr(b);
a=finddown(a);b=findup(b);
down[a]=b;up[b]=a;dist[a]=1;
}
else {
a=0;qr(a);
finddown(a);
write(dist[a],'\n',true);
}
}
return 0;
}

int findup(ci x) { return up[x] != x ? up[x]=findup(up[x]) : x; }

int finddown(ci x) {
if(down[x] == x) return x;
int tk=finddown(down[x]);
dist[x]+=dist[down[x]];
return down[x]=tk;
}
```

## Summary

这套题大概是6天里面最简单的一套了……起码有260保底……然而竟然挂在了最拿手的DP上……

在写DP的时候发现有一部分转移无法优化时,可以考虑这一块转移是否是必须的,如果不是,可以直接删除该转移

一定记得srand!\]