NOIP2011普及组复赛 解题分析

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 00的情况,再减去。无论是加号还是乘号,都需要特判符号在边缘的情况。

递归过程中的一些优化细节:

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;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值