1.数字反转
算法分析
反转后需要考虑前导0。重新组数就可以规避这个问题。就是剥数和组数的过程。
#include <iostream>
#include <cstdio>
#include <cstring>
#define ll long long
using namespace std;
int main()
{
int n;
scanf("%d", &n);
if (n < 0)
{
n = -n;
printf("-");
}
int x = 0;
while (n)
{
x = x * 10 + n % 10;
n /= 10;
}
printf("%d\n", x);
return 0;
}
2.统计单词数
算法分析
题目没有说文章中单词距离单词有多少空格,实际上有的数据点,第一个单词前就有空格。读取一整行带空格的字符串,可以考虑用 s t r i n g string string类型的 g e t l i n e getline getline。这道题的数据推测是windows下造的,用不了 g e t c h a r getchar getchar。
读完后,对于文章的位置一个个的和单词进行比对判断。文章中开始比对的字符,它前面和后面得是空格。特殊情况是:第一个和最后一个字符。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <string>
#define ll long long
using namespace std;
int t;
string s, s1;
int scmp(int i)
{
int n = i + t - 1, j = 0;
while (i <= n)
{
if(s1[i] != s[j]) return 0;
++i;
++j;
}
return 1;
}
int main()
{
getline(cin, s);
t = s.length();
for (int i = 0; i < t; ++i) if (s[i] >= 'A' && s[i] <= 'Z') s[i] += 32;
getline(cin, s1);
int n = s1.length();
for (int i = 0; i < n; ++i) if (s1[i] >= 'A' && s1[i] <= 'Z') s1[i] += 32;
int cnt = 0, spos;
for (int i = 0; i <= n; ++i)
{
if (s1[i] == 32) continue;
if ((i - 1 == -1 ? 1 : s1[i-1] == 32) && scmp(i) && (i + t - 1 == n ? 1 : s1[i+t] == 32))
{
++cnt;
if (cnt == 1) spos = i;
}
}
if (!cnt) printf("-1\n"); else printf("%d %d\n", cnt, spos);
return 0;
}
3.瑞士轮
算法分析
每轮比赛结束后,需要重新排序。这个过程不能用sort,时间复杂度太大。观察得出,对于获胜的一方和失败的一方,他们都是有序的,也就是我们需要对两个有序的序列重新排序,借鉴归并排序中最后合并的思想。每次排序是 O ( n ) O(n) O(n)的复杂度,需要排 R R R次。整体时间复杂度 O ( ( n l o g n + R n ) ) O((nlogn+Rn)) O((nlogn+Rn))。
#include <cstdio>
#include <iostream>
#include<algorithm>
using namespace std;
int n;
struct node
{
int num,score,power;
};
node a[200010],win[100010],lost[100010];
inline bool cmp(node a,node b)
{
if(a.score==b.score) return a.num<b.num;
else return a.score>b.score;
}
void solve()
{
int lenwin=0,lenlost=0;
for(int i=1;i<=2*n;i+=2)
{
if(a[i].power>a[i+1].power)
{
a[i].score+=1;
win[++lenwin]=a[i];
lost[++lenlost]=a[i+1];
}else
{
a[i+1].score+=1;
win[++lenwin]=a[i+1];
lost[++lenlost]=a[i];
}
}
//
int i=1,j=1,k=1;
while(i<=lenwin&&j<=lenlost)
{
if(win[i].score==lost[j].score)
{
if(win[i].num<lost[j].num) a[k]=win[i],++k,++i;
else a[k]=lost[j],++k,++j;
}
else if(win[i].score>lost[j].score) a[k]=win[i],++k,++i;
else a[k]=lost[j],++k,++j;
}
while(i<=lenwin) a[k]=win[i],++k,++i;
while(j<=lenlost) a[k]=lost[j],++k,++j;
}
int main()
{
int r,q,x;
scanf("%d%d%d",&n,&r,&q);
for(register int i=1;i<=2*n;++i)
{
scanf("%d",&x);
a[i].num=i;
a[i].score=x;
}
for(register int i=1;i<=2*n;++i)
{
scanf("%d",&x);
a[i].power=x;
}
sort(a+1,a+2*n+1,cmp);
for(register int i=1;i<=r;++i)
{
solve();
}
printf("%d\n",a[q].num);
return 0;
}
4.表达式的值
算法分析
类似于中缀表达式求值。这题求的是方案数。用递归, d f s ( l , r ) dfs(l, r) dfs(l,r)求的是区间 [ l , r ] [l, r] [l,r]为0的方案数。整体思路:从右往左扫描,找到括号外的第一个加号,返回左右两个区间相乘的结果。如果没有加号,则找到括号外的第一个乘号,如果左侧是0,则右侧怎么填结果都是0,假设此时右侧的加号和乘号总数为cnt,则右侧的方案为 2 c n t + 1 2^{cnt+1} 2cnt+1;如果右侧是0,则左侧怎么填结果都是0,假设此时左侧的加号和乘号总数为cnt,则左侧的方案为 2 c n t + 1 2^{cnt+1} 2cnt+1。此时多加了一个 0 ∗ 0 0*0 0∗0的情况,再减去。无论是加号还是乘号,都需要特判符号在边缘的情况。
递归过程中的一些优化细节:
1.前缀和维护区间里加号和乘号的个数。
2.利用栈来维护每个右括号对应的左括号,在递归中,首先判断左右边界是不是相匹配的括号,如果是,则边界缩减。
3.不要重复计算。
4. a n s % p ans \% p ans%p可能为负,但是 ( a n s % p + p ) % p (ans \% p + p) \% p (ans%p+p)%p一定为正。
这是道练习递归的好题。
#include <iostream>
#include <cstdio>
#include <cstring>
#include <stack>
#define ll long long
using namespace std;
const int P = 10007;
int n, sum[100010];
char s[100010];
stack<int> sstack;
int spi[100010]; // 记录右括号匹配的左括号
int smi[100010]; // 2的i次方mod P
ll dfs(int l, int r)
{
while (s[r] == ')' && spi[r] == l)
{
++l; --r;
}
if (l == r && s[l] == '*') return 3;
if (l == r && s[l] == '+') return 1;
// 找括号外从右往左的第一个+和*
int posjia = 0, poscheng = 0, kh = 0; // kh:括号的个数
//
for (int i = r; i >= l; --i)
{
if (s[i] == ')')
{
i = spi[i]; // 直接跳到对应的左括号处
continue;
}
if (s[i] == '+' && posjia == 0 && kh == 0) posjia = i;
if (s[i] == '*' && poscheng == 0 && kh == 0) poscheng = i;
if (posjia) break;
}
if (posjia)
{
if (posjia == l) return dfs(posjia+1, r) % P;
else if (posjia == r) return dfs(l, posjia-1) % P;
else return (dfs(l, posjia-1) % P * dfs(posjia+1, r) ) % P;
}else if (poscheng)
{
int cnt1 = sum[poscheng-1] - sum[l-1], cnt2 = sum[r] - sum[poscheng];
ll ans = 0;
if (poscheng == l) return (smi[cnt2+1] + dfs(poscheng+1, r) % P) % P;
else if (poscheng == r) return (smi[cnt1+1] + dfs(l, poscheng-1) % P) % P;
else
{
ll ans1 = dfs(l, poscheng-1) % P;
ll ans2 = dfs(poscheng+1, r) % P;
ans += (smi[cnt1+1] * ans2 ) % P;
ans += (ans1 * smi[cnt2+1]) % P;
// 多加了左右都是0的情况,减去
ans -= (ans1 * ans2 ) % P;
return (ans + P) % P; // 防止变成负数,重要
}
}
}
int main()
{
// freopen("P1310_2.in", "r", stdin);
scanf("%d", &n);
scanf("%s", s + 1);
for (int i = 1; i <= n; ++i)
if (s[i] == '*' || s[i] == '+') sum[i] = sum[i-1] + 1;
else sum[i] = sum[i-1];
// spi
int stop;
for (int i = 1; i <= n; ++i)
if (s[i] == '(') sstack.push(i);
else if (s[i] == ')')
{
stop = sstack.top(); sstack.pop();
spi[i] = stop; // 位置i的右括号匹配的左括号的位置
}
smi[0] = 1;
for (int i = 1; i <= n; ++i) smi[i] = smi[i-1] * 2 % P;
printf("%lld\n", dfs(1, n));
return 0;
}