潍坊一中第三届挑战赛题解

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_1a1b2b_2b2 匹配,a2a_2a2b3b_3b3 匹配,a3a_3a3b1b_1b1 匹配。

对于剩下的,我们尽量让他达成平局,得 111 分。

因此尽量让剩下的 a1a_1a1b1b_1b1a2a_2a2b2b_2b2a3a_3a3b3b_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-sts个。

方法一

那么从任意一个空地开始,可以直接通过 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[i1][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[i1][0]×3+f[i1][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[i1][1]×2+f[i1][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[i1][2]+f[i1][3]×26

动态规划优化了解多的同学可以发现,这个式子可以用 4×44 \times 44×4 矩阵快速幂优化,时间复杂度降为 O(64∗logn)O(64*log^n)O(64logn),也可以解决 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} ABC=i=1n3Cnij=1ni1Cnijk=1nijCnijk23nijk

这一坨式子看起来就很难计算,我们换个思路。

计数类问题常见的一个方法是正难则反。设所有可能字符串的集合是全集 UUU ,∣U∣=26n|U|=26^nU=26n

从全集中扣除 同时不包含ABC的符号,剩下的就是同时包含ABC的诗句了。
$$
A \cap B \cap C =

U-A’ \cup B’ \cup C’
$$
我们求集合 AAA 的补集 A′A'A,就是不包含字符’A’的集合,也就是每个位置其他 252525 个字符可选,∣A′∣=25n|A'|=25^nA=25n
同样的原理计算 ∣A′B′∣=24n|A'B'|=24^nAB=24n , ∣A′B′C′∣=23n|A'B'C'|=23^nABC=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+CABACBC+ABC

带入上面的公式可得26n−3∗25n+3∗24n−23n26^n-3*25^n+3*24^n-23^n26n325n+324n23n


#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_1a1b2b_2b2 匹配,a2a_2a2b3b_3b3 匹配,a3a_3a3b1b_1b1 匹配。

对于剩下的,我们尽量让他达成平局,得 111 分。

因此尽量让剩下的 a1a_1a1b1b_1b1a2a_2a2b2b_2b2a3a_3a3b3b_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-sts个。

方法一

那么从任意一个空地开始,可以直接通过 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[i1][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[i1][0]×3+f[i1][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[i1][1]×2+f[i1][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[i1][2]+f[i1][3]×26

动态规划优化了解多的同学可以发现,这个式子可以用 4×44 \times 44×4 矩阵快速幂优化,时间复杂度降为 O(64∗logn)O(64*log^n)O(64logn),也可以解决 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} ABC=i=1n3Cnij=1ni1Cnijk=1nijCnijk23nijk

这一坨式子看起来就很难计算,我们换个思路。

计数类问题常见的一个方法是正难则反。设所有可能字符串的集合是全集 UUU ,∣U∣=26n|U|=26^nU=26n

从全集中扣除 同时不包含ABC的符号,剩下的就是同时包含ABC的诗句了。
$$
A \cap B \cap C =

U-A’ \cup B’ \cup C’
$$
我们求集合 AAA 的补集 A′A'A,就是不包含字符’A’的集合,也就是每个位置其他 252525 个字符可选,∣A′∣=25n|A'|=25^nA=25n
同样的原理计算 ∣A′B′∣=24n|A'B'|=24^nAB=24n , ∣A′B′C′∣=23n|A'B'C'|=23^nABC=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+CABACBC+ABC

带入上面的公式可得26n−3∗25n+3∗24n−23n26^n-3*25^n+3*24^n-23^n26n325n+324n23n


#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;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值