题目描述
小文同学刚刚接触了信息学竞赛,有一天她遇到了这样一个题:给定正整数 a 和 b,求 a^b 的值是多少。a^b 即 b 个 a 相乘的值,例如 2^3 即为 3 个 2 相乘,结果为 2 × 2 × 2 = 8。
“简单!”小文心想,同时很快就写出了一份程序,可是测试时却出现了错误。小文很快意识到,她的程序里的变量都是 int 类型的。在大多数机器上,int 类型能表示的最大数为 2^31 − 1 ,因此只要计算结果超过这个数,她的程序就会出现错误。由于小文刚刚学会编程,她担心使用 int 计算会出现问题。因此她希望你在 a^b 的值超过 10^9 时,输出一个 ‐1 进行警示,否则就输出正确的 a^b 的值。然而小文还是不知道怎么实现这份程序,因此她想请你帮忙。
输入格式
从文件 pow.in 中读入数据。
输入共一行,两个正整数 a, b 。
输出格式
输出到文件 pow.out 中。
输出共一行,如果 a^b 的值不超过 10^9 ,则输出 a^b 的值,否则输出 ‐1 。
输入输出样例
10 9
1000000000
23333 66666
-1
说明
【数据范围】
对于 10% 的数据,保证 b = 1。
对于 30% 的数据,保证 b ≤ 2。
对于 60% 的数据,保证 b ≤ 30,a^b ≤ 10^18。
对于 100% 的数据,保证 1 ≤ a, b ≤ 10^9。
耗时限制1000ms 内存限制128MB
#include
using namespace std;
const int INF = 1e9;
//ans:标记 a 的 b 次方的结果
long long a,b,ans = 1;
int main(){
cin>>a>>b;
for(int i = 1;i <= b;i++){
ans = ans * a;
if(ans > INF){
cout<<-1;
return 0;
}
}
cout<
题目描述
给定一个正整数 k,有 k 次询问,每次给定三个正整数 ni, ei, di,求两个正整数 pi, qi, 使 ni = pi × qi, ei × di = (pi − 1)(qi − 1) + 1。
输入格式
从文件 decode.in 中读入数据。
第一行一个正整数 k,表示有 k 次询问。
接下来 k 行,第 i 行三个正整数 ni, di, ei。
输出格式
输出到文件 decode.out 中。
输出 k 行,每行两个正整数 pi, qi 表示答案。为使输出统一,你应当保证 pi ≤ qi。
如果无解,请输出 NO。
输入输出样例
10
770 77 5
633 1 211
545 1 499
683 3 227
858 3 257
723 37 13
572 26 11
867 17 17
829 3 263
528 4 109
2 385
NO
NO
NO
11 78
3 241
2 286
NO
NO
6 88
说明
耗时限制1000ms 内存限制128MB
1.数学做法
分析:
∵ n = p * q,e * d = (p-1)(q-1) + 1
∴ e * d = pq – (p+q) + 2
∴ p + q = pq – ed + 2 = n – ed + 2
设 m = n – ed + 2,则有:
p * q = n
p + q = m
那么本题就是分别解出 p 的值。
∵ (p + q)^2 = m^2
∴ p^2 + 2pq + q^2 = m^2
∴ p^2 - 2pq + q^2 + 4pq = m^2
∴ (p-q)2 + 4pq = m2
由于: pq = n
∴ (p-q)^2 = m^2 – 4n
∴ p-q = ±sqrt(m^2 – 4n)
由于正负号仅影响最终 pq 的顺序,因此可以认为: p-q = sqrt(m2 – 4n) 结合: p + q = m
可以得出: p = (sqrt(m^2 – 4n)+m) / 2
q = m - p。
由于 pq 都是正整数,因此只需要再检验 sqrt(m^2 – 4n)是否为整数,就可以确认是否存在 整数解。
参考代码:
#include
using namespace std;
/*
给定 n,e,d,求出 pq,使得 pq 满足:
pq = n
(p-1)(q-1)+1=ed
*/
long long m,n,k,p,q,e,d,t;
int main(){
scanf("%lld",&k);
while(k--){
scanf("%lld%lld%lld",&n,&e,&d);
m = n - e * d + 2;
t = sqrt(m*m-4*n);
//如果有解,sqrt()的结果一定是整数
if(t * t == m * m - 4 * n){
p = (t + m) / 2;
q = m - p;
printf("%lld %lld\n",min(p,q),max(p,q));
}else{
puts("NO");
}
}
return 0;
}
二分做法:
p≤m/2
分析,易得:p∗q 在 p∈[1,m/2] 上,单调递增,因此可以对p 进行二分查找。
参考代码
#include
using namespace std;
long long n, k, e, d, m, p, q;
int main(){
cin >> k;
while(k--){
cin >> n >> d >> e;
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) { // 在 p 的值域范围内,p*q 是单调递增的
break;
} else if(n < p*q) {
r = p - 1;
} else {
l = p + 1;
}
}
if(n == p*q) {
cout << p << ' ' << q << '\n';
} else {
cout << "NO\n";
}
}
return 0;
}
题目描述
逻辑表达式是计算机科学中的重要概念和工具,包含逻辑值、逻辑运算、逻辑运算优先级等内容。在一个逻辑表达式中,元素的值只有两种可能:0 (表示假)和 1 (表示真)。元素之间有多种可能的逻辑运算,本题中只需考虑如下两种:“与”(符号为&)和“或”(符号为|)。其运算规则如下:0&0 = 0&1 = 1&0 = 0,1&1 = 1; 0|0 = 0,0|1 = 1|0 = 1|1 = 1。
在一个逻辑表达式中还可能有括号。规定在运算时,括号内的部分先运算;两种运算并列时,& 运算优先于 | 运算;同种运算并列时,从左向右运算。
比如,表达式 0|1&0 的运算顺序等同于0|(1&0) ;表达式 0&1&0|1 的运算顺序等同于 ((0&1)&0)|1。
此外,在 C++ 等语言的有些编译器中,对逻辑表达式的计算会采用一种“短路”的策略。:在形如 a&b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 0 ,那么整个逻辑表达式的值就一定为 0,故无需再计算 b 部分的值;同理,在形如 a|b 的逻辑表达式中,会先计算 a 部分的值,如果 a = 1 ,那么整个逻辑表达式的值就一定为 1,无需再计算 b 部分的值。
现在给你一个逻辑表达式,你需要计算出它的值,并且统计出在计算过程中,两种类型的“短路”各出现了多少次。需要注意的是,如果某处“短路”包含在更外层被“短路”的部分内则不被统计,如表达式 1|(0&1) 中,尽管 0&1 是一处“短路”,但由于外层的 1|(0&1) 本身就是一处“短路”,无需再计算 0&1 部分的值,因此不应当把这里的0&1 计入一处“短路”。
输入格式
从文件 expr.in 中读入数据。
输入共一行,一个非空字符串 s 表示待计算的逻辑表达式。
输出格式
输出到文件 expr.out 中。
输出共两行,第一行输出一个字符 0 或 1 ,表示这个逻辑表达式的值;
第二行输出两个非负整数,分别表示计算上述逻辑表达式的过程中,形如 a&b 和 a|b 的“短路”各出现了多少次。
输入输出样例
0&(1|0)|(1|1|1&0)
1
1 2
(0|1&0|1|1|(1|1))&(0&1&(1|0)|0|1|0)&0
输出样例2:
0
2 3
说明
耗时限制1000ms 内存限制128MB
思路:
计算机来说,必须构建出表达式树,才可以进行计算。而输入是中缀表达式,因此,需要先把中缀表达式转后缀表达式,然后基于后缀表达式构建出表达式树,最后进行计算。
1. 中缀转后缀
需要借助栈来实现从中缀表达式到后缀表达式的转换。
这里明确一下使用栈转换的算法思想:
从左到右开始扫描中缀表达式,遇到数字, 直接输出
遇到运算符时:
a. 若为“(” 直接入栈
b. 若为“)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出
c. 若为其他符号, 将符号栈中的元素依次出栈并输出, 直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。
扫描完后, 将栈中剩余符号依次输出。
2. 构建表达式树
逐次读取后缀表达式的每一个符号
如果符号是操作数,那么我们就建立一个单节点树并将一个指向它的指针推入栈中;
如果符号是操作数,则从栈中弹出两棵树 T1 和 T2(先弹出 T1),并形成一颗以操作符为根的树,其中 T1 为右儿子,T2 为左儿子;
然后将新的树压入栈中,继续上述过程。
3. 计算表达式值
DFS 遍历即可。表达式树中,叶子一定是数值 00 或 11,非叶子一定是 &
或者 |
。DFS 过程:
1|
会发生“或短路”,并且返回 110&
会发生“与短路”,并且返回 00#include
using namespace std;
const int N = 1e6+5;
string s;
struct Node {
int v, l, r;
} tr[N];
int num, ans1, ans2;
stack ops;
stack sta;
vector sf; // suffix 后缀表达式
/* 中缀转后缀:
从左到右开始扫描中缀表达式
遇到数字, 直接输出
遇到运算符
a.若为“(” 直接入栈
b.若为“)” 将符号栈中的元素依次出栈并输出, 直到 “(“, “(“只出栈, 不输出
c.若为其他符号, 将符号栈中的元素依次出栈并输出, 直到遇到比当前符号优先级更低的符号或者”(“。 将当前符号入栈。
扫描完后, 将栈中剩余符号依次输出
*/
void in2sf() {
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(){
cin >> s;
in2sf(); // 在构建表达式树前,需要把中缀表达式转后缀
build(); // 利用后缀表达式构建表达式树
cout << dfs(num) << '\n'; // 后缀表达式下,根在末尾,从根 dfs
cout << ans1 << ' ' << ans2;
return 0;
}
参考代码2
#include
using namespace std;
const int N = 1e6 + 10;
struct node
{
char c;//存储结点的运算符 & |
int l,r;//左右孩子的编号
int v;//结点的值
} a[N];
stack op;//存储运算符,辅助中缀转后缀的过程
stack st;//存储运算数,辅助后缀表达式计算的过程
char s[N];//存储中缀表达式
vector v;//存储后缀表达式
int k = 0;//表示结点编号
int c1,c2;//&短路的次数, |短路的次数
//搜索求出短路的次数
void dfs(int x){
if(a[x].l == 0 && a[x].r == 0) return;//叶子结点
int t1 = a[x].l,t2 = a[x].r;
dfs(t1);//搜索左孩子
//左孩子搜到底返回的过程中判断短路的次数
if(a[x].c == '&' && a[t1].v == 0){
c1++;
return;
}
if(a[x].c == '|' && a[t1].v == 1){
c2++;
return;
}
dfs(t2);
}
int main(){
scanf("%s",s+1);
int len = strlen(s+1);
//1. 将中缀表达式转换为后缀表达式
for(int i = 1; i <= len; i++){
//如果是运算数
if(isdigit(s[i])) v.push_back(s[i]);
else if(s[i] == '(') op.push(s[i]);
else if(s[i] == ')'){
//弹出元素,直到遇到左括号
while(!op.empty() && op.top() != '('){
v.push_back(op.top());
op.pop();
}
//左括号也要单独弹出
op.pop();
}else if(s[i] == '&'){
//弹出元素直到遇到更低优先级的运算符或者(
while(!op.empty() && op.top() == '&')
{
v.push_back(op.top());
op.pop();
}
op.push(s[i]);//将&入栈
}else{
//|
while(!op.empty()&&(op.top()=='&'||op.top()=='|')){
v.push_back(op.top());
op.pop();
}
op.push(s[i]);//将|入栈
}
}
//如果栈中还有元素,依次弹出
while(!op.empty()){
v.push_back(op.top());
op.pop();
}
//2. 后缀表达式计算, 通过计算的过程建树
len = v.size();
int x,y;
for(int i = 0;i < len;i++){
//如果遇到运算数, 入栈,遇到运算符,取出次顶和栈顶运算,结果入栈
if(isdigit(v[i])){
a[++k] = {0,0,0,v[i] - '0'};
st.push(k);
}else{
x = st.top();//栈顶
st.pop();
y = st.top();//次顶
st.pop();
if(v[i] == '&'){
a[++k] = {v[i],y,x,a[y].v & a[x].v};
st.push(k);
}else{
a[++k] = {v[i],y,x,a[y].v | a[x].v};
st.push(k);
}
}
}
cout<
题目描述
在一个二维平面内,给定 n 个整数点 (xi, yi),此外你还可以自由添加 k 个整数点。你在自由添加 k 个点后,还需要从 n + k 个点中选出若干个整数点并组成一个序列,使
得序列中任意相邻两点间的欧几里得距离恰好为 1 而且横坐标、纵坐标值均单调不减,即 x[i+1] − x[i] = 1, y[i+1] = y[i] 或 y[i+1] − y[i] = 1, x[i+1] = x[i]。请给出满足条件的序列的最大长度。
输入格式
从文件 point.in 中读入数据。
第一行两个正整数 n, k 分别表示给定的整点个数、可自由添加的整点个数。
接下来 n 行,第 i 行两个正整数 xi, yi 表示给定的第 i 个点的横纵坐标。
输出格式
输出到文件 point.out 中。
输出一个整数表示满足要求的序列的最大长度。
输入输出样例
8 2
3 1
3 2
3 3
3 6
1 2
2 2
5 5
5 3
输出样例1:
8
4 100
10 10
15 25
20 20
30 30
103
说明
思路1:
依据题意, 要形成的上升点列一定要满足 x 和 y 均单调不递减。因此不难想到先按 x 升序,x 相等按 y 升序。
如果没有能够增加 k 个点的条件,发现本题就是 LIS 的 DP 模型。
考虑到最多可以添加 k 个点,可以将 LIS 中的一维状态设为二维状态。
即: f[i][j] 表示以第 i 个点结尾,添加了 j 个点的最大长度。
边界条件: f[i][0] = 1,表示以第 i 个点结尾, 即使没有添加任何点,也不能和其 他任何点拼接也能形成一个长度为 1 的上升点列。
可以再添加 j 个点, 那么 f[i][j] = j + 1,即:当前点和添加的 j 个点。
考虑 f[i][j] 的状态转移, 第 i 个点可以由 i 左下角的任意点转移过来,那么可 以枚举第 i 个点左下角的所有点。
设: p 为两点之间最多需要补充的点。
c = a[i].x - a[t].x + a[i].y - a[t].y - 1;
如果: j >= c,则点 i 可以由点 t 转移过来:
f[i][j] = max(f[i][j], f[t][j-c]+c+1);
参考代码:
#include
using namespace std;
/*
题目大意:在有 n 个点的情况下,补充 k 个点
选出最多的点,满足:
(1) 点是相邻的
(2) 坐标单调不递减
*/
const int N = 510,K = 110;
struct node{
int x,y;
}a[N];
//f[i][j]:以第 i 个点结尾,补充 j 个点,形成最长上升点列的长度
int f[N][K];
int n,k;
//按 x 升序, x 相等按 y 升序
bool cmp(node n1,node n2){
return n1.x>n>>k;
for(int i = 1;i <= n;i++){
cin>>a[i].x>>a[i].y;
}
//排序
sort(a+1,a+n+1,cmp);
//边界条件
for(int i = 1;i <= n;i++){
for(int j = 0;j <= k;j++){
f[i][j] = j + 1;
}
}
//从第 2 个点开始递推
int c;
for(int i = 2;i <= n;i++){
//枚举每个点前面的点
for(int t = 1;t < i;t++){
//只能取第 i 个点左下角的点
if(a[t].y > a[i].y) continue;
//c:从第 t 个点到第 i 个点要补充点的数量
c = a[i].x-a[t].x + a[i].y-a[t].y - 1;
//枚举 j 的值:补充点的数量
for(int j = c;j <= k;j++){
f[i][j] = max(f[i][j],f[t][j-c]+c+1);
}
}
}
//求答案:求以哪个点结尾形成的上升点列的长度是最长的
int ans = 0;
for(int i = 1;i <= n;i++) ans = max(ans,f[i][k]);
cout<