比赛:
http://oj.hzjingma.com/contest/view?id=71
A.Fade
思路一、枚举
根据题目意思,。由于。所以我们可以枚举所有的 x,判断是否满足条件
根据题目的 p 的数据范围,。不会超时。如果有成立的 x 存在,说明有一个解,那就答案 + 1。
思路二、数学分析
也可以根据数学分析,我们对于,可以得到 ,根据,可以得到,要么 使得左边为 0,那么就是 p 的倍数。要么 ,使得等于 p 的倍数。
因此,解都是 2 个。
特别的,当 p = 2的时候,是重根,那解为 1 个。
#include<bits/stdc++.h>
using namespace std;
int main()
{
int p;
cin >> p;
int ans = 0;
for(int x = 1; x < p; ++x)
{
if(x * x % p == 1) ++ans;
}
cout << ans << endl;
return 0;
}
#include<bits/stdc++.h>
using namespace std;
int main()
{
int p;
cin >> p;
if(p == 2) cout << 1 << endl;
else cout << 2 << endl;
return 0;
}
B. Different World
简单思维题,无论一个数,是质数还是合数,它的倍数,几乎都是合数,除了特别的 1,要至少是 8 和 9 才会是连续的合数。
所以我们知道了差,要找到对应的两个合数,使得这两个合数的差,是输入的值。那么根据 k * x - (k - 1) * x = x,我们可以知道,只要是输入值的连续倍数(满足要都是合数),就可以了。
正常的 >= 2 的,我们可以 2 倍 和 3 倍即可。
但是对应 1 而言, 2 倍 和 3 倍 不行,至少要为 8 和 9 才满足条件。
因此我们特判,如果是 1,那就输出 8 和 9。否则,输出 输入值的 2 倍数和 3倍数。
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
int a, b;
if(n == 1)
{
a = 9;
b = 10;
}
else
{
a = 2 * n;
b = 3 * n;
}
printf("%d %d\n", a, b);
return 0;
}
C.Sing Me to Sleep
一开始的想法,枚举少的那一个数,然后求出剩余数的乘积,和。如果乘积都是最大值,那么取出,和中的最大值。
这个方法的时间复杂度应该是 O(N ^ 2),因为枚举缺少的数,剩下的数还要遍历一次相乘,相加。
根据 N 的范围不会超时。但是根据每一个值的大小,那么可能的乘积大小,最大为 10 ^ (2 * N),这个超出了数据范围,无法存储(除非用大数)。
那么能不能根据分类讨论呢,因为是要从 N 个数中,选出 N - 1 个数。我们可以根据 数据中 0 的个数分类讨论
- 当 0 的个数 >= 2的时候,那么无论 从 N 个数中,怎么取 N - 1,都会包含 0,也就是说,乘积的最大值是 0。那么只要我们在 N 个数的和,去掉最小值,就是最大的 和。
- 当 0 个数 == 1 的时候。这个时候,又分为两种情况
- 取出 N - 1 个数中,有 0。那么乘积为 0。
- 取出的 N - 1 个数中,没有 0。那么此时根据数据中,负数的个数。如果是偶数个负数,那么乘积是 正的,最大乘积也是这个值。如果是奇数个 负数,那么乘积是 负的,最大乘积是 0。
- 如果最大乘积是 0,那么对于最大和就是,我们要从 N 个数中,去掉一个最小数即可。
- 如果最大乘积是第二种情况的(也就是偶数个 负数),那么最大和是确定的,因为从 N 个数中,去掉的那个数是 0。
- 当 没有出现 0。
- 如果 N 个数的 乘积是 正数 (也就是负数个数是 偶数个)
- 全是负数的情况下,说明,去掉其中一个属之后,乘积变为负数,那么此时的最大乘积,比如 - 4,-3,-2,-1,我们应该选择大的三个,也就是说,此时的最大乘积是除去了最小数,因为最大和,也是去掉了最小数。
- 全是正数的情况下。那么最大乘积,去掉最小数,因此,最大和,也是去掉了最小数
- 有正有负的情况下, 我们应该去掉一个正数,同时为了保证是最大乘积,去掉的是,最小正数,同时,最大和,也就是去掉了这个最小正数。
- 如果 N 个数的乘积 是 负数 (也就是负数 个数 是 奇数)
- 不可能全是正数
- 全是负数的情况下,那么去掉了一个负数,乘积一定是正的,比如 比如 - 4,-3,-2,我们应该去掉 -2,保证最大乘积。因此,是去掉了最大数,同时,最大和,也是去掉了这个最大数。
- 有正有负的情况下, 我们应该去掉一个负数,同时为了保证是最大乘积,去掉的是,绝对值最小的负数,那就是最大负数,同时,最大和,也就是去掉了这个最大负数。
- 如果 N 个数的 乘积是 正数 (也就是负数个数是 偶数个)
#include <bits/stdc++.h>
using namespace std;
#define LL long long
#define INF 0x3f3f3f3f
int main() {
int n, a;
cin >> n;
vector<int> nums;
int zeros = 0;
int sum = 0;
int fushu = 0;
int zhengshu = 0;
for(int i = 0;i < n; ++i) {
cin >> a;
nums.push_back(a);
sum += a;
if(a < 0) ++fushu;
if(a > 0) ++zhengshu;
if(a == 0) ++zeros;
}
sort(nums.begin(), nums.end());
if(zeros >= 2)
{
sum -= nums[0];
cout << sum << endl;
return 0;
}
if(zeros == 1)
{
if(fushu % 2 == 0)
{
cout << sum << endl;
}
else
{
cout << sum - nums[0] << endl;
}
return 0;
}
if(fushu % 2 == 0) // 正数
{
if(zhengshu == n){
sum -= nums[0];
}
else if(fushu == n) {
sum -= nums[0];
}
else
{
int tep = INF;
for(int i = 0;i < n; ++i){
if(nums[i] < 0) continue;
tep = min(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
else // 结果是负数
{
if(fushu == n) {
sum -= nums[n - 1];
}
else
{
int tep = -INF;
for(int i = 0;i < n; ++i){
if(nums[i] > 0) continue;
tep = max(tep, nums[i]);
}
sum -= tep;
}
cout << sum << endl;
}
return 0;
}
D. Alone
要求是三角形,那就是,任意两边之和,大于第三边。
一开始的想法,是枚举所有可能的三个值,那么复杂度是 O(N ^ 3)。根据数据范围,会超时。
因此,我们要降低时间复杂度。根据数据范围,时间复杂度最大不能超过 O(N ^ 2)。
那么也就是要两层遍历,就要得到答案。
我们如果第一层循环,先固定三条边中的最大值,那么根据两边之和大于第三边,剩下的条件,就是剩下的两条没确定的边之和要大于这个 三条边的最大值。
我们对数组先排序,然后第二层循环的时候,我们 left 是剩下数的第一个值,right 是剩下数的第二个值,类似二分查找。
- 那么当这个两个满足条件的时候,我们可以发现,因为 比 left 后面的数,一定是大于 left 这个数的。因为对于 left 满足条件,剩下的数,和 right ,还有最大数,都满足,将这些都加进答案(也就是 right - left)。而且因为这两个值的大于了这个最大值了,那么对于我们来说,考虑变小点满不满足条件,那就是 right - 1
- 如果不满足条件,说明这两个数之和太小,我们想增大,那么就是 left +1.
这样子,第二层循环,我们用了双指针,双指针刚好一起走的一次数组。
所以时间复杂度是 O(N ^ 2)
这道题也是一道LeetCode的题目,第 611。
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const int MAXN = 1e4 + 10;
LL nums[MAXN];
int main() {
int n;
scanf("%d", &n);
for(int i = 0;i < n; ++i) {
scanf("%lld", &nums[i]);
}
LL count = 0;
sort(nums, nums + n);
for (int i = n - 1; i >= 2; i--) {
int left = 0, right = i - 1;
while(left < right) {
if (nums[left] + nums[right] > nums[i]) {
count += (right - left) * 1ll;
right--;
}
else {
left++;
}
}
}
cout << count << endl;
return 0;
}
E. Lost Control
对于少部分的数据,可以解决的。对于大范围的数据,是要分块打表。具体可以看官方题解,不是很懂
http://oj.hzjingma.com/contest/editorial?id=71
代码就去看官方比赛AC的人的代码
F. All Falls Down
数学公式题,根据题意,是求的前 n 项和,根据等差数列前 n 项和 公式,还是平方数的前 n 和公式,我们可以得到
注意是要求 MOD,同时,我们注意 n 的取值很大,所以对于 n 我们就需要求 MOD。
还有一个地方要注意,对于除法的取模,需要计算逆元。
也可以不用计算逆元,不过这个时候,就需要先利用 n 把 3 给约去。
- 当 n % 3 == 0,那么先计算 n / 3,可以刚好相除
- 当 n % 3 == 1,那么先计算 (2n + 1) / 3
- 当 n % 3 == 2,那么先计算 (n + 1) / 3
#include <bits/stdc++.h>
using namespace std;
#define LL long long
const LL MOD = 1e9 + 7;
int main() {
LL res = 0;
LL n;
cin >> n;
if(n % 3 == 0)
{
res = (((((2 * n / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
else if(n % 3 == 1)
{
res = (((((2 * (2 * n + 1) / 3) % MOD) * ((n + 1) % MOD)) % MOD) * ((n) % MOD)) % MOD;
}
else if(n % 3 == 2)
{
res = (((((2 * (n + 1) / 3) % MOD) * ((n) % MOD)) % MOD) * ((2 * n + 1) % MOD)) % MOD;
}
LL tep = (n % MOD) * ((n + 1) % MOD) % MOD;
res = (res - tep + MOD) % MOD;
cout << res << endl;
return 0;
}
G. Love
这是一道,DP问题,主要是设好变量
- 设 表示,第 i 个元素,填的 num,此时递减长度为 k,前 i 项(包括该项)和为 sum 的序列种类。
- 初始条件,对于第一个元素,如果是 - 1,也就是说,这个元素可以随便放,那么就是 。如果这个元素不是 -1,也就是有值。假设输入的数组为 a,那么
- 状态转移,我们对于当前状态
- 当前状态为 -1,那么就是这个值可以随便放,那么遍历此时可以放的 值 j 从 0 到 40。这个值是从上一个值过来,那么L上一个值也可以是 0 到 40,遍历。同时,我们还要遍历,前 i - 1 的 和(要保证 前面的平均数 >= j )才可以状态过来。同时转移过来的,还要求比较 L 和 j 的值(因为递减长度不能为 3)
- 假如 L > j,那么只能是前面递减序列为长度为 1 的转移到,现在的变成了 2.
- 假如 L <= j,那么此时不消耗递减序列,所以可以是 递减序列 1 - 1,也可以是 2 到 2。
- 当前状态不为 -1,也是就是 上面讨论的 j 是确定的 a[i] 的值,剩下的和上面一样。
- 转移过来的时候,是值进行累加。
- 当前状态为 -1,那么就是这个值可以随便放,那么遍历此时可以放的 值 j 从 0 到 40。这个值是从上一个值过来,那么L上一个值也可以是 0 到 40,遍历。同时,我们还要遍历,前 i - 1 的 和(要保证 前面的平均数 >= j )才可以状态过来。同时转移过来的,还要求比较 L 和 j 的值(因为递减长度不能为 3)
- 结果,结果就是,我们知道所有元素结束 n,但是不知道 num,k,sum,所以枚举所有中,任何一种都是可能的序列。问题问,我们有多少种序列,那就是把所有可能情况的值,都累加起来。
#include <bits/stdc++.h>
using namespace std;
#define Mod 1000000007
long long a[42];
long long dp[42][42][3][1602];
int main(){
int n;
memset(dp,0,sizeof(dp));
cin>>n;
for(int i=1;i<=n;i++)cin>>a[i];
if(a[1]==-1){ // 初始条件
for(int i=0;i<=40;i++)
dp[1][i][1][i]=1;
}
else
dp[1][a[1]][1][a[1]]=1;
// 从 2 开始
for(int i=2;i<=n;i++)
{
if(a[i]==-1) // == -1
{
for(int j=0;j<=40;j++) // 当前值 j 都有可能
{
for(int L=0;L<=40;L++) // 上一个值的取值 L 都可以转移过来
{
// 对于前 i - 1的和 k,要求 k/(i - 1) >= j,所以 k 从 (i - 1) * j 开始,但是总和值不会超过 1600(因为最长 40,每一个元素最大 40).所以 k + j <= 1600确定上界
for(int k=j*(i-1);k<=1600-j;k++)
{
if(j>=L)
{
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][1][k])%Mod;
dp[i][j][1][k+j] = (dp[i][j][1][k+j]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][j][2][k+j]=(dp[i][j][2][k+j]+dp[i-1][L][1][k])%Mod;
}
}
}
}
else // a[i] != -1,也就是上面 j = a[i],是确定的
{
for(int L=0;L<=40;L++)
{
for(int k=a[i]*(i-1);k<=1600-a[i];k++)
{
if(a[i]>=L)
{
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][1][k])%Mod;
dp[i][a[i]][1][k+a[i]] = (dp[i][a[i]][1][k+a[i]]+dp[i-1][L][2][k])%Mod;
}
else
dp[i][a[i]][2][k+a[i]]=(dp[i][a[i]][2][k+a[i]]+dp[i-1][L][1][k])%Mod;
}
}
}
}
// 最后答案累加,对于 n 结束的时候,枚举 num,k,sum的所有可能成立的序列
long long sum=0;
for(int j=0;j<=40;j++){
for(int k=j*n;k<=1600;k++){
sum=(sum+dp[n][j][1][k])%Mod;
sum=(sum+dp[n][j][2][k])%Mod;
}
}
cout<<sum<<endl;
return 0;
}
H. Diamond Heart
注意到,此题如果真的去一一枚举dist,那么复杂度最优也是O(n^2logn)的
我们考虑dist的意义。对于每一条边,这条边两端上,左半边的点与右半边的点,一定会经过这条边。所以这条边会被经过左半边点数 * 右半边点数,这条边的权值是w,所以这条边产生的答案贡献就是次数 * 权值。
把所有边的贡献加起来就是答案了。
因此,先要构建图,然后 DFS,我们从一个节点 u,找到 v 节点 的时候,需要统计(u --- v), v 那边的的点数,因为我们需要 DFS 还可以附带计算 某一个点这边的点数,我们用 一个数组 sz 记录。
从 u 父节点开始的时候,表示在 u 这边有一个点(u 本身),所以 sz[u] = 1。
然乎遍历这个 u 的所有子节点 v,得到 v 的时候,由于我们要先计算 v 这边都有多少点(从而得到 一个半边的点数,同时另一半边的点数 是 n - 另一边,从而计算答案)。所以我们先继续 DFS,等这里的 DFS 返回的时候,表示 sz[v] 这里计算好了,那么就计算。同时这里返回得到 v 的点数,那么我们要更新 u 的点数(因为 u 的这边,包括了 v,所以 v 的点数,加回到 u 上),sz[ u ] += sz[ v ]。
#include <bits/stdc++.h>
using namespace std;
#define ll long long
const int N = 200000 + 11;
typedef pair<int, int> pii;
ll ans;
int n;
vector<pii> G[N]; // 图
int sz[N];
void DFS(int u,int f){
sz[u] = 1; // 这个点的点数 初始化,就自己 = 1
for(int i = 0; i < G[u].size(); i++){
int v = G[u][i].first;
if(v == f) continue; // 因为是 u 到 v,如果 u 回到了自己的父节点,那就不考虑,因为是要找子节点
DFS(v, u); // 先继续 DFS,因为 DFS的一个隐返回值,可以返回,这个点半边的点数
ans = (ans + sz[v] * 1ll * (n - sz[v]) * G[u][i].second);
sz[u] += sz[v]; // 返回了 v 的点数,那么 u 的点数,累加 v 起来
}
}
int main(){
scanf("%d", &n);
for(int i = 0;i < n - 1; ++i)
{
int a, b, c; scanf("%d%d%d", &a, &b, &c);
G[a].push_back(pii(b, c));
G[b].push_back(pii(a, c));
}
ans = 0; // 答案
DFS(1, -1); // 随便从某一个节点出发
printf("%lld\n", ans);
return 0;
}
I. Lily
KMP的题目(等待学习中)
看到这个Alan Walker值的定义,如果对kmp熟悉的选手一定知道,这就是next[]数组的含义。
所以跑一遍kmp,然后取个min就好了。
#include <bits/stdc++.h>
using namespace std;
int main() {
ios :: sync_with_stdio(false); cin.tie(0);
string s; cin >> s;
int n = s.size(); s = ' ' + s;
vector <int> kmp(n + 1);
for (int i = 2, j = 0; i <= n; i++) {
while (j > 0 && s[i] != s[j + 1]) {
j = kmp[j];
}
if (s[i] == s[j + 1]) {
j++;
}
kmp[i] = j;
//cout << "kmp[" << i << "] = " << kmp[i] << '\n';
}
int q; cin >> q;
while (q--) {
int l, k;
cin >> l >> k;
if (kmp[l] >= k) {
cout << 0 << '\n';
} else {
cout << k - kmp[l] << '\n';
}
}
return 0;
}