西南科技大學第十六屆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 <iostream>
#include <algorithm>
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 <iostream>
#include <algorithm>
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,所以我們只保留每個質因數的最高冪次。
在這裏插入圖片描述
分解完之後,對所有的質因數進行快速冪取模,相乘取模即可。

#include <iostream>
#include <algorithm>
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 <iostream>
#include <algorithm>
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 <iostream>
#include <algorithm>
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;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章