西南科技大学第十六届ACM程序设计竞赛暨绵阳市邀请赛 补题报告

本补题报告仅供参考,顺序由简到难

C:给你一个数字n,你需要输出一个字符串,该字符串长度必须小于等于n,并且含有n个“AR”子序列。

贪心或者思维。
直接就想到每一个“A”后面有多少个“R”,那么就可以贡献多少个“AR”。所以,只需要凑出n个“AR”即可。
默认长度为n。
直接命令第一个字母为“A”,显然,后面如果全是“R”,只有n-1个“AR”,所以,只需要在倒数第三的位置填上一个“A”即可。
如此,第一个“A”贡献n-2个“AR”,倒数第三位置上的“A”贡献2个“AR”,凑成n个。
并且,通过思考,或者手画可以知道在n <= 3时是无法凑成n个“AR”的,输出-1.

#include 
#include 
using namespace std;
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int n;
	cin >> n;
	if(n <= 3)
	cout << -1 << endl;
	else
	{
		cout << "A";
		for(int i = 1; i <= n-4; i++)
		{
			cout << "R";
		}
		cout << "ARR" << endl;
	}
	return 0;
}

B题:在1-n之间随机生成长度为n的整数序列,请问正好含有n-1个不同的整数的方案数,答案mod 1e9+7

长度为n,只含有n-1个不同的数字,所以必定有两个数字相同。
组合数学,从n个数字当中选出n-1个数字进行组合,为n。
其次,需要从n-1个数字当中选出哪个数字有两个,为n-1
之后将所有的数字进行排列组合,为n!
由于有两个数字相同,所以排列组合的结果需要 除2。
即ans = n*(n-1)* n! / 2 % 1e9+7

#include 
#include 
using namespace std;
const long long mod = 1e9 + 7;
const int maxn = 1e5 + 50;
long long a[maxn];
int main()
{
    long long n;
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    a[0] = 1;
    for(int i = 1; i <= 1e5; i++)
    {
        a[i] = a[i-1] * i % mod;
    }
    while(cin >> n)
    {
        long long ans = n*(n-1)/2%mod;
        ans *= a[n]%mod;
        ans %= mod;
        cout << ans << endl;
    }
    return 0;
}

值得注意的是,除法与取模的运算顺序,取模之后原本的偶数会变成奇数,进而再进行除法会影响结果,造成误差,导致WA。

E:给你n个数,求出最大的lcm。结果取余1e9+ 9.

求最大的lcm,那肯定是所有n个数的lcm最大。
自然想到了利用gcd来求lcm,多次除法与取余会导致误差WA,想到使用逆元也WA了(不理解)。
转而使用唯一分解定理:一个整数,一定可以唯一的分解为质因数相乘的形式。
针对n个数中的每一个数,我们都进行质因数分解,lcm,所以我们只保留每个质因数的最高幂次。
西南科技大学第十六届ACM程序设计竞赛暨绵阳市邀请赛 补题报告_第1张图片
分解完之后,对所有的质因数进行快速幂取模,相乘取模即可。

