我要是没记错的话,今天的题难度算挺适中的。
*标程来自高天宇哥哥
T1:小G的字符串
题目描述
有一天,小 L 给小 G 出了这样一道题:生成一个长度为 n 的、全由小写英文字母构成的字符串,只能使用 k 种字母。要求满足:
- 字符串中相邻的两个字母不能相同。
- 必须出现恰好 k 种不同的字母。
这样的合法字符串可能有很多,小 L 让小 G 输出字典序最小的那个。
小 G 太笨啦,不会做这道题,希望你帮帮他。
输入格式
输入文件只有两个数字 n; k,含义如题。
输出格式
输出文件共一行,输出合法的字典序最小的字符串。
如果不存在任意一个合法的方案,输出 1。
样例输入
7 4
样例输出
ababacd
数据范围
对于 100% 的数据,1≤n≤105; 1≤k≤26
题目大概的意思是说让我们生成一个字典序最小的字符串,满足相邻字符不相同,并且出现k个不同的字符。
乍一看无法下手,其实特别特别简单。。
(表面慌的一匹,实则稳如老狗)
对于k>2的情况,只需要在前面不断输出ababab……最后把剩下的字符都输出出来就好了。
这样显然是字典序。
恶心的是,特殊情况太多。
k>n,直接-1不用考虑
k=1,n=1,只输出一个a
k=1,n>1这个显然-1
k=2,全都是abababab…
1 #include <cstring> 2 #include <cstdlib> 3 #include <iostream> 4 #include <cmath> 5 #include <cstdio> 6 using namespace std; 7 8 int main() { 9 freopen("str.in", "r", stdin); 10 freopen("str.out", "w", stdout); 11 int n, k; 12 cin >> n >> k; 13 14 if (k > n) { 15 cout << -1 << endl; 16 return 0; 17 } 18 19 if (k == 1) { 20 if (n > 1) 21 cout << -1 << endl; 22 else 23 cout << "a" << endl; 24 } else if (k == 2) { 25 for (int i = 1; i <= n; i++) 26 if (i % 2) putchar(‘a‘); 27 else putchar(‘b‘); 28 putchar(‘\n‘); 29 } else { 30 for (int i = 1; i <= n - (k - 2); i++) 31 if (i % 2) putchar(‘a‘); 32 else putchar(‘b‘); 33 for (int i = 2; i < k; i++) putchar(‘a‘ + i); 34 putchar(‘\n‘); 35 } 36 }
T2:小G的城堡
题目描述
小 G 家有一座城堡。城堡里面有 n 个房间,每个房间上都写着一个数字 pi。
小 G 拉着几个小伙伴在城堡里面玩耍,他们约定,如果某个人当前站在 i 房间里面,下一步这个人就会去 pi 房间,再下一步这个人去 ppi 。
为了增加趣味性,小 G 想重新书写每个房间的 pi,以满足:
- 如果从编号 1 到 k 中的某个房间开始,按照规则走,必须能够走到 1 号房间。特别地,如果从 1 号房间开始走,也要能够走回 1 号房间(至少走一步,如果 p1 = 1,从 1 走到 1 也算合法)。
- 如果从编号大于 k 的某个房间开始,按照规则走,一定不能走到 1 号房间。
小 G 想知道,有多少种书写 pi 的方案,可以满足要求。
输入格式
输入文件一行两个数字 n; k,含义如题。
输出格式
输出文件一个数字,表示合法的方案数。答案对 109 + 7 取模
样例输入 1
5 2
样例输出 1
54
样例输入 2
7 4
样例输出 2
1728
数据范围
对于 40% 的数据,1 ≤ n ≤ 8
对于 70% 的数据,1 ≤ n ≤ 105
对于 100% 的数据,1 ≤ n ≤ 1018; 1 ≤k ≤min(8,n)。
题目大意:有n个点,每个点有一条出边。要求前k个点能走到1号点,后k个点不能走到一号点,问方案数。
n<=8
暴力搜索,看每个点的出边指向哪里,然后检查就好。
n<=10^5
我们发现,前k个点肯定和前k个点互相连边。后n-k个点肯定不会连到前k个点里面去。
所以,我们只要爆搜前k个点连接的方案,然后检查;后n-k个点,只要连的是后n-k个点,爱怎么连怎么连,方案数是(n-k)^(n-k)。最后把两部分方案数乘起来就行。
n<=10^18
(n-k)^(n-k)太大?请使用快速幂。
1 #include <cstdio> 2 #include <cstring> 3 #include <cmath> 4 #include <cstdlib> 5 #include <iostream> 6 using namespace std; 7 8 typedef long long ll; 9 const int mod = 1e9 + 7; 10 11 ll n, k; 12 ll ans = 0; 13 14 inline ll qe(ll a, ll p) { 15 ll ans = 1; 16 a %= mod; 17 for (; p; p >>= 1, a = a * a % mod) 18 if (p & 1) 19 ans = ans * a % mod; 20 return ans; 21 } 22 23 int go_loc[10]; 24 25 inline bool check() { 26 static bool vis[10]; 27 memset(vis, 0, sizeof(vis)); 28 29 int now = 1; 30 while (!vis[now]) { 31 vis[now] = true; 32 now = go_loc[now]; 33 } 34 if (now != 1) return false; 35 for (int i = 1; i <= k; i++) 36 if (!vis[i]) { 37 static bool other_vis[10]; 38 memset(other_vis, 0, sizeof(other_vis)); 39 int now = i; 40 while (!other_vis[now] && !vis[now]) { 41 other_vis[now] = true; 42 now = go_loc[now]; 43 } 44 if (!vis[now]) return false; 45 } 46 return true; 47 } 48 49 void dfs(int now) { 50 if (now == k + 1) { 51 if (check()) ans++; 52 return; 53 } 54 for (int i = 1; i <= k; i++) { 55 go_loc[now] = i; 56 dfs(now + 1); 57 } 58 } 59 60 int main() { 61 freopen("castle.in", "r", stdin); 62 freopen("castle.out", "w", stdout); 63 cin >> n >> k; 64 dfs(1); 65 ans = ans * qe(n - k, n - k) % mod; 66 cout << ans << endl; 67 }
T3:小G坐电梯
题目描述
小 G 来到了著名的 CIGOM 大厦。大厦一共有 n 层,初始的时候小 G 在第 A 层。小 G 特别想去 B 层小 M 的办公室看一看,然而因为安保原因,B 层已经被封锁无法进入。
但是小 G 既然来了,就想在大厦里面逛一逛。大厦里面有一部电梯,小 G 决定坐 k 次电梯。因为小 G 比较无聊,他给自己设定了这样一个规矩:假如当前他在 x 层,则他要去的下一个楼层 y 和 x 的楼层差必须要小于 x 和 B 的楼层差,即|x-y| < |x-B|。每到达一个楼层,小 G 都要记录下来其楼层号。
当小 G 转完一圈后,他也记录下了 k + 1 个楼层号(可能有重复)。小 G 现在想知道,按照他定下的规矩,一共有多少种可能的楼层号序列?
输入格式
输入文件一行,4 个数字 n,A, B, k,含义如题目所述。
输出格式
输出一个数字,表示可能的楼层号序列的数量。答案对 109 + 7 取模。
样例输入 1
5 2 4 1
样例输出 1
2
样例输入 2
5 2 4 2
样例输出 2
2
样例输入 3
5 3 4 1
样例输出 3
0
数据范围
对于 30% 的数据,2≤ n≤ 8; 1 ≤ k ≤ 8。
对于 70% 的数据,2 ≤ n 300; 1 ≤ k ≤ 300。
对于 100% 的数据,2 ≤ n ≤ 5000; 1 ≤k ≤5000; 1≤ A,B≤n; A ≠B。
这个题告诉我们,有n个楼层,走k步。每次走的距离不能超过当前点距离B层的距离。问方案数。
搜索是显然的,但这个题正解是DP。
注意到主人公永远不能跨越B层。
令f[step][i]表示当前是第step步,走到i这个位置的方案数。
转移(我们以B层下侧为例):
f[step][i]=f[step-1][1~i-1]+f[step-1][i+1~ k]
其中,如果i+B为偶数,k=(i+B)/2-1
i+B为奇数,k=(i+B)/2
但这样并不能过所有点,因为转移是O(n)的。
我们要用O(1)的转移。注意我们每次的转移都来自一个连续的区间,而且我们是求和
来自一个连续的区间,还要求和,读者朋友,您是不是想到了前缀和?
令sum[step][i]表示f[step][1~i]的和
还是以B下侧为例
有f[step][i]=sum[step-1][i-1]+sum[step-1][k]-sum[step-1][i]。
1 #include <cstdio> 2 #include <cstring> 3 #include <cstdlib> 4 #include <cmath> 5 #include <iostream> 6 #include <algorithm> 7 using namespace std; 8 9 typedef long long ll; 10 const int MAXN = 6000; 11 const int MOD = 1000000007; 12 int f[MAXN][MAXN] = {{0}}; 13 int n, a, b, k; 14 15 inline int getnum() 16 { 17 char c; int ans = 0; bool flag = false; 18 while ((c = getchar()) == ‘ ‘ || c == ‘\r‘ || c == ‘\n‘); 19 if (c == ‘-‘) flag = true; else ans = c - ‘0‘; 20 while ((c = getchar()) >= ‘0‘ && c <= ‘9‘) ans = ans * 10 + c - ‘0‘; 21 return ans * (flag ? -1 : 1); 22 } 23 24 inline void add(int &a, int b) 25 { 26 ll tmp = (ll)a + b; 27 if (tmp >= MOD) a = (int)(tmp - MOD); 28 else if (tmp < 0) a = (int)(tmp + MOD); 29 else a = (int)tmp; 30 } 31 32 int main() 33 { 34 freopen("lift.in", "r", stdin); 35 freopen("lift.out", "w", stdout); 36 n = getnum(); a = getnum(); b = getnum(); k = getnum(); 37 if (fabs(a - b) - 1 == 0) { printf("0\n"); return 0; } 38 for (int i = a; i <= n; i++) 39 f[0][i] = 1; 40 for (int step = 1; step <= k; step++) 41 for (int i = 1; i <= n; i++) 42 if (i == b) f[step][i] = f[step][i - 1]; 43 else 44 { 45 f[step][i] = f[step][i - 1]; 46 if (i < b) 47 { 48 int x = (b + i) / 2; 49 if ((b + i) % 2 == 0) x--; 50 add(f[step][i], f[step - 1][x]); 51 add(f[step][i], -f[step - 1][i]); 52 add(f[step][i], f[step - 1][i - 1]); 53 } 54 else 55 { 56 int x = (b + i) / 2; 57 add(f[step][i], f[step - 1][n]); 58 add(f[step][i], -f[step - 1][x]); 59 add(f[step][i], -f[step - 1][i]); 60 add(f[step][i], f[step - 1][i - 1]); 61 } 62 } 63 printf("%d\n", f[k][n]); 64 }
哇今天的解题报告好短。。