2019牛客国庆集训派对day1
// 2019.10.1 国庆练习赛
// 比赛链接:2019牛客国庆集训派对day1
// 虽然我国庆七天溜回家了,队友还是督促我好好打比赛...
// 毕竟现场赛也没几天了,好好练习哈
A 全 1 子矩阵
题目大意
判断矩阵是否存在子矩阵满足 (x_1 le x le x_2, y_1 le y le y_2) 内全是1,其他地方为0。
水题,暴力判断一下即可。
AC代码
#include<iostream>
#include<cstdio>
using namespace std;
char mat[15][15];
int n, m;
int x1, y1, x2, y2;
void check() {
for(int i=x1;i<=x2;i++) {
for(int j=y1;j<=y2;j++) {
if(mat[i][j]=='1') {
mat[i][j] = '0';
}else {
printf("No
");
return;
}
}
}
for(int i=0;i<n;i++) {
for(int j=0;j<m;j++) {
if(mat[i][j]=='1') {
printf("No
");
return;
}
}
}
printf("Yes
");
}
int main() {
while(scanf("%d %d", &n, &m)!=EOF) {
for(int i=0;i<n;i++)
scanf("%s", mat[i]);
x1 = y1 = x2 = y2 = -1;
for(int i=0;i<n&&x1==-1;i++) {
for(int j=0;j<m;j++) {
if(mat[i][j]=='1') {
x1 = i, y1 = j;
break;
}
}
}
for(int i=n-1;i>=0&&x2==-1;i--) {
for(int j=m-1;j>=0;j--) {
if(mat[i][j]=='1') {
x2 = i, y2 = j;
break;
}
}
}
if(x1==-1||x2==-1) {
printf("No
");
continue;
}
check();
}
return 0;
}
B 组合数
题目大意
给定n 与 k, 求 (min(C_n^k, 10^{18})) 。
(0 ≤ k ≤ n≤10^9) , 至多 (10^5) 组数据。
思路
组合数 (C_{100}^{50}) 已经远超 (10^{18}),那么显然可以考虑使用 O(n) 的递推式求组合数,即便 (10^5) 组数据也跑的过去。
再次复习一遍组合数递推式
AC代码
折腾半天才A,注意到 (C_n^{n-k} = C_n^k) , 先 k = min(k, n-k),保证计算过程是单调的。
double
A不过去,又要用到无敌的 __int128
。赛后发现可以用 long double
保证精度。
#include<cstdio>
using namespace std;
typedef long long ll;
//typedef __int128 i128;
const ll INF = 1e18;
ll cal(ll n, ll k) {
if(k>n/2) k = n - k;
// i128 res = 1;
long double res = 1.0;
for(ll i=1;i<=k;i++) {
res = res*(n-i+1)/i;
if(res>INF) return INF;
}
return (ll)res;
}
int main() {
ll n, k;
while(scanf("%lld %lld", &n, &k)!=EOF) {
printf("%lld
", cal(n, k));
}
return 0;
}
优雅简洁的Python写法:
while True:
try:
n, k = map(int, input().split())
k = min(k, n-k)
ans = 1
for i in range(1, k+1):
ans = ans * (n-i+1) // i
if ans>10**18 :
ans = 10**18
break
print(ans)
except EOFError:
break
E Numbers
题目大意
有 n 个范围在 [0, 99] 之间的整数,现在他们相连写成了一列数,问原来的数组有多少种可能。
思路
原以为是 dp,写了一半不对劲,改写 dfs ,没搜出样例,溜了。。。
// 原来是dfs时 pos==len
才++ans。
AC代码
#include<cstdio>
#include<iostream>
#include<cstring>
using namespace std;
char s[55];
bool vis[100];
int len;
int ans;
void dfs(int pos) {
if(pos>len) return;
if(pos==len) {
++ans;
return;
}
int n = s[pos]-'0';
int nn = n*10;
if(!vis[n]) {
vis[n] = 1;
dfs(pos+1);
vis[n] = 0;
}
if(nn && pos<len-1) nn += s[pos+1]-'0';
if(nn>=10 && !vis[nn]) {
vis[nn] = 1;
dfs(pos+2);
vis[nn] = 0;
}
}
int main() {
while(scanf("%s", s)!=EOF) {
memset(vis, 0, sizeof(vis));
len = strlen(s);
ans = 0;
dfs(0);
printf("%d
", ans);
}
return 0;
}
F 4 Buttons
题目大意
Bobo一开始位于平面上的原点 (0,0),有四种操作:向右最多移动a步,向上最多移动b步,向左最多移动c步,向下最多移动d步。问执行 n 次操作可以到达多少个不同的点。
思路
只进行一步的操作很简单,分别可以到达坐标轴上离原点最远的 (a, 0), (0, b), (-c, 0), (0, -d)。故包含原点在内共有 1 + a + b + c + d 个点。
多步的话,我们先只看第一象限内的情况:
第一次操作到达x轴上区间 [1, a],第2次到第n次竖直方向上能到达 [1, (n-1)b)] ,共 a*(n-1)b个点;
两次操作到达x轴上区间 [a+1, 2a], 第2次到第n次竖直方向上能到达 [1, (n-2)b)] ,共 a*(n-2)b个点;
···
n-1次操作到达x轴上区间 [(n-2)a+1, (n-1)a], 第n-1次到第n次竖直方向上能到达 [1, b] ,共 a*b个//点;
// n 次操作到达x轴上最远的a个点 [(n-1)a+1, na]。
所以答案很简单, 1 + n(a + b + c + d) + n(n-1)/2 * (ab + bc + cd + ad) 。
AC代码
#include<cstdio>
using namespace std;
const int mod = 1e9+7;
typedef long long ll;
ll n, a, b, c, d;
int main() {
while(scanf("%lld %lld %lld %lld %lld", &n, &a, &b, &c, &d)!=EOF) {
ll ans = 1 + (a+b+c+d)%mod * n%mod + n*(n-1)/2 %mod
*((a*b%mod+ b*c%mod + c*d%mod + a*d%mod)%mod)%mod;
printf("%lld
", ans%mod);
}
return 0;
}
H 有向图
题目大意
数学题,待补,我的锅。