第一题:连通图
在线测评链接:http://121.196.235.151/p/P1054
题目描述
给定一个只有 n n n个点的图,图上没有边,ak机准备在这张图上连 m m m 条无向边,将这个图变成无自环无重边的简单连通图,ak机想知道是否存在至少一种连边方案。
输入描述
第一行输入一个整数 T ( 1 ≤ T ≤ 1 0 5 ) T(1 \leq T \leq 10^5) T(1≤T≤105) 表示询问数量。
第二行输入两个整数 n , m ( 1 ≤ n , m ≤ 1 0 9 ) n,m(1 \leq n,m \leq 10^9) n,m(1≤n,m≤109) 。
输出描述
对于每个询问,若存在至少一种连边方案,则输出 “YES” 。否则输出 “NO” 。
样例
输入
2
3 3
3 1
输出
YES
NO
思路:模拟 简单图论
对于一个无自环重边的无向图来说,他至少需要
n
−
1
n-1
n−1条边才可以连通,例如1->2->...->n
,这就可以构成一种需要边数最少的连通图
n n n个点,两两可以连边,最多可以连接 C ( n , 2 ) = n × ( n − 1 ) 2 C(n,2)=\frac{n\times (n-1)}{2} C(n,2)=2n×(n−1)条边,因此最多的边数不能超过 n × ( n − 1 ) 2 \frac{n\times (n-1)}{2} 2n×(n−1)
本题数据范围较大,C++和Java选手需要开long long
C++
#include <bits/stdc++.h>
using namespace std;
int n,m,T;
int main() {
cin>>T;
while(T--){
cin>>n>>m;
if(m>=n-1&&m<=1ll*n*(n+1)/2)puts("YES");
else puts("NO");
}
}
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int T = scanner.nextInt();
while (T-- > 0) {
int n = scanner.nextInt();
int m = scanner.nextInt();
if (m >= n - 1 && m <= 1L * n * (n + 1) / 2) {
System.out.println("YES");
} else {
System.out.println("NO");
}
}
}
}
Python
T = int(input())
for _ in range(T):
n, m = map(int, input().split())
if m >= n - 1 and m <= n * (n + 1) // 2:
print("YES")
else:
print("NO")
第二题:流血debuff的最大层数
在线测评链接:http://121.196.235.151/p/P1029
题目描述
ak机面前有 n n n个怪物,站成一排,最初每个怪物有 a i a_i ai的流血状态,ak机可以进行 m m m次攻击,每次攻击选择 k k k个连续的怪物,每个怪物增加 1 层流血状态。ak机想知道 m m m次攻击之后,流血状态最少的怪物最多可以有多少层流血状态。
输入描述
第一行输入三个整数 n , m , k n,m,k n,m,k,表示怪物数量,攻击次数,每次攻击选择的怪物数量。
第二行输入 n n n个整数 a i a_i ai,表示每个怪物初始的流血状态。
1 ≤ k ≤ n ≤ 1 0 5 1 \leq k \leq n \leq 10^5 1≤k≤n≤105
1 ≤ a i , m ≤ 1 0 9 1 \leq a_i, m \leq 10^9 1≤ai,m≤109
输出描述
输出一个整数,表示流血状态最少的怪物最多可以有多少层流血状态。
样例
输入
5 3 2
3 1 2 5 4
输出
4
说明
3 次攻击分别选择 [1, 2], [2, 3], [2, 3],最后怪物的流血状态为 [4, 4, 4, 5, 4],流血状态最少的怪物最多有 4 层流血状态。
思路:二分答案+差分check
本题抽象出来其实就是最小值最大化,我们可以考虑使用二分答案求解(遇到这种最大化或者最小化问题,第一反应就要想到二分答案)
二分答案枚举到 m i d mid mid时,我们需要验证是否可以将所有怪物的流血层数都在 m i d mid mid层以上
由于操作是对一个区间操作,即为区间加法,因此我们可以使用差分来维护对区间的操作
维护一个差分数组 d i f f diff diff, d i f f [ i ] diff[i] diff[i]表示当前位置 i i i已经增加了 d i f f [ i ] diff[i] diff[i]层流血
那么当我们遍历到位置 i i i时,如果有 d i f f [ i ] + a [ i ] < m i d diff[i]+a[i]<mid diff[i]+a[i]<mid,则我们需要对区间 [ i , i + k − 1 ] [i,i+k-1] [i,i+k−1]操作 c n t 1 cnt1 cnt1次,其中 c n t 1 = m i d − d i f f [ i ] − a [ i ] cnt1=mid-diff[i]-a[i] cnt1=mid−diff[i]−a[i]
然后我们使用差分数组模版对区间进行操作,最终判断所有操作次数 c n t cnt cnt是否满足 c n t ≤ m cnt\le m cnt≤m即可
- 如果所有的操作次数 c n t ≤ m cnt\le m cnt≤m,则说明当前答案满足条件,可以将区间缩小至 [ m i d , r ] [mid,r] [mid,r]
- 如果所有的操作次数 c n t > m cnt> m cnt>m,则说明当前答案不满足条件,可以将区间缩小至 [ l , m i d − 1 ] [l,mid-1] [l,mid−1]
C++
#include <bits/stdc++.h>
using namespace std;
const int N=1E5+10;
int n,m,k,a[N];
bool check(int x){ //枚举是否所有怪物都可以至少有x层流血状态
vector<int>diff(n+10,0); //差分数组
int cnt=0;
for(int i=1;i<=n;i++){
diff[i]+=diff[i-1];
if(diff[i]+a[i]<x) //层数不够 需要叠加
{
int cnt1=x-a[i]-diff[i];
cnt+=cnt1;
diff[i]+=cnt1; //对区间[i,i+k]+cnt1
diff[min(n+1,i+k)]-=cnt1;
}
if(cnt>m)return false;
}
return true;
}
int main() {
cin>>n>>m>>k;
for(int i=1;i<=n;i++)cin>>a[i];
int l=1,r=1e9;
while(l<r){
int mid=(l+r+1)>>1;
if(check(mid))l=mid;
else r=mid-1;
}
cout<<l<<endl;
}
Java
import java.util.Scanner;
import java.util.Arrays;
public class Main {
static int N = (int)1e5 + 10;
static int n, m, k;
static int[] a = new int[N];
static int[] diff = new int[N];
// 检查所有怪物是否都可以至少有x层流血状态
static boolean check(int x) {
Arrays.fill(diff, 0);
int cnt = 0;
for (int i = 1; i <= n; i++) {
diff[i] += diff[i - 1];
if (diff[i] + a[i] < x) { // 层数不够,需要叠加
int cnt1 = x - a[i] - diff[i];
cnt += cnt1;
diff[i] += cnt1; // 对区间[i, i+k]叠加cnt1
diff[Math.min(n + 1, i + k)] -= cnt1;
}
if (cnt > m) return false;
}
return true;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
m = scanner.nextInt();
k = scanner.nextInt();
for (int i = 1; i <= n; i++) a[i] = scanner.nextInt();
int l = 1, r = (int)1e9;
while (l < r) {
int mid = (l + r + 1) / 2;
if (check(mid)) l = mid;
else r = mid - 1;
}
System.out.println(l);
}
}
Python
N = int(1e5) + 10
a = [0] * N
diff = [0] * N
# 检查所有怪物是否都可以至少有x层流血状态
def check(x):
diff[:] = [0] * N
cnt = 0
for i in range(1, n + 1):
diff[i] += diff[i - 1]
if diff[i] + a[i] < x: # 层数不够,需要叠加
cnt1 = x - a[i] - diff[i]
cnt += cnt1
diff[i] += cnt1 # 对区间[i, i+k]叠加cnt1
diff[min(n + 1, i + k)] -= cnt1
if cnt > m:
return False
return True
n, m, k = map(int, input().split())
for i in range(1, n + 1):
a[i] = int(input())
l, r = 1, int(1e9)
while l < r:
mid = (l + r + 1) // 2
if check(mid):
l = mid
else:
r = mid - 1
print(l)
第三题:子序列乘积
在线测评链接:http://121.196.235.151/p/P1155
题目描述
ak机拿到了一个数组,数组的元素的绝对值不超过 1,她想取一个非空子序列(在原数组中可以不连续),并计算该子序列的乘积。
ak机想知道,子序列乘积为 -1、0、1 的方案数分别有多少种?
输入描述
第一行输入一个正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n \leq 10^5) n(1≤n≤105),代表数组的大小。
第二行输入 n n n个整数 a i ( − 1 ≤ a i ≤ 1 ) a_i(-1 \leq a_i \leq 1) ai(−1≤ai≤1),代表数组的元素。
输出描述
三个整数,分别代表子序列乘积为负数、0、正数的方案数。由于答案可能过大,请对 1 0 9 + 7 10^9+7 109+7取模。
样例
输入
4
-1 0 -1 1
输出
4 8 3
说明
长度为 1 的子序列共有 4 个,乘积为 -1 的有 2 个,乘积为 0 的有 1 个,乘积为 1 的有 1 个。
长度为 2 的子序列共有 6 个,乘积为 -1 的有 2 个,乘积为 0 的有 3 个,乘积为 1 的有 1 个。
长度为 3 的子序列共有 4 个,乘积为 -1 的有 0 个,乘积为 0 的有 3 个,乘积为 1 的有 1 个。
长度为 4 的子序列只有 1 个,乘积为 0。
思路:组合计数 乘法逆元
我们首先设数组中-1,0,1的个数分别为 a , b , c a,b,c a,b,c
首先,对于乘积为0的情况,我们只需要选择至少1个0,其余数可以选或者不选都可以,因此对于乘积为0的方案数为 ( 2 b − 1 ) × 2 a + c (2^b-1)\times 2^{a+c} (2b−1)×2a+c
对于乘积为1的情况,需要选择偶数个-1,其余的1可以任意选
偶数个-1的情况有 0 , 2 , 4 , . . . 2 k ( 2 k ≤ c ) 0,2,4,...2k(2k\le c) 0,2,4,...2k(2k≤c)
因此对应的方案数为 ( ∑ i = 0 2 i ≤ c C ( a , 2 i ) × 2 c ) − 1 (\sum_{i=0}^{2i\le c}C(a,2i)\times 2^c)-1 (∑i=02i≤cC(a,2i)×2c)−1
这里减1是因为不能所有的-1和1都不选,这样就成空数组了,因此需要-1。
其中 C ( n , k ) = n ! k ! ( n − k ) ! C(n,k)=\frac{n!}{k!(n-k)!} C(n,k)=k!(n−k)!n!
对于乘积为-1的情况,需要选择奇数个-1,其余的1可以任意选
同理可得,对应的方案数为 ∑ i = 0 2 i + 1 ≤ c C ( a , 2 i + 1 ) × 2 c \sum_{i=0}^{2i+1\le c}C(a,2i+1)\times 2^c ∑i=02i+1≤cC(a,2i+1)×2c
C++
#include<bits/stdc++.h>
using namespace std;
const int mod = 1e9 + 7;
const int N = 1e5+10;
long long fac[N], ifac[N]; //预处理阶乘和阶乘的逆元
string s;
int n,a,b,c,w[N];
long long qmi(long long a, long long b,int mod) { //快速幂
long long res = 1;
while (b > 0) {
if (b & 1) {
res = res * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return res;
}
long long C(int a, int b) { //求C(a,b)的组合数
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
int main() {
cin>>n;
for(int i=0;i<n;i++)cin>>w[i];
for(int i=0;i<n;i++){ //统计数字-1,0,1的个数
if(w[i]==-1){
a++;
}
else if(w[i]==0){
b++;
}
else{
c++;
}
}
fac[0] = ifac[0] = 1;
for (int i = 1; i <=n; i++) {
fac[i] = fac[i - 1] * i % mod;
ifac[i]=ifac[i-1]*qmi(i,mod-2,mod)%mod;
}
long long res1=0,res2=0,res3=0; //分别计算乘积为负数、0、正数的方案数
for(int i=1;i<=a;i+=2){ //计算乘积为负数的个数
res1=(res1+C(a,i))%mod;
}
res1=(res1*qmi(2,c,mod))%mod;
res2=(qmi(2,b,mod)-1+mod)%mod;
res2=(res2*qmi(2,a+c,mod))%mod;
for(int i=0;i<=a;i+=2){ //计算乘积为正数的个数
res3=(res3+C(a,i))%mod;
}
res3=(res3*qmi(2,c,mod))%mod;
res3=(res3-1+mod)%mod;
cout<<res1<<" "<<res2<<" "<<res3<<endl;
return 0;
}
Java
import java.util.Scanner;
public class Main {
static final int mod = (int)1e9 + 7;
static final int N = (int)1e5+10;
static long[] fac = new long[N], ifac = new long[N]; //预处理阶乘和阶乘的逆元
static int n,a,b,c;
static int[] w = new int[N];
static long qmi(long a, long b, int mod) { //快速幂
long res = 1;
while (b > 0) {
if ((b & 1) == 1) {
res = res * a % mod;
}
a = a * a % mod;
b >>= 1;
}
return res;
}
static long C(int a, int b) { //求C(a,b)的组合数
return fac[a] * ifac[b] % mod * ifac[a - b] % mod;
}
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
n = scanner.nextInt();
for(int i=0;i<n;i++) w[i] = scanner.nextInt();
for(int i=0;i<n;i++){ //统计数字-1,0,1的个数
if(w[i]==-1){
a++;
}
else if(w[i]==0){
b++;
}
else{
c++;
}
}
fac[0] = ifac[0] = 1;
for (int i = 1; i <=n; i++) {
fac[i] = fac[i - 1] * i % mod;
ifac[i]=ifac[i-1]*qmi(i,mod-2,mod)%mod;
}
long res1=0,res2=0,res3=0; //分别计算乘积为负数、0、正数的方案数
for(int i=1;i<=a;i+=2){ //计算乘积为负数的个数
res1=(res1+C(a,i))%mod;
}
res1=(res1*qmi(2,c,mod))%mod;
res2=(qmi(2,b,mod)-1+mod)%mod;
res2=(res2*qmi(2,a+c,mod))%mod;
for(int i=0;i<=a;i+=2){ //计算乘积为正数的个数
res3=(res3+C(a,i))%mod;
}
res3=(res3*qmi(2,c,mod))%mod;
res3=(res3-1+mod)%mod;
System.out.println(res1+" "+res2+" "+res3);
}
}
Python
mod = 10**9 + 7
N = 10**5+10
fac = [0]*N
ifac = [0]*N #预处理阶乘和阶乘的逆元
n,a,b,c = 0,0,0,0
w = [0]*N
def qmi(a, b): #快速幂
res = 1
while b > 0:
if b & 1:
res = res * a % mod
a = a * a % mod
b >>= 1
return res
def C(a, b): #求C(a,b)的组合数
return fac[a] * ifac[b] % mod * ifac[a - b] % mod
n = int(input())
w = list(map(int, input().split()))
for i in range(n): #统计数字-1,0,1的个数
if w[i]==-1:
a += 1
elif w[i]==0:
b += 1
else:
c += 1
fac[0] = ifac[0] = 1
for i in range(1, n+1):
fac[i] = fac[i - 1] * i % mod
ifac[i]=ifac[i-1]*qmi(i,mod-2)%mod
res1,res2,res3 = 0,0,0 #分别计算乘积为负数、0、正数的方案数
for i in range(1,a+1,2): #计算乘积为负数的个数
res1=(res1+C(a,i))%mod
res1=(res1*qmi(2,c))%mod
res2=(qmi(2,b)-1+mod)%mod
res2=(res2*qmi(2,a+c))%mod
for i in range(0,a+1,2): #计算乘积为正数的个数
res3=(res3+C(a,i))%mod
res3=(res3*qmi(2,c))%mod
res3=(res3-1+mod)%mod
print(res1,res2,res3)