首页 > 其他分享 >2022CSP-J题解

2022CSP-J题解

时间:2022-11-09 18:12:44浏览次数:53  
标签:ops int 题解 tr long times 2022CSP 表达式

2022CSP-J如期举行,<del>本人在封控区参加不了</del>,CCF收钱之后题目确实是变简单了,所以半场外人士写了一片题解,希望对各位大佬有帮助。

# T1-乘方

第一题往往没有**实现难度**。如果稍微有点难度的第一题,可能暴力60~80分,需要简单数学实现优化。但本题并没有思维难度。

注意点:
1.不开**long long**见祖宗。
2.**a=1**时要特判,否则会痛失10分。
3.不能使用pow,因为会爆范围。

**完整代码**

```cpp
#include<bits/stdc++.h>

using namespace std;

long long i,a,b,s=1; // 不开long long见祖宗
int main()
{
//freopen("pow.in","r",stdin);
//freopen("pow.out","w",stdout);
cin>>a>>b;
if(a==1) // a=1要特判,否则会超时
{
cout<<1;
return 0;
}
for(i=1;i<=b;i++) // 求幂过程
{
s*=a; // 累乘
if(s>1e9) // 超出1e9
{
cout<<-1;
return 0;
}
}
cout<<s; //不超出则输出结果
return 0;
}
```

# T2-解密

本题极度不符合CCF往年出题风格,两三行的题目让人怀疑自己是不是走错考场了。不过从简短的题干也能很容易清晰看出,本题是数学题。


## F0-暴力求解(60分)

相信大家应该都会吧,这里不啰嗦了。

## F1-一元二次方程求根公式

仔细观察一下题目给的两个式子:
$n_i = p_i \times q_i$、$e_i \times d_i = (p_i - 1)(q_i - 1) + 1$。
将第二个式子进行转换:
$e_i \times d_i = p_i \times q_i - p_i - q_i + 1 + 1$
$p_i + q_i = p_i \times q_i - e_i \times d_i + 2$
将第一个式子代入转换后的第二个式子:
$p_i + q_i = n - e_i \times d_i + 2$

等式太繁琐了,怎么办?
观察一下题目**数据范围**框的第一行:
以下记 $m = n - e \times d + 2$。
……
其实这是提示。
如果将 $m = n - e \times d + 2$再次代入第二个式子:
$p_i + q_i = m$

综上可得:
$$
\begin{cases} p_i + q_i = m \\
p_i \times q_i = n_i
\end{cases}
$$

再次代入:
$ p_i \times ( m - p_i ) = n_i $
$ p_i \times m - p_i ^ 2 = n_i $
$ p_i \times m - p_i ^ 2 - n_i= 0 $
$ p_i ^ 2 - p_i \times m + n_i= 0 $

而形如 $ax^2 + bx +c$ 一元二次方程的求根公式为:
$$
x_{1,2} = \frac{-b \pm \sqrt {b^2 - 4ac}}{2a}
$$

这里$ a = 1 $,$ b = -m $,$ c = n $
如果$ b ^ 2 - 4ac < 0 $,即$ m ^ 2 -4n < 0 $(一元二次方程判别式),则方程无解,输出```NO```即可。

**此外,还要判断一种无解情况:**
1.如果$ m ^ 2 - 4 * n $不是完全平方数,则解为无理数,不符合题意,输出```NO```。

**完整代码**

```cpp
#include <bits/stdc++.h>

using namespace std;

long long n,k,e,d,m,p,q; // 还是不开long long见祖宗
void solve()
{
m=n-e*d+2;
if(m*m-4*n<0) // 一元二次方程判别式
{
cout<<"NO\n";
return;
}
else
{
long long d=round(sqrt(m*m-4*n));
if(d*d!=m*m-4*n) // 如果开根号不是整数,则无解
{
cout<<"NO\n";
return;
}
p=(m-d)/2,q=m-p; // 一元二次方程求根公式求解
cout<<p<<' '<<q<<'\n';
}
}
int main()
{
//freopen("decode.in", "r", stdin);
//freopen("decode.out", "w", stdout);
cin>>k;
while(k--)
{
cin>>n>>d>>e;
solve();
}
return 0;
}
```

## F2-完全平方公式

F1是初二数学,那么没有学过一元二次方程的Oier怎么办呢?还有一种理解方式,那就是完全平方公式,虽说是初一数学,但大部分小学生应该也知道。

前面推导过程与F1相同,此处不赘述。
来到这一步:
$p_i + q_i = m$