#include 
#include 
using namespace std;
const int N = 1e5 + 50;
const long long mod = 1e9 + 9;
int su[N] = {0};
long long zhi[N];
int z = 0;
int ans[N];
int aishai()
{
    su[1] = 1;
    for(int i = 2; i <= 1e5 + 5; i++)
    {
        if(su[i] == 0)
        {
            zhi[z] = i;
            z++;
            //num[i] = num[i-1] + 1;
            for(int j = 2; j * i <= 1e5 + 5; j++)
            {
                su[i*j] = 1;
            }
        }
    //  else
    //  num[i] = num[i-1];
    }
    //cout << z << endl;
}
long long hanshu(long long a)
{
        int t = 0;
        while(t < z)
        {
            long long temp = zhi[t] * zhi[t];
            if(temp > a) break;
            int cnt = 0;
            while(a % zhi[t] == 0)
            {
                a /= zhi[t];
                cnt++;
            }
            ans[zhi[t]] = max(ans[zhi[t]],cnt);
            t++;
        }
        if(a > 1) ans[a] = max(1,ans[a]);//保留本身
}
int quickPower(int x, int y)//快速幂加取模
{
    long long result = 1; // 定义答案
    while (y > 0) // 当指数大于 0 时进行幂运算
    {
        if (y & 1) // y 和 1 做与运算,相当于对 2 求模
        {
            result = (result * x) % mod;// 如果 y 为奇数,则结果只乘一个 x
        }
        x = x * x % mod;  // x 乘二次方,下次使用
        y = y >> 1; // y 右移一位,相当于除以 2
    }
    return result % mod; // 返回结果
}
int main()
{
    ios::sync_with_stdio(0);
    cin.tie(0);
    cout.tie(0);
    aishai();
    int n;
    cin >> n;
    for(int i = 1; i <= n; i++)
    {
        long long x;
        cin >> x;
        hanshu(x);
    }
    long long sum = 1;
    for(int i = 0; i < z; i++)
    {
        sum = sum * quickPower(zhi[i],ans[zhi[i]]) % mod;
    }
    cout << sum % mod << endl;
    return 0;
}

写完之后一直T,后来发现是唯一分解定理写垮了,还可以进行优化。
即原本的唯一分解定理是

for(int i = 0; i < z && zhi[i] <= a; i++)
        {
            int cnt = 0;
            if(a % zhi[i] == 0)
            {
                while(a % zhi[i] == 0)
                {
                    a /= zhi[i];
                    cnt++;
                }
            }
            ans[zhi[i]] = max(ans[zhi[i]],cnt);
        }

简单地对所有质数进行遍历,然后判断相除。数据范围为1e5,共有将近1e4个质数,每次进行遍历复杂度过高。
优化一下:

		int t = 0;
        while(t < z)
        {
            long long temp = zhi[t] * zhi[t];
            if(temp > a) break;
            int cnt = 0;
            while(a % zhi[t] == 0)
            {
                a /= zhi[t];
                cnt++;
            }
            ans[zhi[t]] = max(ans[zhi[t]],cnt);
            t++;
        }
        if(a > 1) ans[a] = max(1,ans[a]);//保留本身

枚举1 ~ 根号(a)的质数,判断是否相除,时间复杂度变为 300.
其次,如果a本身为质数,需要对a本身进行特判

if(a > 1) ans[a] = max(1,ans[a]);//保留本身

由此,可以ac本题。

A:一共有13张扑克牌,A 2 3 4 5 6 7 8 9 10 J Q K,有一个洗牌机器,现在给你初始的扑克牌序列,再给出洗牌两次之后的序列,求洗牌5次后的序列。

经过我们的交流与ac,本题的题意不太严谨,可以理解为:位置之间存在固定的映射关系,并且映射关系成环(即:映射13次之后会回归原状)
我们即可用洗2遍来推出洗5遍的结果,因为洗13遍之后会恢复原状。
所以:洗5遍和洗18遍相同。我们不能用洗两遍去求出5遍的结果,但是可以求出18遍的结果,由此得解。

