【算法比赛】竞码编程-IOI赛制测试赛1

比赛:

http://oj.hzjingma.com/contest/view?id=71


A.Fade

思路一、枚举

根据题目意思,x^2 \% p = 1。由于x<p。所以我们可以枚举所有的  x,判断是否满足条件

根据题目的 p 的数据范围,p < 1000。不会超时。如果有成立的 x 存在,说明有一个解,那就答案 + 1。

 

思路二、数学分析

也可以根据数学分析,我们对于x^2 \% p = 1,可以得到 (x + 1)(x - 1) \% p = 0,根据x<p,可以得到,要么 x - 1 = 0使得左边为 0,那么就是 p 的倍数。要么 x + 1 = p,使得等于  p 的倍数。

因此,解都是 2 个。x = 1 \, or\, x = p - 1

特别的,当 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,保证最大乘积。因此,是去掉了最大数,同时,最大和,也是去掉了这个最大数。
      • 有正有负的情况下, 我们应该去掉一个负数,同时为了保证是最大乘积,去掉的是,绝对值最小的负数,那就是最大负数,同时,最大和,也就是去掉了这个最大负数。

 

 

#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

数学公式题,根据题意,是求4 i ^ 2 - 2i的前 n 项和,根据等差数列前 n 项和 公式,还是平方数的前 n 和公式,我们可以得到4\frac{n(n + 1)(2n + 1)}{6} - 2\frac{n(n +1)}{2} = \frac{2n(n + 1)(2n + 1)}{3} - n(n +1)

注意是要求 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问题,主要是设好变量

  • 设 dp[i][num][k][sum] 表示,第 i 个元素,填的 num,此时递减长度为 k,前 i 项(包括该项)和为 sum 的序列种类。
  • 初始条件,对于第一个元素,如果是 - 1,也就是说,这个元素可以随便放,那么就是 dp[1][num = 0 \, to \, 40][1][num] = 1。如果这个元素不是 -1,也就是有值。假设输入的数组为 a,那么dp[1][a[1]][1][a[1]] = 1
  • 状态转移,我们对于当前状态
    • 当前状态为 -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] 的值,剩下的和上面一样。
    • 转移过来的时候,是值进行累加。
  • 结果,结果就是,我们知道所有元素结束 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;
}

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值