再来看看两个完全平方公式:
平方和:$(a + b) ^ 2 = a ^ 2 + b ^ 2 + 2ab$
平方差:$(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab$
不难推导出:
$ a - b $
= $ \sqrt{a ^ 2 + b ^ 2 - 2ab} $
= $ \sqrt{a ^ 2 + b ^ 2 + 2ab -4ab} $
= $ \sqrt{(a + b) ^ 2 - 4ab} $

此处$a$为$p_i$,$b$为$q_i$,$a + b = m$,$ab = n$。
所以$ \vert{p_i - q_i}\vert = \sqrt{m ^ 2 - 4n}$。
因为题目中$ p_i \leqslant q_i $,所以$ q_i - p_i= \sqrt{m ^ 2 - 4n}$。

这时,求$p_i$和$q_i$就变成了三年级小学生也会的和差问题。
$ p_i = ( m - \sqrt{m ^ 2 - 4n} ) $ ,$ q_i = ( m + \sqrt{m ^ 2 - 4n} ) $

**注意几点:**
2.$ m ^ 2 - 4 * n $是个负数或不是完全平方数,则解为无理数,不符合题意,输出```NO```。
3.和差异奇偶,则为分数,不符合题意,输出```NO```。
4.若$ p_i $和$ q_i $中有负数,则不符合题意,输出```NO```。

**完整代码**

```cpp
#include<bits/stdc++.h>

using namespace std;

long long i,k,n,e,d,p,q;
void solve()
{
long long a=n+2-e*d,b=a*a-4*n;
if(a<0)cout<<"NO\n"; // 需要开平方的是个负数,舍
else if(sqrt(b*1.0)!=(long long)sqrt(b*1.0))cout<<"NO\n"; // 根式结果不是有理数,舍
else if(a%2!=b%2)cout<<"NO\n"; // 和差异奇偶,则为分数,舍
else
{
b=sqrt(b);
p=(a-b)/2,q=(a+b)/2; // 和差求解
if(p<0||q<0)cout<<"NO\n"; // 不能为负
else cout<<p<<' '<<q<<endl;
}
}
int main()
{
//freopen("decode.in","r",stdin);
//freopen("decode.out","w",stdout);
cin>>k;
while(k--)
{
cin>>n>>e>>d;
solve();
}
return 0;
}

```

## F3-二分查找
如果说什么公式都不知道,该怎么办呢?
那么,二分查找也是一种选择。

还是一样,前几步与F1、F2相同。直到推出:
$$
\begin{cases} p_i + q_i = m \\
p_i \times q_i = n_i \\
p_i \leqslant q_i
\end{cases}
$$
$ p_i \leqslant m / 2 $


由"和一定,差小积大",易得:$ p \times q $ 在 $ p\in [1,m/2]$上,单调递增,因此可以对$ p $进行二分查找。

**完整代码**

```cpp
#include <bits/stdc++.h>

using namespace std;

long long n,k,e,d,m,p,q;
void solve()
{
long long m = n - e*d +2;
long long l = 1, r = m/2, p, q; // p <= q 所以 p <= m/2
while(l<=r)
{
p=(l+r)/2;
q=m-p;
if(n==p*q)break; // 在 p 的值域范围内,p*q 是单调递增的
else if(n<p*q)r=p-1;
else l=p+1;
}
if(n==p*q)cout<<p<<' '<<q<<'\n';
else cout<<"NO\n";
}
int main()
{
//freopen("decode.in","r",stdin);
//freopen("decode.out","w",stdout);
cin>>k;
while(k--)
{
cin>>n>>d>>e;
solve();
}
return 0;
}

```

# T3-逻辑表达式
还是一样,CCF保留了出题传统——最难题放T3。

## Step1-中缀转后缀
首先看题目中给出的是中缀表达式。中缀表达式人看着舒服,但计算机读取很是难受。所以,想要解决问题,必须先要将中缀表达式转为后缀表达式。

显而易见,这里需要借助栈来实现中缀表达式到后缀表达式的转换。

相信大家备战J1时也知道了,中缀转后缀有如下规则:
1.如果是数字,直接入后缀表达式。
2.如果是'(',入运算符栈。
3.如果是')',运算符栈不断出栈到后缀表达式,直到碰到'(','('要出栈。
4.如果是运算符,运算符栈不断出栈到后缀表达式,直到碰到一个优先级更低的运算符或者栈为空。然后当前运算符入栈。
5.结束时,运算符栈可能不空,要不断出栈到后缀表达式。


```cpp
void change()
{
for(int i=0;i<s.size();i++)
{
if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下
else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈
else if(s[i]==')') // 是 )
{
while(!ops.empty()&&ops.top()!='(')
{
sf.push_back(ops.top()); // 一直输出,直到碰到左括号
ops.pop();
}
ops.pop(); // 弹出额外的 '('
}
else if(s[i] == '&') // 是 &
{
while(!ops.empty()&&ops.top()=='&')
{
sf.push_back(ops.top());
ops.pop();
}
ops.push('&');
}
else // 是 |
{
while(!ops.empty()&&ops.top() != '(')
{
sf.push_back(ops.top());
ops.pop();
}
ops.push('|');
}
}
while(!ops.empty())
{
sf.push_back(ops.top());
ops.pop();
}
}
```


## Step2-建表达式树

逐次读取后缀表达式的每一个符号。
如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中;
如果符号不是操作数,则从栈中弹出两棵树 T1 和 T2(先弹出 T1),并形成一颗以操作符为根的树,其中 T1 为右儿子,T2 为左儿子;
然后将新的树压入栈中,继续上述过程。

```cpp
void build()
{
for(int i=0;i<sf.size();i++)
{
if(sf[i]=='0'||sf[i] == '1')
{
tr[++num]={sf[i]-'0',-1,-1};
sta.push(num);
}
else
{
int r=sta.top();sta.pop();
int l=sta.top();sta.pop();
int v=(sf[i]=='&'?2:3);
tr[++num]={v,l,r};
sta.push(num);
}
}
}
```
## Step3-遍历表达式树求结果

DFS 遍历即可。
表达式树中,叶子一定是数值 0 或 1,非叶子一定是 & 或者 |。

DFS 过程:
遍历到叶子直接返回叶子的值;
遍历到非叶子时,先递归遍历左子树返回对应的子树的值。
然后基于左子树的返回值和当前结点的运算符判断是否会短路:
```1|``` 会发生“或短路”,并且返回 1;
```0&``` 会发生“与短路”,并且返回 0。
非上面两种情况,计算右子树的值并返回其结果即可:
```1&``` ,不管是0还是1,结果都是那个数;
```0&``` ,不管是0还是1,结果都是那个数。

```cpp
int dfs(int u)
{
if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点
int l=dfs(tr[u].l);
if(l==0&&tr[u].v == 2) // 0&
{
ans1++;
return 0;
}
if(l==1&&tr[u].v == 3) // 1|
{
ans2++;
return 1;
}
int r=dfs(tr[u].r);
return r; // 只要不短路,结果肯定就取决于右值 1& 0|
}
```


**完整代码**

```cpp
#include <bits/stdc++.h>
using namespace std;
const int N = 1e6+5;
string s;
struct Node
{
int v,l,r;
} tr[N];
int num, ans1, ans2;
stack<char> ops;
stack<int> sta;
vector<char> sf; // suffix 后缀表达式
void change()
{
for(int i=0;i<s.size();i++)
{
if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下
else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈
else if(s[i]==')') // 是 )
{
while(!ops.empty()&&ops.top()!='(')
{
sf.push_back(ops.top()); // 一直输出,直到碰到左括号
ops.pop();
}
ops.pop(); // 弹出额外的 '('
}
else if(s[i] == '&') // 是 &
{
while(!ops.empty()&&ops.top()=='&')
{
sf.push_back(ops.top());
ops.pop();
}
ops.push('&');
}
else // 是 |
{
while(!ops.empty()&&ops.top() != '(')
{
sf.push_back(ops.top());
ops.pop();
}
ops.push('|');
}
}
while(!ops.empty())
{
sf.push_back(ops.top());
ops.pop();
}
}
void build()
{
for(int i=0;i<sf.size();i++)
{
if(sf[i]=='0'||sf[i] == '1')
{
tr[++num]={sf[i]-'0',-1,-1};
sta.push(num);
}
else
{
int r=sta.top();sta.pop();
int l=sta.top();sta.pop();
int v=(sf[i]=='&'?2:3);
tr[++num]={v,l,r};
sta.push(num);
}
}
}
int dfs(int u)
{
if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点
int l=dfs(tr[u].l);
if(l==0&&tr[u].v == 2) // 0&
{
ans1++;
return 0;
}
if(l==1&&tr[u].v == 3) // 1|
{
ans2++;
return 1;
}
int r=dfs(tr[u].r);
return r; // 只要不短路,结果肯定就取决于右值 1& 0|
}
int main()
{
//freopen("expr.in", "r", stdin);
//freopen("expr.out", "w", stdout);
cin>>s;
change(); // 在构建表达式树前,需要把中缀表达式转后缀
build(); // 利用后缀表达式构建表达式树
cout<<dfs(num)<<'\n'; // 后缀表达式下,根在末尾,从根 dfs
cout<<ans1<<' '<<ans2;
return 0;
}
```

# T4-上升点列

仍然是一道不符合CSP风格的题目。所以说很容易看出,这是一道很模板的DP。

题目大意:从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,同时可以插入$ k $个任意位置的点,使得选择的点和差入的点相邻两个点之间距离为 1,求最大点的数量。

考虑一个更简单的问题:令$ k = 0 $,则问题变成了从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,相邻点之间距离为 1,求最大点数。

感觉有一种熟悉的味道?没错,就是最长上升子序列!此时容易想到对 n 个点排序,先按横坐标排序,再按纵坐标排序。如此,再对这 n 个点求最长上升子序列即可。

记$ f[i] $为以$ i $结尾的最长上升点序列最大长度,则有:
若$ {j\in[1,i-1]} $ ,则$ f[i] = \max ( f[i], f[j] + 1 ) $

此时,我们再考虑$ k > 0$,只需对上述状态增加一维,记$ f[i][p] $为以$ i $结尾,且已经插入了$ p $个额外点的最长上升点序列最大长度,则有:
$$
若 { j\in[1,i-1], p\in[d,k] } ,则 f[i][p] = \max ( f[i][p] , f[j][p-d] + d + 1 )\\d = a[i].x - a[j].x + a[i].y - a[j].y - 1
$$

**所以推得动态转移方程为:**

```cpp
int d=a[i].x-a[j].x+a[i].y-a[j].y-1;
......
f[i][p]=max(f[i][p],f[j][p-d]+d+1);
```

当然,别忘了初始化:
给定一个点i,可以在前面插入j个点,怎么插入上升点列最长?
当然是直接插入j个点!

```cpp
for(i=1;i<=n;i++)
{
for(j=0;j<=k;j++)f[i][j]=j+1;
}
```

**完整代码**

```cpp
#include <bits/stdc++.h>

using namespace std;
#define x first
#define y second
int i,j,p,n,k,f[505][105]; // f[i][j]: 前 i 个点,插入了 j 个点后最大长度
pair<int,int>a[505];
int main()
{
cin>>n>>k;
for(i=1;i<=n;i++)cin>>a[i].x>>a[i].y;
sort(a+1,a+n+1);
for(i=1;i<=n;i++)
{
for(j=0;j<=k;j++)f[i][j]=1+j; // 直接在 i 点前插入 j 个点
}
// 类似最长上升子序列
for(i=2;i<=n;i++)
{
for(j=i-1;j>=1;j--) // j -> i
{
if(a[j].y>a[i].y)continue;
// 从 j 到 i 要插入 d 个点才能满足
int d=a[i].x-a[j].x+a[i].y-a[j].y-1;
for(p=d;p<=k;p++)f[i][p]=max(f[i][p],f[j][p-d]+d+1);
}
}
int ans=0;
for(i=1;i<=n;i++)ans=max(ans,f[i][k]);
cout<<ans;
return 0;
}

```2022CSP-J如期举行,<del>本人在封控区参加不了</del>,CCF收钱之后题目确实是变简单了,所以半场外人士写了一片题解,希望对各位大佬有帮助。

 

# T1-乘方

 

第一题往往没有**实现难度**。如果稍微有点难度的第一题,可能暴力60~80分,需要简单数学实现优化。但本题并没有思维难度。

 

注意点:

1.不开**long long**见祖宗。

2.**a=1**时要特判,否则会痛失10分。

3.不能使用pow,因为会爆范围。

 

**完整代码**

 

```cpp

#include<bits/stdc++.h>

 

using namespace std;

 

long long i,a,b,s=1; // 不开long long见祖宗

int main()

{

//freopen("pow.in","r",stdin);

//freopen("pow.out","w",stdout);

cin>>a>>b;

if(a==1) // a=1要特判,否则会超时

{

cout<<1;

return 0;

}

for(i=1;i<=b;i++) // 求幂过程

{

s*=a; // 累乘

if(s>1e9) // 超出1e9

{

cout<<-1;

return 0;

}

}

cout<<s; //不超出则输出结果

return 0;

}

```

 

# T2-解密

 

本题极度不符合CCF往年出题风格,两三行的题目让人怀疑自己是不是走错考场了。不过从简短的题干也能很容易清晰看出,本题是数学题。

 

 

## F0-暴力求解(60分)

 

相信大家应该都会吧,这里不啰嗦了。

 

## F1-一元二次方程求根公式

 

仔细观察一下题目给的两个式子:

$n_i = p_i \times q_i$、$e_i \times d_i = (p_i - 1)(q_i - 1) + 1$。

将第二个式子进行转换:

$e_i \times d_i = p_i \times q_i - p_i - q_i + 1 + 1$

$p_i + q_i = p_i \times q_i - e_i \times d_i + 2$

将第一个式子代入转换后的第二个式子:

$p_i + q_i = n - e_i \times d_i + 2$

 

等式太繁琐了,怎么办?

观察一下题目**数据范围**框的第一行:

以下记 $m = n - e \times d + 2$。

……

其实这是提示。

如果将 $m = n - e \times d + 2$再次代入第二个式子:

$p_i + q_i = m$

 

综上可得:

$$

\begin{cases} p_i + q_i = m \\

p_i \times q_i = n_i

\end{cases}

$$

 

再次代入:

$ p_i \times ( m - p_i ) = n_i $

$ p_i \times m - p_i ^ 2 = n_i $

$ p_i \times m - p_i ^ 2 - n_i= 0 $

$ p_i ^ 2 - p_i \times m + n_i= 0 $

 

而形如 $ax^2 + bx +c$ 一元二次方程的求根公式为:

$$

x_{1,2} = \frac{-b \pm \sqrt {b^2 - 4ac}}{2a}

$$

 

这里$ a = 1 $,$ b = -m $,$ c = n $

如果$ b ^ 2 - 4ac < 0 $,即$ m ^ 2 -4n < 0 $(一元二次方程判别式),则方程无解,输出```NO```即可。

 

**此外,还要判断一种无解情况:**

1.如果$ m ^ 2 - 4 * n $不是完全平方数,则解为无理数,不符合题意,输出```NO```。

 

**完整代码**

 

```cpp

#include <bits/stdc++.h>

 

using namespace std;

 

long long n,k,e,d,m,p,q; // 还是不开long long见祖宗

void solve()

{

m=n-e*d+2;

if(m*m-4*n<0) // 一元二次方程判别式

{

cout<<"NO\n";

return;

}

else

{

long long d=round(sqrt(m*m-4*n));

if(d*d!=m*m-4*n) // 如果开根号不是整数,则无解

{

cout<<"NO\n";

return;

}

p=(m-d)/2,q=m-p; // 一元二次方程求根公式求解

cout<<p<<' '<<q<<'\n';

}

}

int main()

{

//freopen("decode.in", "r", stdin);

//freopen("decode.out", "w", stdout);

cin>>k;

while(k--)

{

cin>>n>>d>>e;

solve();

}

return 0;

}

```

 

## F2-完全平方公式

 

F1是初二数学,那么没有学过一元二次方程的Oier怎么办呢?还有一种理解方式,那就是完全平方公式,虽说是初一数学,但大部分小学生应该也知道。

 

前面推导过程与F1相同,此处不赘述。

来到这一步:

$p_i + q_i = m$

 

再来看看两个完全平方公式:

平方和:$(a + b) ^ 2 = a ^ 2 + b ^ 2 + 2ab$

平方差:$(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab$

不难推导出:

$ a - b $

= $ \sqrt{a ^ 2 + b ^ 2 - 2ab} $

= $ \sqrt{a ^ 2 + b ^ 2 + 2ab -4ab} $

= $ \sqrt{(a + b) ^ 2 - 4ab} $

 

此处$a$为$p_i$,$b$为$q_i$,$a + b = m$,$ab = n$。

所以$ \vert{p_i - q_i}\vert = \sqrt{m ^ 2 - 4n}$。

因为题目中$ p_i \leqslant q_i $,所以$ q_i - p_i= \sqrt{m ^ 2 - 4n}$。

 

这时,求$p_i$和$q_i$就变成了三年级小学生也会的和差问题。

$ p_i = ( m - \sqrt{m ^ 2 - 4n} ) $ ,$ q_i = ( m + \sqrt{m ^ 2 - 4n} ) $

 

**注意几点:**

2.$ m ^ 2 - 4 * n $是个负数或不是完全平方数,则解为无理数,不符合题意,输出```NO```。

3.和差异奇偶,则为分数,不符合题意,输出```NO```。

4.若$ p_i $和$ q_i $中有负数,则不符合题意,输出```NO```。

 

**完整代码**

 

```cpp

#include<bits/stdc++.h>

 

using namespace std;

 

long long i,k,n,e,d,p,q;

void solve()

{

long long a=n+2-e*d,b=a*a-4*n;

if(a<0)cout<<"NO\n"; // 需要开平方的是个负数,舍

else if(sqrt(b*1.0)!=(long long)sqrt(b*1.0))cout<<"NO\n"; // 根式结果不是有理数,舍

else if(a%2!=b%2)cout<<"NO\n"; // 和差异奇偶,则为分数,舍

else

{

b=sqrt(b);

p=(a-b)/2,q=(a+b)/2; // 和差求解

if(p<0||q<0)cout<<"NO\n"; // 不能为负

else cout<<p<<' '<<q<<endl;

}

}

int main()

{

//freopen("decode.in","r",stdin);

//freopen("decode.out","w",stdout);

cin>>k;

while(k--)

{

cin>>n>>e>>d;

solve();

}

return 0;

}

 

```

 

## F3-二分查找

如果说什么公式都不知道,该怎么办呢?

那么,二分查找也是一种选择。

 

还是一样,前几步与F1、F2相同。直到推出:

$$

\begin{cases} p_i + q_i = m \\

p_i \times q_i = n_i \\

p_i \leqslant q_i

\end{cases}

$$

$ p_i \leqslant m / 2 $

 

 

由"和一定,差小积大",易得:$ p \times q $ 在 $ p\in [1,m/2]$上,单调递增,因此可以对$ p $进行二分查找。

 

**完整代码**

 

```cpp

#include <bits/stdc++.h>

 

using namespace std;

 

long long n,k,e,d,m,p,q;

void solve()

{

long long m = n - e*d +2;

long long l = 1, r = m/2, p, q; // p <= q  所以 p <= m/2

while(l<=r)

{

p=(l+r)/2;

q=m-p;

if(n==p*q)break; // 在 p 的值域范围内,p*q 是单调递增的

else if(n<p*q)r=p-1;

else l=p+1;

}

if(n==p*q)cout<<p<<' '<<q<<'\n';

else cout<<"NO\n";

}

int main()

{

//freopen("decode.in","r",stdin);

//freopen("decode.out","w",stdout);

cin>>k;

while(k--)

{

cin>>n>>d>>e;

solve();

}

return 0;

}

 

```

 

# T3-逻辑表达式

还是一样,CCF保留了出题传统——最难题放T3。

 

## Step1-中缀转后缀

首先看题目中给出的是中缀表达式。中缀表达式人看着舒服,但计算机读取很是难受。所以,想要解决问题,必须先要将中缀表达式转为后缀表达式。

 

显而易见,这里需要借助栈来实现中缀表达式到后缀表达式的转换。

 

相信大家备战J1时也知道了,中缀转后缀有如下规则:

1.如果是数字,直接入后缀表达式。

2.如果是'(',入运算符栈。

3.如果是')',运算符栈不断出栈到后缀表达式,直到碰到'(','('要出栈。

4.如果是运算符,运算符栈不断出栈到后缀表达式,直到碰到一个优先级更低的运算符或者栈为空。然后当前运算符入栈。

5.结束时,运算符栈可能不空,要不断出栈到后缀表达式。

 

 

```cpp

void change()

{

for(int i=0;i<s.size();i++)

{

if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下

else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈

else if(s[i]==')') // 是 )

{

while(!ops.empty()&&ops.top()!='(')

{

sf.push_back(ops.top()); // 一直输出,直到碰到左括号

ops.pop();

}

ops.pop(); // 弹出额外的 '('

}

else if(s[i] == '&') // 是 &

{

while(!ops.empty()&&ops.top()=='&')

{

sf.push_back(ops.top());

ops.pop();

}

ops.push('&');

}

else // 是 |

{

while(!ops.empty()&&ops.top() != '(')

{

sf.push_back(ops.top());

ops.pop();

}

ops.push('|');

}

}

while(!ops.empty())

{

sf.push_back(ops.top());

ops.pop();

}

}

```

 

 

## Step2-建表达式树

 

逐次读取后缀表达式的每一个符号。

如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中;

如果符号不是操作数,则从栈中弹出两棵树 T1 和 T2(先弹出 T1),并形成一颗以操作符为根的树,其中 T1 为右儿子,T2 为左儿子;

然后将新的树压入栈中,继续上述过程。

 

```cpp

void build()

{

for(int i=0;i<sf.size();i++)

{

if(sf[i]=='0'||sf[i] == '1') 

{

tr[++num]={sf[i]-'0',-1,-1};

sta.push(num);

}

else

{

int r=sta.top();sta.pop();

int l=sta.top();sta.pop();

int v=(sf[i]=='&'?2:3);

tr[++num]={v,l,r};

sta.push(num);

}

}

}

```

## Step3-遍历表达式树求结果

 

DFS 遍历即可。

表达式树中,叶子一定是数值 0 或 1,非叶子一定是 & 或者 |。

 

DFS 过程:

遍历到叶子直接返回叶子的值;

遍历到非叶子时,先递归遍历左子树返回对应的子树的值。

然后基于左子树的返回值和当前结点的运算符判断是否会短路:

```1|``` 会发生“或短路”,并且返回 1;

```0&``` 会发生“与短路”,并且返回 0。

非上面两种情况,计算右子树的值并返回其结果即可:

```1&``` ,不管是0还是1,结果都是那个数;

```0&``` ,不管是0还是1,结果都是那个数。

 

```cpp

int dfs(int u)

{

if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点

int l=dfs(tr[u].l);

if(l==0&&tr[u].v == 2) // 0&

{

ans1++;

return 0;

}

if(l==1&&tr[u].v == 3) // 1|

{

ans2++;

return 1;

}

int r=dfs(tr[u].r); 

return r; // 只要不短路,结果肯定就取决于右值  1&  0|

}

```

 

 

**完整代码**

 

```cpp

#include <bits/stdc++.h>

using namespace std;

const int N = 1e6+5;

string s;

struct Node

{

    int v,l,r;

} tr[N];

int num, ans1, ans2;

stack<char> ops;

stack<int> sta;

vector<char> sf; // suffix 后缀表达式

void change()

{

for(int i=0;i<s.size();i++)

{

if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下

else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈

else if(s[i]==')') // 是 )

{

while(!ops.empty()&&ops.top()!='(')

{

sf.push_back(ops.top()); // 一直输出,直到碰到左括号

ops.pop();

}

ops.pop(); // 弹出额外的 '('

}

else if(s[i] == '&') // 是 &

{

while(!ops.empty()&&ops.top()=='&')

{

sf.push_back(ops.top());

ops.pop();

}

ops.push('&');

}

else // 是 |

{

while(!ops.empty()&&ops.top() != '(')

{

sf.push_back(ops.top());

ops.pop();

}

ops.push('|');

}

}

while(!ops.empty())

{

sf.push_back(ops.top());

ops.pop();

}

}

void build()

{

for(int i=0;i<sf.size();i++)

{

if(sf[i]=='0'||sf[i] == '1') 

{

tr[++num]={sf[i]-'0',-1,-1};

sta.push(num);

}

else

{

int r=sta.top();sta.pop();

int l=sta.top();sta.pop();

int v=(sf[i]=='&'?2:3);

tr[++num]={v,l,r};

sta.push(num);

}

}

}

int dfs(int u)

{

if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点

int l=dfs(tr[u].l);

if(l==0&&tr[u].v == 2) // 0&

{

ans1++;

return 0;

}

if(l==1&&tr[u].v == 3) // 1|

{

ans2++;

return 1;

}

int r=dfs(tr[u].r); 

return r; // 只要不短路,结果肯定就取决于右值  1&  0|

}

int main()

{

//freopen("expr.in", "r", stdin);

//freopen("expr.out", "w", stdout);

cin>>s;

change(); // 在构建表达式树前,需要把中缀表达式转后缀

build(); // 利用后缀表达式构建表达式树

cout<<dfs(num)<<'\n'; // 后缀表达式下,根在末尾,从根 dfs

cout<<ans1<<' '<<ans2;

return 0;

}

```

 

# T4-上升点列

 

仍然是一道不符合CSP风格的题目。所以说很容易看出,这是一道很模板的DP。

 

题目大意:从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,同时可以插入$ k $个任意位置的点,使得选择的点和差入的点相邻两个点之间距离为 1,求最大点的数量。

 

考虑一个更简单的问题:令$ k = 0 $,则问题变成了从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,相邻点之间距离为 1,求最大点数。

 

感觉有一种熟悉的味道?没错,就是最长上升子序列!此时容易想到对 n 个点排序,先按横坐标排序,再按纵坐标排序。如此,再对这 n 个点求最长上升子序列即可。

 

记$ f[i] $为以$ i $结尾的最长上升点序列最大长度,则有:

若$ {j\in[1,i-1]} $ ,则$ f[i] = \max ( f[i], f[j] + 1 ) $

 

此时,我们再考虑$ k > 0$,只需对上述状态增加一维,记$ f[i][p] $为以$ i $结尾,且已经插入了$ p $个额外点的最长上升点序列最大长度,则有:

$$

若 { j\in[1,i-1], p\in[d,k] } ,则 f[i][p] = \max ( f[i][p] , f[j][p-d] + d + 1 )\\d = a[i].x - a[j].x + a[i].y - a[j].y - 1 

$$

 

**所以推得动态转移方程为:**

 

```cpp

int d=a[i].x-a[j].x+a[i].y-a[j].y-1;

......

f[i][p]=max(f[i][p],f[j][p-d]+d+1);

```

 

当然,别忘了初始化:

给定一个点i,可以在前面插入j个点,怎么插入上升点列最长?

当然是直接插入j个点!

 

```cpp

for(i=1;i<=n;i++)

{

for(j=0;j<=k;j++)f[i][j]=j+1;

}

```

 

**完整代码**

 

```cpp

#include <bits/stdc++.h>

 

using namespace std;

#define x first

#define y second

int i,j,p,n,k,f[505][105]; // f[i][j]: 前 i 个点,插入了 j 个点后最大长度

pair<int,int>a[505];

int main()

{

cin>>n>>k;

for(i=1;i<=n;i++)cin>>a[i].x>>a[i].y;

sort(a+1,a+n+1);

for(i=1;i<=n;i++)

{

for(j=0;j<=k;j++)f[i][j]=1+j; // 直接在 i 点前插入 j 个点

}

// 类似最长上升子序列

for(i=2;i<=n;i++)

{

for(j=i-1;j>=1;j--) // j -> i

{

if(a[j].y>a[i].y)continue;

// 从 j 到 i 要插入 d 个点才能满足

int d=a[i].x-a[j].x+a[i].y-a[j].y-1;

for(p=d;p<=k;p++)f[i][p]=max(f[i][p],f[j][p-d]+d+1);

}

}

int ans=0;

for(i=1;i<=n;i++)ans=max(ans,f[i][k]);

cout<<ans;

return 0;

}

 

```

  2022CSP-J如期举行,<del>本人在封控区参加不了</del>,CCF收钱之后题目确实是变简单了,所以半场外人士写了一片题解,希望对各位大佬有帮助。
# T1-乘方
第一题往往没有**实现难度**。如果稍微有点难度的第一题,可能暴力60~80分,需要简单数学实现优化。但本题并没有思维难度。
注意点:1.不开**long long**见祖宗。2.**a=1**时要特判,否则会痛失10分。3.不能使用pow,因为会爆范围。
**完整代码**
```cpp#include<bits/stdc++.h>
using namespace std;
long long i,a,b,s=1; // 不开long long见祖宗int main(){//freopen("pow.in","r",stdin);//freopen("pow.out","w",stdout);cin>>a>>b;if(a==1) // a=1要特判,否则会超时{cout<<1;return 0;}for(i=1;i<=b;i++) // 求幂过程{s*=a; // 累乘if(s>1e9) // 超出1e9{cout<<-1;return 0;}}cout<<s; //不超出则输出结果return 0;}```
# T2-解密
本题极度不符合CCF往年出题风格,两三行的题目让人怀疑自己是不是走错考场了。不过从简短的题干也能很容易清晰看出,本题是数学题。

## F0-暴力求解(60分)
相信大家应该都会吧,这里不啰嗦了。
## F1-一元二次方程求根公式
仔细观察一下题目给的两个式子:$n_i = p_i \times q_i$、$e_i \times d_i = (p_i - 1)(q_i - 1) + 1$。将第二个式子进行转换:$e_i \times d_i = p_i \times q_i - p_i - q_i + 1 + 1$$p_i + q_i = p_i \times q_i - e_i \times d_i + 2$将第一个式子代入转换后的第二个式子:$p_i + q_i = n - e_i \times d_i + 2$
等式太繁琐了,怎么办?观察一下题目**数据范围**框的第一行:以下记 $m = n - e \times d + 2$。……其实这是提示。如果将 $m = n - e \times d + 2$再次代入第二个式子:$p_i + q_i = m$
综上可得:$$\begin{cases} p_i + q_i = m \\p_i \times q_i = n_i\end{cases}$$
再次代入:$ p_i \times ( m - p_i ) = n_i $$ p_i \times m - p_i ^ 2 = n_i $$ p_i \times m - p_i ^ 2 - n_i= 0 $$ p_i ^ 2 - p_i \times m + n_i= 0 $
而形如 $ax^2 + bx +c$ 一元二次方程的求根公式为:$$x_{1,2} = \frac{-b \pm \sqrt {b^2 - 4ac}}{2a}$$
这里$ a = 1 $,$ b = -m $,$ c = n $如果$ b ^ 2 - 4ac < 0 $,即$ m ^ 2 -4n < 0 $(一元二次方程判别式),则方程无解,输出```NO```即可。
**此外,还要判断一种无解情况:**1.如果$ m ^ 2 - 4 * n $不是完全平方数,则解为无理数,不符合题意,输出```NO```。
**完整代码**
```cpp#include <bits/stdc++.h>
using namespace std;
long long n,k,e,d,m,p,q; // 还是不开long long见祖宗void solve(){m=n-e*d+2;if(m*m-4*n<0) // 一元二次方程判别式{cout<<"NO\n";return;}else{long long d=round(sqrt(m*m-4*n));if(d*d!=m*m-4*n) // 如果开根号不是整数,则无解{cout<<"NO\n";return;}p=(m-d)/2,q=m-p; // 一元二次方程求根公式求解cout<<p<<' '<<q<<'\n';}}int main(){//freopen("decode.in", "r", stdin);//freopen("decode.out", "w", stdout);cin>>k;while(k--){cin>>n>>d>>e;solve();}return 0;}```
## F2-完全平方公式
F1是初二数学,那么没有学过一元二次方程的Oier怎么办呢?还有一种理解方式,那就是完全平方公式,虽说是初一数学,但大部分小学生应该也知道。
前面推导过程与F1相同,此处不赘述。来到这一步:$p_i + q_i = m$
再来看看两个完全平方公式:平方和:$(a + b) ^ 2 = a ^ 2 + b ^ 2 + 2ab$平方差:$(a - b) ^ 2 = a ^ 2 + b ^ 2 - 2ab$不难推导出:$ a - b $= $ \sqrt{a ^ 2 + b ^ 2 - 2ab} $= $ \sqrt{a ^ 2 + b ^ 2 + 2ab -4ab} $= $ \sqrt{(a + b) ^ 2 - 4ab} $
此处$a$为$p_i$,$b$为$q_i$,$a + b = m$,$ab = n$。所以$ \vert{p_i - q_i}\vert = \sqrt{m ^ 2 - 4n}$。因为题目中$ p_i \leqslant q_i $,所以$ q_i - p_i= \sqrt{m ^ 2 - 4n}$。
这时,求$p_i$和$q_i$就变成了三年级小学生也会的和差问题。$ p_i = ( m - \sqrt{m ^ 2 - 4n} ) $ ,$ q_i = ( m + \sqrt{m ^ 2 - 4n} ) $
**注意几点:**2.$ m ^ 2 - 4 * n $是个负数或不是完全平方数,则解为无理数,不符合题意,输出```NO```。3.和差异奇偶,则为分数,不符合题意,输出```NO```。4.若$ p_i $和$ q_i $中有负数,则不符合题意,输出```NO```。
**完整代码**
```cpp#include<bits/stdc++.h>
using namespace std;
long long i,k,n,e,d,p,q;void solve(){long long a=n+2-e*d,b=a*a-4*n;if(a<0)cout<<"NO\n"; // 需要开平方的是个负数,舍else if(sqrt(b*1.0)!=(long long)sqrt(b*1.0))cout<<"NO\n"; // 根式结果不是有理数,舍else if(a%2!=b%2)cout<<"NO\n"; // 和差异奇偶,则为分数,舍else{b=sqrt(b);p=(a-b)/2,q=(a+b)/2; // 和差求解if(p<0||q<0)cout<<"NO\n"; // 不能为负else cout<<p<<' '<<q<<endl;}}int main(){//freopen("decode.in","r",stdin);//freopen("decode.out","w",stdout);cin>>k;while(k--){cin>>n>>e>>d;solve();}return 0;}
```
## F3-二分查找如果说什么公式都不知道,该怎么办呢?那么,二分查找也是一种选择。
还是一样,前几步与F1、F2相同。直到推出:$$\begin{cases} p_i + q_i = m \\p_i \times q_i = n_i \\p_i \leqslant q_i\end{cases}$$$ p_i \leqslant m / 2 $

由"和一定,差小积大",易得:$ p \times q $ 在 $ p\in [1,m/2]$上,单调递增,因此可以对$ p $进行二分查找。
**完整代码**
```cpp#include <bits/stdc++.h>
using namespace std;
long long n,k,e,d,m,p,q;void solve(){long long m = n - e*d +2;long long l = 1, r = m/2, p, q; // p <= q  所以 p <= m/2while(l<=r){p=(l+r)/2;q=m-p;if(n==p*q)break; // 在 p 的值域范围内,p*q 是单调递增的else if(n<p*q)r=p-1;else l=p+1;}if(n==p*q)cout<<p<<' '<<q<<'\n';else cout<<"NO\n";}int main(){//freopen("decode.in","r",stdin);//freopen("decode.out","w",stdout);cin>>k;while(k--){cin>>n>>d>>e;solve();}return 0;}
```
# T3-逻辑表达式还是一样,CCF保留了出题传统——最难题放T3。
## Step1-中缀转后缀首先看题目中给出的是中缀表达式。中缀表达式人看着舒服,但计算机读取很是难受。所以,想要解决问题,必须先要将中缀表达式转为后缀表达式。
显而易见,这里需要借助栈来实现中缀表达式到后缀表达式的转换。
相信大家备战J1时也知道了,中缀转后缀有如下规则:1.如果是数字,直接入后缀表达式。2.如果是'(',入运算符栈。3.如果是')',运算符栈不断出栈到后缀表达式,直到碰到'(','('要出栈。4.如果是运算符,运算符栈不断出栈到后缀表达式,直到碰到一个优先级更低的运算符或者栈为空。然后当前运算符入栈。5.结束时,运算符栈可能不空,要不断出栈到后缀表达式。

```cppvoid change(){for(int i=0;i<s.size();i++){if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈else if(s[i]==')') // 是 ){while(!ops.empty()&&ops.top()!='('){sf.push_back(ops.top()); // 一直输出,直到碰到左括号ops.pop();}ops.pop(); // 弹出额外的 '('}else if(s[i] == '&') // 是 &{while(!ops.empty()&&ops.top()=='&'){sf.push_back(ops.top());ops.pop();}ops.push('&');}else // 是 |{while(!ops.empty()&&ops.top() != '('){sf.push_back(ops.top());ops.pop();}ops.push('|');}}while(!ops.empty()){sf.push_back(ops.top());ops.pop();}}```

## Step2-建表达式树
逐次读取后缀表达式的每一个符号。如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中;如果符号不是操作数,则从栈中弹出两棵树 T1 和 T2(先弹出 T1),并形成一颗以操作符为根的树,其中 T1 为右儿子,T2 为左儿子;然后将新的树压入栈中,继续上述过程。
```cppvoid build(){for(int i=0;i<sf.size();i++){if(sf[i]=='0'||sf[i] == '1') {tr[++num]={sf[i]-'0',-1,-1};sta.push(num);}else{int r=sta.top();sta.pop();int l=sta.top();sta.pop();int v=(sf[i]=='&'?2:3);tr[++num]={v,l,r};sta.push(num);}}}```## Step3-遍历表达式树求结果
DFS 遍历即可。表达式树中,叶子一定是数值 0 或 1,非叶子一定是 & 或者 |。
DFS 过程:遍历到叶子直接返回叶子的值;遍历到非叶子时,先递归遍历左子树返回对应的子树的值。然后基于左子树的返回值和当前结点的运算符判断是否会短路:```1|``` 会发生“或短路”,并且返回 1;```0&``` 会发生“与短路”,并且返回 0。非上面两种情况,计算右子树的值并返回其结果即可:```1&``` ,不管是0还是1,结果都是那个数;```0&``` ,不管是0还是1,结果都是那个数。
```cppint dfs(int u){if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点int l=dfs(tr[u].l);if(l==0&&tr[u].v == 2) // 0&{ans1++;return 0;}if(l==1&&tr[u].v == 3) // 1|{ans2++;return 1;}int r=dfs(tr[u].r); return r; // 只要不短路,结果肯定就取决于右值  1&  0|}```

**完整代码**
```cpp#include <bits/stdc++.h>using namespace std;const int N = 1e6+5;string s;struct Node{    int v,l,r;} tr[N];int num, ans1, ans2;stack<char> ops;stack<int> sta;vector<char> sf; // suffix 后缀表达式void change(){for(int i=0;i<s.size();i++){if(s[i]=='0'||s[i]=='1')sf.push_back(s[i]); // 数字直接写下else if(s[i]=='(')ops.push(s[i]); // 是 (,直接入运算符栈else if(s[i]==')') // 是 ){while(!ops.empty()&&ops.top()!='('){sf.push_back(ops.top()); // 一直输出,直到碰到左括号ops.pop();}ops.pop(); // 弹出额外的 '('}else if(s[i] == '&') // 是 &{while(!ops.empty()&&ops.top()=='&'){sf.push_back(ops.top());ops.pop();}ops.push('&');}else // 是 |{while(!ops.empty()&&ops.top() != '('){sf.push_back(ops.top());ops.pop();}ops.push('|');}}while(!ops.empty()){sf.push_back(ops.top());ops.pop();}}void build(){for(int i=0;i<sf.size();i++){if(sf[i]=='0'||sf[i] == '1') {tr[++num]={sf[i]-'0',-1,-1};sta.push(num);}else{int r=sta.top();sta.pop();int l=sta.top();sta.pop();int v=(sf[i]=='&'?2:3);tr[++num]={v,l,r};sta.push(num);}}}int dfs(int u){if(tr[u].v==0||tr[u].v==1)return tr[u].v; // 是叶子(数字)结点int l=dfs(tr[u].l);if(l==0&&tr[u].v == 2) // 0&{ans1++;return 0;}if(l==1&&tr[u].v == 3) // 1|{ans2++;return 1;}int r=dfs(tr[u].r); return r; // 只要不短路,结果肯定就取决于右值  1&  0|}int main(){//freopen("expr.in", "r", stdin);//freopen("expr.out", "w", stdout);cin>>s;change(); // 在构建表达式树前,需要把中缀表达式转后缀build(); // 利用后缀表达式构建表达式树cout<<dfs(num)<<'\n'; // 后缀表达式下,根在末尾,从根 dfscout<<ans1<<' '<<ans2;return 0;}```
# T4-上升点列
仍然是一道不符合CSP风格的题目。所以说很容易看出,这是一道很模板的DP。
题目大意:从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,同时可以插入$ k $个任意位置的点,使得选择的点和差入的点相邻两个点之间距离为 1,求最大点的数量。
考虑一个更简单的问题:令$ k = 0 $,则问题变成了从$ n $个坐标中选择若干个,使得横坐标不减,且纵坐标不减,相邻点之间距离为 1,求最大点数。
感觉有一种熟悉的味道?没错,就是最长上升子序列!此时容易想到对 n 个点排序,先按横坐标排序,再按纵坐标排序。如此,再对这 n 个点求最长上升子序列即可。
记$ f[i] $为以$ i $结尾的最长上升点序列最大长度,则有:若$ {j\in[1,i-1]} $ ,则$ f[i] = \max ( f[i], f[j] + 1 ) $
此时,我们再考虑$ k > 0$,只需对上述状态增加一维,记$ f[i][p] $为以$ i $结尾,且已经插入了$ p $个额外点的最长上升点序列最大长度,则有:$$若 { j\in[1,i-1], p\in[d,k] } ,则 f[i][p] = \max ( f[i][p] , f[j][p-d] + d + 1 )\\d = a[i].x - a[j].x + a[i].y - a[j].y - 1 $$
**所以推得动态转移方程为:**
```cppint d=a[i].x-a[j].x+a[i].y-a[j].y-1;......f[i][p]=max(f[i][p],f[j][p-d]+d+1);```
当然,别忘了初始化:给定一个点i,可以在前面插入j个点,怎么插入上升点列最长?当然是直接插入j个点!
```cppfor(i=1;i<=n;i++){for(j=0;j<=k;j++)f[i][j]=j+1;}```
**完整代码**
```cpp#include <bits/stdc++.h>
using namespace std;#define x first#define y secondint i,j,p,n,k,f[505][105]; // f[i][j]: 前 i 个点,插入了 j 个点后最大长度pair<int,int>a[505];int main(){cin>>n>>k;for(i=1;i<=n;i++)cin>>a[i].x>>a[i].y;sort(a+1,a+n+1);for(i=1;i<=n;i++){for(j=0;j<=k;j++)f[i][j]=1+j; // 直接在 i 点前插入 j 个点}// 类似最长上升子序列for(i=2;i<=n;i++){for(j=i-1;j>=1;j--) // j -> i{if(a[j].y>a[i].y)continue;// 从 j 到 i 要插入 d 个点才能满足int d=a[i].x-a[j].x+a[i].y-a[j].y-1;for(p=d;p<=k;p++)f[i][p]=max(f[i][p],f[j][p-d]+d+1);}}int ans=0;for(i=1;i<=n;i++)ans=max(ans,f[i][k]);cout<<ans;return 0;}
```  

标签:ops,int,题解,tr,long,times,2022CSP,表达式
From: https://www.cnblogs.com/WiFi-dreamer/p/16874719.html

相关文章

  • 二分查找与二分答案题解
    此类题目的特征为数据量大,数据为升序或降序根本目的是通过二分法快速缩小答案范围,然后对比数据或验证答案2.1二分查找输出时注意mid是否为第一个出现的答案1#incl......
  • CF1285D Dr. Evil Underscores 题解
    给定一个序列\(a\),选取一个\(x\),使\(\max_{i=1}^na_i\oplusx\)最小。看到这种题直接按位考虑,如果最高位全是\(1\)那把\(x\)的这位全变成\(1\),如果最高位全是\(......
  • 洛谷P1605题解分析
    迷宫题目描述给定一个\(N\timesM\)方格的迷宫,迷宫里有\(T\)处障碍,障碍处不可通过。在迷宫中移动有上下左右四种方式,每次只能移动一个方格。数据保证起点上没有障......
  • CF1747 题解
    比赛链接:https://codeforces.com/contest/1747题解:AB水题//bySkyRainWind#include<cstdio>#include<vector>#include<cstring>#include<iostream>#include......
  • P1688 新单词接龙问题 题解
    大家好,我喜欢Hash,所以我用Hash通过了这道题。思路:处理方法有点类似于P6521首先我们最终接出的字符串要求其中的小字符串按字典序排序,那么我们就将所有字符串排好序......
  • 问题解决-白鹭引擎Egret Wing3修改项目内容后进行调试,项目仍显示修改前样式
    问题描述:修改前: 修改后: 解决方法:点击上方菜单栏项目-清理,进行项目清理    点击菜单栏项目-调试  重新进行调试。  调试成功后,问题成功解......
  • 问题解决-白鹭引擎Egret Wing3调试出错,输出台显示乱码而且编译终止问题Failed to load
    问题描述:Failedtoloadresource:theserverrespondedwithastatusof404()未能加载资源:服务器响应,状态为404() 解决方法:卸载引擎并重新安装,安装时使用默认路径(C......
  • maven打包失败,Could not resolve dependencies for project...Could not find artifac
    一、问题原因:在阿里私服或者Maven的中央仓库找不到Pom文件引入的Jar,也就是说,你想引的依赖Jar包根本没有成功加载到项目中。二、分析2.1这个包可能处于隐患或者其他原因,原作......
  • CF1750题解
    A.IndirectSort首先,如果\(a_i>a_k\),\(a_i\)就会变大,而这样的操作是没有意义的.因此操作可以转换为\(\forallj,k(j<k)\),如果\(\existsi,i<j\),使得\(a[i]<=a[k]\),......
  • [CSP-S 2022]数据传输 题解
    提示:我这个方法比其它解法还要麻烦,目前最简洁的解法是dottle的解法,推荐阅读,我仅在此说明我的思路。给定\(n\)个点的树,点\(i\)的点权为\(a_i\),给定整数\(k\)。称......