#include 
#include 
using namespace std;
const int maxn = 50;
int a[maxn];
int b[maxn];
int pos[maxn];//地址的映射 
//pos[i] = j;从位置i转移到了位置j 
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	string start;
	while(cin >> start)
	{
		if(start[0] == 'A') a[1] = 1;
		else if(start[0] == 'J') a[1] = 11;
		else if(start[0] == 'Q') a[1] = 12;
		else if(start[0] == 'K')	a[1] = 13;
		else if(start == "10") a[1] = 10;
		else
		{
			a[1] = start[0] - '0';
		}
		//a[i]表示i位置的值 
		string s;
		//输入初始的后序序列 
		for(int i = 2; i <= 13; i++)
		{
			cin >> s;
			if(s[0] == 'A') a[i] = 1;
			else if(s[0] == 'J') a[i] = 11;
			else if(s[0] == 'Q') a[i] = 12;
			else if(s[0] == 'K')	a[i] = 13;
			else if(s == "10") a[i] = 10;
			else
			{
				a[i] = s[0] - '0';
			}
		}
		
		
		
		//输入两次变换后的 
		for(int i = 1; i <= 13; i++)
		{
			cin >> s;
			if(s[0] == 'A') 
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == 1)
					{
						pos[j] = i;
						break;
					}
				}
			}
			else if(s[0] == 'J')
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == 11)
					{
						pos[j] = i;
						break;
					}
				}
			}
			else if(s[0] == 'Q')
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == 12)
					{
						pos[j] = i;
						break;
					}
				}
			} 
			else if(s[0] == 'K')
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == 13)
					{
						pos[j] = i;
						break;
					}
				}
			}	
			else if(s == "10")
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == 10)
					{
						pos[j] = i;
						break;
					}
				}
			} 
			else
			{
				for(int j = 1; j <= 13; j++)
				{
					if(a[j] == s[0] - '0')
					{
						pos[j] = i;
						break;
					}
				}
			}
			 
		}
		//记录位置的映射关系
		int num = 9;//需要18次
		while(num--)
		{
			for(int i = 1; i <= 13; i++)
			{
				 b[pos[i]] = a[i]; 
			}
			for(int i = 1; i <= 13; i++)
			{
				a[i] = b[i];
			}
		}
		for(int i = 1; i <= 13; i++)
		{
			if(a[i] == 1) cout << "A ";
			else if(a[i] == 11) cout << "J ";
			else if(a[i] == 12) cout << "Q ";
			else if(a[i] == 13) cout << "K ";
			else	cout << a[i] << " ";
		} 
		cout << endl;
	}
	return 0;
}

注意地址映射关系的细节实现即可。

D:从坐标(0,0)走到(n,m),期间必须使用给出的行进方式,问最多可以使用多少种行进方式。

数据量比较小,使用dfs深搜即可。
期间需要使用状压思想,但是可以避免使用状压dp的解法。
将一个数字看为二进制,每一位的01表示是否使用过某种行进方式,看二进制1的多少来判断使用行进方式的多少。

#include 
#include 
using namespace std;
const int maxn = 105;
int map[maxn][maxn];
int dx[10] = {0};
int dy[10] = {0};
int n, m, k;
void dfs(int x, int y, int z)
{
	if(x == n && y == m) return ;
	for(int i = 0; i < k; i++)
	{
		int nowx = x + dx[i];
		int nowy = y + dy[i];
		//新的坐标 
		int nowz = z|(1<<i);
		//每一位表示一种方法 
		int cnt = __builtin_popcount(nowz);
		//求nowz当中有多少个1 
		if(nowx <= n && nowy <= m && cnt > map[nowx][nowy])
		{
			map[nowx][nowy] = cnt;
		//	cout << nowx << " " << nowy << " " << cnt << endl;
			dfs(nowx, nowy, nowz);
		}
	}
}
int main()
{
	ios::sync_with_stdio(0);
	cin.tie(0);
	cout.tie(0);
	int t;
	cin >> t;
	while(t--)
	{
		cin >> n >> m >> k;
		for(int i = 0; i <= n; i++)
		{
			for(int j = 0; j <= m; j++)
			{
				map[i][j] = 0;
			}
		}
		for(int i = 0; i < k; i++)
		{
			dx[i] = 0;
			dy[i] = 0;
		} 
		for(int i = 0; i < k; i++)
		{
			string s;
			cin >> s;
			for(int j = 0; j < s.length(); j++)
			{
				if(s[j] == 'U')
				{
					dy[i]++;	
				}
				else if(s[j] == 'R') 
				{
					dx[i]++;
				}
			}
			//cout << dx[i] << " " << dy[i] << endl;
		}
		dfs(0,0,0);
		cout << map[n][m] << endl;
	}
	return 0;
}

你可能感兴趣的:(算法)