T1 病毒
这是一道签到题,考查数据类型和算术运算。
一个简单的除法,但是需要注意:$ 0 \leq m≤n≤2^{32} ,,, 2^{32} $正好是无符号长整型(unsigned (long int))的最大值加一,需要开超长整型((signed) long long (int))
保留三位小数,有两种方法:
第一种:
#include<cstdio>
//#include<stdio.h> 这两个头文件用其中一个即可
printf("%.3lf",ans);//.3表示保留三位小数,lf表示double类型
第二种:
#include<iomanip>
cout<<fixed<<setprecision(3)<<ans;//3表示保留三位小数
参考代码
#include<iostream>//或者用万能头<bits/stdc++.h>
#include<cstdio>
using namespace std;
int main()
{
//freopen("norovirus.in","r",stdin);
//freopen("norovirus.out","w",stdout);
long long n,m;
double ans;
cin>>n>>m;
ans=double(m*100)/n;/*处理百分号(%),同时将m转换为double*/
//输出可以使用以下三种方法其中之一。
//方法1
printf("%.3lf%% ",ans);
//方法2
cout<<fixed<<setprecision(3)<<ans<<"%";
//方法3
printf("%.3lf ",ans);
cout<<"%";
return 0;
}
T2 四季
本题想考查分支结构,为了防止输出任意一种骗分,增加为四组数据。不会使用循环的同学可以将代码复制4次。
对有些同学来说,可能难点在提取月份上,有两种方法,一是通过整体读入整数取模,二是通过字符读入,只取最后两个,运算得出月份。
然后使用if语句或switch语句完成代码。
参考代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
char a,b;
for(int i = 1; i <= 4; i++) {
/*方法一
cin >> n; //输入
int date = n % 100; //取日期的后两位 月份
*/
//方法二
cin>>a>>a>>a>>a>>a>>b;
int date= (a-'0')*10+b-'0';
if(date == 0 || date > 12) { //判断日期是否合法
cout << "error" << endl;
continue;
}
if(date >= 9 && date <= 11) { //秋天
cout << "autumn" << endl;
} else if(date >= 3 && date <= 5) { //春天
cout << "spring" << endl;
} else if(date >= 6 && date <= 8) { //夏天
cout << "summer" << endl;
} else { //冬天
cout << "winter" << endl;
}
}
return 0;
}
T3 二进制
本题考查了循环,进制转换,绝对值等。
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, ans;
long long a[200050];
short fun(long long x){
int cnt[2] = {0};
while(x){
cnt[x&1] ++;
x >>= 1;
}
return (cnt[1] > cnt[0]) ? 1 : -1;
}
int main(){
freopen("binary.in","r",stdin);
freopen("binary.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++)
ans += fun(abs(a[i]));
cout << ans << endl;
return 0;
}
T4 猜拳
本题考查了贪心算法。
思路
我们只讲述满分做法。
根据题意,对于小容出的每一个剪刀或石头或布,我们都尽量让他赢,得 222 分。
所以我们尽量的让 a1a_1a1 和 b2b_2b2 匹配,a2a_2a2 和 b3b_3b3 匹配,a3a_3a3 和 b1b_1b1 匹配。
对于剩下的,我们尽量让他达成平局,得 111 分。
因此尽量让剩下的 a1a_1a1 和 b1b_1b1,a2a_2a2 和 b2b_2b2,a3a_3a3 和 b3b_3b3 匹配。
剩下的只能输,不能得分。
可以证明,没有其他任何一种方法使得分数更高。
可结合代码理解。
参考代码
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define fi first
#define se second
using namespace std;
const int N=2e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
int n;
int a[3],b[3];
int ans;
signed main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<=2;i++) cin>>a[i];
for(int i=0;i<=2;i++) cin>>b[i];
int p=min(a[0],b[1]);
ans+=2*p;
a[0]-=p,b[1]-=p;
p=min(a[1],b[2]);
ans+=2*p;
a[1]-=p,b[2]-=p;
p=min(a[2],b[0]);
ans+=2*p;
a[2]-=p,b[0]-=p;
ans+=min(a[0],b[0]);
ans+=min(a[1],b[1]);
ans+=min(a[2],b[2]);
cout<<ans;
return 0;
}
还可以用循环优化下代码表达
参考代码二
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,a[3],b[3],ans=0;
cin>>n>>a[0]>>a[1]>>a[2]>>b[0]>>b[1]>>b[2];
for(int i=0;i<3;i++)
{
int j=(i+1)%3;
int k=min(a[i],b[j]);
ans+=k*2;
a[i]-=k;
b[j]-=k;
}
for(int i=0;i<3;i++){
int k=min(a[i],b[i]);
ans+=k;
a[i]-=k;
b[i]-=k;
}
cout<<ans;
return 0;
}
T5 写作业
本题考查了模拟和贪心算法,以及结构体排序等语法运用。
对于 50%50\%50%
考虑直接模拟这个过程,设立一个访问标记数组,每一轮找到没有选择过的最大值。因为答案最大为 nnn,所以时间复杂度 O(n2)O(n^2)O(n2)。
对于 100%100\%100%
考虑实际上要让最大的先拿,然后再让次大的拿,以此类推。
考虑最大的拿完后我们可能传到次大的,这样就可以在同一轮里拿多个,实际上就是如果他们的下标递增,那么就可以在同一轮里拿。
所以我们可以按照大小为第一关键字,从大到小排序,然后问题转化到了当前这个序列上,求有多少递增的连续段。
循环的同时我们判断当前的数是否比前面的数小,如果小的话那就说明不递增了,我们要新开一轮,然后就做完了。
参考代码
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
const int N = 1e5 + 10;
int n;
pii a[N];
int main()
{
fst
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].fi, a[i].se = i;
sort (a + 1, a + n + 1, [] (pii x, pii y) { return x.fi > y.fi; });
int res = 1;
for (int i = 1; i <= n; i++) if (a[i - 1].se > a[i].se) res++;
cout << res;
return 0;
}
T6 迷宫
信息学的成长路上怎么能少了迷宫问题呢?
思路
首先判断迷宫内空地的连通性,如果本来就不连通,那么及直接输出“No”。如果连通,连通块中包含的格式数要减少 sss 个,原来有 $ t $ 个,现在需要有 t−st-st−s个。
方法一
那么从任意一个空地开始,可以直接通过 dfsdfsdfs 或者 bfsbfsbfs 遍历找到$ t-s $个空地格子打标记,那么没打标记的空地格子就转换位字符’X’。
方法二
先将所有的空的格子转化为’X’,再遍历,将 $ t-s $个’X’变为空地’.’
参考代码 方法二
#include <bits/stdc++.h>
using namespace std;
int n,m,k,sx,sy,cnt,tot;
int dx[4] = {0,1,-1,0},dy[4] = {1,0,0,-1};
char mp[505][505];
void dfs(int x,int y){
if (cnt == tot - k){
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++) cout << mp[i][j];
cout << '\n';
}
exit(0);
}
for (int i = 0;i < 4;i++){
int nx = x + dx[i],ny = y + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > m || mp[nx][ny] != 'X') continue;
mp[nx][ny] = '.',cnt++;
dfs(nx,ny);
}
}
int main(){
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> k;
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++){
cin >> mp[i][j];
if (mp[i][j] == '.') mp[i][j] = 'X',sx = i,sy = j,tot++;
}
}
cnt = 1,mp[sx][sy] = '.';
dfs(sx,sy);
cout << "No";
return 0;
}
参考代码 方法一
#include <bits/stdc++.h>
using namespace std;
const int N = 510, dir[4][2] = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };
char s[N][N];
bool vis[N][N];
int n, m, k, cnt = 0, nr = 0;
void dfs (int x, int y) {
if (vis[x][y]) return;
--cnt;
vis[x][y] = true;
for (int l=0; l<4; l++) {
int dx=dir[l][0], dy= dir[l][1];
int i = dx + x, j = dy + y;
if (!cnt) return;
if (1 <= i && i <= n && 1 <= j && j <= m && !vis[i][j] && s[i][j] == '.') dfs (i, j);
}
}
void dfs1 (int x, int y) {
if (vis[x][y]) return;
vis[x][y] = true, ++nr;
for (int l=0; l<4; l++) {
int dx=dir[l][0], dy= dir[l][1];
int i = dx + x, j = dy + y;
if (1 <= i && i <= n && 1 <= j && j <= m && !vis[i][j] && s[i][j] == '.') dfs1 (i, j);
}
}
int main (void) {
freopen("maze.in","r",stdin);
freopen("maze.out","w",stdout);
cin.tie (0)->sync_with_stdio (false);
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) cin >> (s[i] + 1);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cnt += s[i][j] == '.';
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == '.') {
dfs1 (i, j);
if (cnt != nr) {//迷宫本来不连通
cout << "No\n";
return 0;
}
}
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) vis[i][j] = false;
cnt -= k;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
if (s[i][j] == '.') {
dfs (i, j);
if (cnt) {//空地不够
cout << "No\n";
return 0;
}
for (int i = 1; i <= n; i++, cout << '\n') for (int j = 1; j <= m; j++)
if (s[i][j] == '.') cout << (vis[i][j] ? '.' : 'X');
else cout << s[i][j];
return 0;
}
abort ();
}
T6 奏鸣曲
思路分析
Subtask1
枚举,数据分为 $ 4 $ 类 A\B\C 或者 D…Z,每个位置暴力枚举,时间复杂度 $O(4^{n}) $
Subtask2
要出现ABC三种符号,可以同个状态压缩枚举当前出现的符号类型转移,但我们只需要种类数,可以直接记录出现的类别,就不要状态压缩了。
设 f[i][j]f[i][j]f[i][j] 为前 iii 个位置出现过几个ABC。初始状态为 f[0][0]=1f[0][0]=1f[0][0]=1 。
那么可以设计线性转移方程,时间复杂度 O(n)O(n)O(n).
f[i][0]=f[i−1][0]×23
f[i][0]=f[i-1][0] \times 23
f[i][0]=f[i−1][0]×23
f[i][1]=f[i−1][0]×3+f[i−1][1]×24
f[i][1]=f[i-1][0] \times 3 +f[i-1][1] \times 24
f[i][1]=f[i−1][0]×3+f[i−1][1]×24
f[i][2]=f[i−1][1]×2+f[i−1][2]∗25
f[i][2]=f[i-1][1] \times 2 +f[i-1][2]*25
f[i][2]=f[i−1][1]×2+f[i−1][2]∗25
f[i][3]=f[i−1][2]+f[i−1][3]×26
f[i][3]=f[i-1][2] +f[i-1][3] \times 26
f[i][3]=f[i−1][2]+f[i−1][3]×26
动态规划优化了解多的同学可以发现,这个式子可以用 4×44 \times 44×4 矩阵快速幂优化,时间复杂度降为 O(64∗logn)O(64*log^n)O(64∗logn),也可以解决 100%100\%100%的数据。
Subtask3
这是一个计数类问题,设 包含字符A的集合设为集合A;包含字符B的集合设为集合B,包含字符C的集合设为集合C.
那么问题的答案就是 $A \cap B \cap C $.
根据组合计数原理,
∣A∩B∩C∣=∑i=1n−3Cni∑j=1n−i−1Cn−ij∑k=1n−i−jCn−i−jk23n−i−j−k|A \cap B \cap C|=\sum_{i=1}^{n-3}C_n^i \sum_{j=1}^{n-i-1}C_{n-i}^j\sum_{k=1}^{n-i-j}C_{n-i-j}^k23^{n-i-j-k} ∣A∩B∩C∣=i=1∑n−3Cnij=1∑n−i−1Cn−ijk=1∑n−i−jCn−i−jk23n−i−j−k
这一坨式子看起来就很难计算,我们换个思路。
计数类问题常见的一个方法是正难则反。设所有可能字符串的集合是全集 UUU ,∣U∣=26n|U|=26^n∣U∣=26n 。
从全集中扣除 同时不包含ABC的符号,剩下的就是同时包含ABC的诗句了。
$$
A \cap B \cap C =
U-A’ \cup B’ \cup C’
$$
我们求集合 AAA 的补集 A′A'A′,就是不包含字符’A’的集合,也就是每个位置其他 252525 个字符可选,∣A′∣=25n|A'|=25^n∣A′∣=25n 。
同样的原理计算 ∣A′B′∣=24n|A'B'|=24^n∣A′B′∣=24n , ∣A′B′C′∣=23n|A'B'C'|=23^n∣A′B′C′∣=23n,
$A’ \cup B’ \cup C’ $=
∣A′∣+∣B′∣+∣C′∣−∣A′∩B′∣−∣A′∩C′∣−∣B′∩C′∣+∣A′∩B′∩C′∣
|A'|+|B'|+|C'|-|A' \cap B'|-|A' \cap C'|-|B' \cap C'|+|A' \cap B' \cap C'|
∣A′∣+∣B′∣+∣C′∣−∣A′∩B′∣−∣A′∩C′∣−∣B′∩C′∣+∣A′∩B′∩C′∣
带入上面的公式可得26n−3∗25n+3∗24n−23n26^n-3*25^n+3*24^n-23^n26n−3∗25n+3∗24n−23n
#include <iostream>
using namespace std;
#define int long long
const int P = 1e9+7;
int qpow(int a, int b)
{
int res = 1;
while(b)
{
if(b & 1)
res = res * a % P;
b >>= 1;
a = a * a % P;
}
return res;
}
int F(int x) { return (x % P + P) % P; }
signed main()
{
freopen("sonata.in","r",stdin);
freopen("sonata.out","w",stdout);
int n; cin >> n;
if(n < 3)
return cout << 0, 0;
int all = qpow(26, n);
int xxy = qpow(25, n);
int x = qpow(24, n);
int oth = qpow(23, n);
int res = F(3 * xxy - 3 * x + oth);
cout << F(all - res) << '\n';
return 0;
}
```# T1 病毒
这是一道签到题,考查数据类型和算术运算。
一个简单的除法,但是需要注意:$ 0 \leq m≤n≤2^{32} $,$ 2^{32} $正好是无符号长整型(unsigned (long int))的最大值加一,需要开超长整型((signed) long long (int))
保留三位小数,有两种方法:
第一种:
```c++
#include<cstdio>
//#include<stdio.h> 这两个头文件用其中一个即可
printf("%.3lf",ans);//.3表示保留三位小数,lf表示double类型
第二种:
#include<iomanip>
cout<<fixed<<setprecision(3)<<ans;//3表示保留三位小数
参考代码
#include<iostream>//或者用万能头<bits/stdc++.h>
#include<cstdio>
using namespace std;
int main()
{
//freopen("norovirus.in","r",stdin);
//freopen("norovirus.out","w",stdout);
long long n,m;
double ans;
cin>>n>>m;
ans=double(m*100)/n;/*处理百分号(%),同时将m转换为double*/
//输出可以使用以下三种方法其中之一。
//方法1
printf("%.3lf%% ",ans);
//方法2
cout<<fixed<<setprecision(3)<<ans<<"%";
//方法3
printf("%.3lf ",ans);
cout<<"%";
return 0;
}
T2 四季
本题想考查分支结构,为了防止输出任意一种骗分,增加为四组数据。不会使用循环的同学可以将代码复制4次。
对有些同学来说,可能难点在提取月份上,有两种方法,一是通过整体读入整数取模,二是通过字符读入,只取最后两个,运算得出月份。
然后使用if语句或switch语句完成代码。
参考代码
#include<bits/stdc++.h>
using namespace std;
int main() {
int n;
char a,b;
for(int i = 1; i <= 4; i++) {
/*方法一
cin >> n; //输入
int date = n % 100; //取日期的后两位 月份
*/
//方法二
cin>>a>>a>>a>>a>>a>>b;
int date= (a-'0')*10+b-'0';
if(date == 0 || date > 12) { //判断日期是否合法
cout << "error" << endl;
continue;
}
if(date >= 9 && date <= 11) { //秋天
cout << "autumn" << endl;
} else if(date >= 3 && date <= 5) { //春天
cout << "spring" << endl;
} else if(date >= 6 && date <= 8) { //夏天
cout << "summer" << endl;
} else { //冬天
cout << "winter" << endl;
}
}
return 0;
}
T3 二进制
本题考查了循环,进制转换,绝对值等。
参考代码
#include<bits/stdc++.h>
using namespace std;
int n, ans;
long long a[200050];
short fun(long long x){
int cnt[2] = {0};
while(x){
cnt[x&1] ++;
x >>= 1;
}
return (cnt[1] > cnt[0]) ? 1 : -1;
}
int main(){
freopen("binary.in","r",stdin);
freopen("binary.out","w",stdout);
cin >> n;
for(int i = 1; i <= n; i ++) cin >> a[i];
for(int i = 1; i <= n; i ++)
ans += fun(abs(a[i]));
cout << ans << endl;
return 0;
}
T4 猜拳
本题考查了贪心算法。
思路
我们只讲述满分做法。
根据题意,对于小容出的每一个剪刀或石头或布,我们都尽量让他赢,得 222 分。
所以我们尽量的让 a1a_1a1 和 b2b_2b2 匹配,a2a_2a2 和 b3b_3b3 匹配,a3a_3a3 和 b1b_1b1 匹配。
对于剩下的,我们尽量让他达成平局,得 111 分。
因此尽量让剩下的 a1a_1a1 和 b1b_1b1,a2a_2a2 和 b2b_2b2,a3a_3a3 和 b3b_3b3 匹配。
剩下的只能输,不能得分。
可以证明,没有其他任何一种方法使得分数更高。
可结合代码理解。
参考代码
#include <bits/stdc++.h>
#define endl '\n'
#define int long long
#define fi first
#define se second
using namespace std;
const int N=2e5+10;
const int inf=0x3f3f3f3f3f3f3f3f;
int n;
int a[3],b[3];
int ans;
signed main()
{
//freopen(".in","r",stdin);
//freopen(".out","w",stdout);
cin.tie(0);
cout.tie(0);
ios::sync_with_stdio(false);
cin>>n;
for(int i=0;i<=2;i++) cin>>a[i];
for(int i=0;i<=2;i++) cin>>b[i];
int p=min(a[0],b[1]);
ans+=2*p;
a[0]-=p,b[1]-=p;
p=min(a[1],b[2]);
ans+=2*p;
a[1]-=p,b[2]-=p;
p=min(a[2],b[0]);
ans+=2*p;
a[2]-=p,b[0]-=p;
ans+=min(a[0],b[0]);
ans+=min(a[1],b[1]);
ans+=min(a[2],b[2]);
cout<<ans;
return 0;
}
还可以用循环优化下代码表达
参考代码二
#include<bits/stdc++.h>
using namespace std;
int main(){
int n,a[3],b[3],ans=0;
cin>>n>>a[0]>>a[1]>>a[2]>>b[0]>>b[1]>>b[2];
for(int i=0;i<3;i++)
{
int j=(i+1)%3;
int k=min(a[i],b[j]);
ans+=k*2;
a[i]-=k;
b[j]-=k;
}
for(int i=0;i<3;i++){
int k=min(a[i],b[i]);
ans+=k;
a[i]-=k;
b[i]-=k;
}
cout<<ans;
return 0;
}
T5 写作业
本题考查了模拟和贪心算法,以及结构体排序等语法运用。
对于 50%50\%50%
考虑直接模拟这个过程,设立一个访问标记数组,每一轮找到没有选择过的最大值。因为答案最大为 nnn,所以时间复杂度 O(n2)O(n^2)O(n2)。
对于 100%100\%100%
考虑实际上要让最大的先拿,然后再让次大的拿,以此类推。
考虑最大的拿完后我们可能传到次大的,这样就可以在同一轮里拿多个,实际上就是如果他们的下标递增,那么就可以在同一轮里拿。
所以我们可以按照大小为第一关键字,从大到小排序,然后问题转化到了当前这个序列上,求有多少递增的连续段。
循环的同时我们判断当前的数是否比前面的数小,如果小的话那就说明不递增了,我们要新开一轮,然后就做完了。
参考代码
#include <bits/stdc++.h>
using namespace std;
#define pii pair<int, int>
#define fi first
#define se second
#define fst ios::sync_with_stdio(false); cin.tie(0); cout.tie(0);
const int N = 1e5 + 10;
int n;
pii a[N];
int main()
{
fst
cin >> n;
for (int i = 1; i <= n; i++) cin >> a[i].fi, a[i].se = i;
sort (a + 1, a + n + 1, [] (pii x, pii y) { return x.fi > y.fi; });
int res = 1;
for (int i = 1; i <= n; i++) if (a[i - 1].se > a[i].se) res++;
cout << res;
return 0;
}
T6 迷宫
信息学的成长路上怎么能少了迷宫问题呢?
思路
首先判断迷宫内空地的连通性,如果本来就不连通,那么及直接输出“No”。如果连通,连通块中包含的格式数要减少 sss 个,原来有 $ t $ 个,现在需要有 t−st-st−s个。
方法一
那么从任意一个空地开始,可以直接通过 dfsdfsdfs 或者 bfsbfsbfs 遍历找到$ t-s $个空地格子打标记,那么没打标记的空地格子就转换位字符’X’。
方法二
先将所有的空的格子转化为’X’,再遍历,将 $ t-s $个’X’变为空地’.’
参考代码 方法二
#include <bits/stdc++.h>
using namespace std;
int n,m,k,sx,sy,cnt,tot;
int dx[4] = {0,1,-1,0},dy[4] = {1,0,0,-1};
char mp[505][505];
void dfs(int x,int y){
if (cnt == tot - k){
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++) cout << mp[i][j];
cout << '\n';
}
exit(0);
}
for (int i = 0;i < 4;i++){
int nx = x + dx[i],ny = y + dy[i];
if (nx < 1 || nx > n || ny < 1 || ny > m || mp[nx][ny] != 'X') continue;
mp[nx][ny] = '.',cnt++;
dfs(nx,ny);
}
}
int main(){
ios_base::sync_with_stdio(false); cin.tie(0); cout.tie(0);
cin >> n >> m >> k;
for (int i = 1;i <= n;i++){
for (int j = 1;j <= m;j++){
cin >> mp[i][j];
if (mp[i][j] == '.') mp[i][j] = 'X',sx = i,sy = j,tot++;
}
}
cnt = 1,mp[sx][sy] = '.';
dfs(sx,sy);
cout << "No";
return 0;
}
参考代码 方法一
#include <bits/stdc++.h>
using namespace std;
const int N = 510, dir[4][2] = { { 1, 0 }, { -1, 0 }, { 0, 1 }, { 0, -1 } };
char s[N][N];
bool vis[N][N];
int n, m, k, cnt = 0, nr = 0;
void dfs (int x, int y) {
if (vis[x][y]) return;
--cnt;
vis[x][y] = true;
for (int l=0; l<4; l++) {
int dx=dir[l][0], dy= dir[l][1];
int i = dx + x, j = dy + y;
if (!cnt) return;
if (1 <= i && i <= n && 1 <= j && j <= m && !vis[i][j] && s[i][j] == '.') dfs (i, j);
}
}
void dfs1 (int x, int y) {
if (vis[x][y]) return;
vis[x][y] = true, ++nr;
for (int l=0; l<4; l++) {
int dx=dir[l][0], dy= dir[l][1];
int i = dx + x, j = dy + y;
if (1 <= i && i <= n && 1 <= j && j <= m && !vis[i][j] && s[i][j] == '.') dfs1 (i, j);
}
}
int main (void) {
freopen("maze.in","r",stdin);
freopen("maze.out","w",stdout);
cin.tie (0)->sync_with_stdio (false);
cin >> n >> m >> k;
for (int i = 1; i <= n; i++) cin >> (s[i] + 1);
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) cnt += s[i][j] == '.';
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) if (s[i][j] == '.') {
dfs1 (i, j);
if (cnt != nr) {//迷宫本来不连通
cout << "No\n";
return 0;
}
}
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++) vis[i][j] = false;
cnt -= k;
for (int i = 1; i <= n; i++) for (int j = 1; j <= m; j++)
if (s[i][j] == '.') {
dfs (i, j);
if (cnt) {//空地不够
cout << "No\n";
return 0;
}
for (int i = 1; i <= n; i++, cout << '\n') for (int j = 1; j <= m; j++)
if (s[i][j] == '.') cout << (vis[i][j] ? '.' : 'X');
else cout << s[i][j];
return 0;
}
abort ();
}
T6 奏鸣曲
思路分析
Subtask1
枚举,数据分为 $ 4 $ 类 A\B\C 或者 D…Z,每个位置暴力枚举,时间复杂度 $O(4^{n}) $
Subtask2
要出现ABC三种符号,可以同个状态压缩枚举当前出现的符号类型转移,但我们只需要种类数,可以直接记录出现的类别,就不要状态压缩了。
设 f[i][j]f[i][j]f[i][j] 为前 iii 个位置出现过几个ABC。初始状态为 f[0][0]=1f[0][0]=1f[0][0]=1 。
那么可以设计线性转移方程,时间复杂度 O(n)O(n)O(n).
f[i][0]=f[i−1][0]×23
f[i][0]=f[i-1][0] \times 23
f[i][0]=f[i−1][0]×23
f[i][1]=f[i−1][0]×3+f[i−1][1]×24
f[i][1]=f[i-1][0] \times 3 +f[i-1][1] \times 24
f[i][1]=f[i−1][0]×3+f[i−1][1]×24
f[i][2]=f[i−1][1]×2+f[i−1][2]∗25
f[i][2]=f[i-1][1] \times 2 +f[i-1][2]*25
f[i][2]=f[i−1][1]×2+f[i−1][2]∗25
f[i][3]=f[i−1][2]+f[i−1][3]×26
f[i][3]=f[i-1][2] +f[i-1][3] \times 26
f[i][3]=f[i−1][2]+f[i−1][3]×26
动态规划优化了解多的同学可以发现,这个式子可以用 4×44 \times 44×4 矩阵快速幂优化,时间复杂度降为 O(64∗logn)O(64*log^n)O(64∗logn),也可以解决 100%100\%100%的数据。
Subtask3
这是一个计数类问题,设 包含字符A的集合设为集合A;包含字符B的集合设为集合B,包含字符C的集合设为集合C.
那么问题的答案就是 $A \cap B \cap C $.
根据组合计数原理,
∣A∩B∩C∣=∑i=1n−3Cni∑j=1n−i−1Cn−ij∑k=1n−i−jCn−i−jk23n−i−j−k|A \cap B \cap C|=\sum_{i=1}^{n-3}C_n^i \sum_{j=1}^{n-i-1}C_{n-i}^j\sum_{k=1}^{n-i-j}C_{n-i-j}^k23^{n-i-j-k} ∣A∩B∩C∣=i=1∑n−3Cnij=1∑n−i−1Cn−ijk=1∑n−i−jCn−i−jk23n−i−j−k
这一坨式子看起来就很难计算,我们换个思路。
计数类问题常见的一个方法是正难则反。设所有可能字符串的集合是全集 UUU ,∣U∣=26n|U|=26^n∣U∣=26n 。
从全集中扣除 同时不包含ABC的符号,剩下的就是同时包含ABC的诗句了。
$$
A \cap B \cap C =
U-A’ \cup B’ \cup C’
$$
我们求集合 AAA 的补集 A′A'A′,就是不包含字符’A’的集合,也就是每个位置其他 252525 个字符可选,∣A′∣=25n|A'|=25^n∣A′∣=25n 。
同样的原理计算 ∣A′B′∣=24n|A'B'|=24^n∣A′B′∣=24n , ∣A′B′C′∣=23n|A'B'C'|=23^n∣A′B′C′∣=23n,
$A’ \cup B’ \cup C’ $=
∣A′∣+∣B′∣+∣C′∣−∣A′∩B′∣−∣A′∩C′∣−∣B′∩C′∣+∣A′∩B′∩C′∣
|A'|+|B'|+|C'|-|A' \cap B'|-|A' \cap C'|-|B' \cap C'|+|A' \cap B' \cap C'|
∣A′∣+∣B′∣+∣C′∣−∣A′∩B′∣−∣A′∩C′∣−∣B′∩C′∣+∣A′∩B′∩C′∣
带入上面的公式可得26n−3∗25n+3∗24n−23n26^n-3*25^n+3*24^n-23^n26n−3∗25n+3∗24n−23n
#include <iostream>
using namespace std;
#define int long long
const int P = 1e9+7;
int qpow(int a, int b)
{
int res = 1;
while(b)
{
if(b & 1)
res = res * a % P;
b >>= 1;
a = a * a % P;
}
return res;
}
int F(int x) { return (x % P + P) % P; }
signed main()
{
freopen("sonata.in","r",stdin);
freopen("sonata.out","w",stdout);
int n; cin >> n;
if(n < 3)
return cout << 0, 0;
int all = qpow(26, n);
int xxy = qpow(25, n);
int x = qpow(24, n);
int oth = qpow(23, n);
int res = F(3 * xxy - 3 * x + oth);
cout << F(all - res) << '\n';
return 0;
}
3628

被折叠的 条评论
为什么被折叠?



